mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-11-09 13:09:21 +03:00
fix: push to sign in screen when logout (#3127)
* fix: push to sign in screen when logout * chore: show historical login users * chore: open historical user * chore: show historical user * chore: reload app widget with unique key * chore: add tooltip for user history
This commit is contained in:
parent
a3bea472bf
commit
3c04b72932
@ -89,6 +89,7 @@ class ApplicationWidget extends StatelessWidget {
|
||||
],
|
||||
child: BlocBuilder<AppearanceSettingsCubit, AppearanceSettingsState>(
|
||||
builder: (context, state) => MaterialApp(
|
||||
key: UniqueKey(),
|
||||
builder: overlayManagerBuilder(),
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: state.lightTheme,
|
||||
|
@ -215,6 +215,22 @@ extension on String {
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a completer that listens to Supabase authentication state changes and
|
||||
/// completes when a user signs in.
|
||||
///
|
||||
/// This function sets up a listener on Supabase's authentication state. When a user
|
||||
/// signs in, it triggers the provided [onSuccess] callback with the user's `id` and
|
||||
/// `email`. Once the [onSuccess] callback is executed and a response is received,
|
||||
/// the completer completes with the response, and the listener is canceled.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - [onSuccess]: A callback function that's executed when a user signs in. It
|
||||
/// should take in a user's `id` and `email` and return a `Future` containing either
|
||||
/// a `FlowyError` or a `UserProfilePB`.
|
||||
///
|
||||
/// Returns:
|
||||
/// A completer of type `Either<FlowyError, UserProfilePB>`. This completer completes
|
||||
/// with the response from the [onSuccess] callback when a user signs in.
|
||||
Completer<Either<FlowyError, UserProfilePB>> supabaseLoginCompleter({
|
||||
required Future<Either<FlowyError, UserProfilePB>> Function(
|
||||
String userId,
|
||||
@ -227,16 +243,15 @@ Completer<Either<FlowyError, UserProfilePB>> supabaseLoginCompleter({
|
||||
|
||||
subscription = auth.onAuthStateChange.listen((event) async {
|
||||
final user = event.session?.user;
|
||||
if (event.event != AuthChangeEvent.signedIn || user == null) {
|
||||
completer.complete(left(AuthError.supabaseSignInWithOauthError));
|
||||
} else {
|
||||
if (event.event == AuthChangeEvent.signedIn && user != null) {
|
||||
final response = await onSuccess(
|
||||
user.id,
|
||||
user.email ?? user.newEmail ?? '',
|
||||
);
|
||||
// Only cancel the subscription if the Event is signedIn.
|
||||
subscription.cancel();
|
||||
completer.complete(response);
|
||||
}
|
||||
subscription.cancel();
|
||||
});
|
||||
return completer;
|
||||
}
|
||||
|
@ -70,6 +70,24 @@ class UserBackendService {
|
||||
return UserEventInitUser().send();
|
||||
}
|
||||
|
||||
Future<Either<List<HistoricalUserPB>, FlowyError>>
|
||||
loadHistoricalUsers() async {
|
||||
return UserEventGetHistoricalUsers().send().then(
|
||||
(result) {
|
||||
return result.fold(
|
||||
(historicalUsers) => left(historicalUsers.items),
|
||||
(error) => right(error),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> openHistoricalUser(
|
||||
HistoricalUserPB user,
|
||||
) async {
|
||||
return UserEventOpenHistoricalUser(user).send();
|
||||
}
|
||||
|
||||
Future<Either<List<WorkspacePB>, FlowyError>> getWorkspaces() {
|
||||
final request = WorkspaceIdPB.create();
|
||||
|
||||
|
@ -13,6 +13,13 @@ import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
const routerNameRoot = '/';
|
||||
const routerNameSignUp = '/signUp';
|
||||
const routerNameSignIn = '/signIn';
|
||||
const routerNameSkipLogIn = '/skipLogIn';
|
||||
const routerNameWelcome = '/welcome';
|
||||
const routerNameHome = '/home';
|
||||
|
||||
class AuthRouter {
|
||||
void pushForgetPasswordScreen(BuildContext context) {}
|
||||
|
||||
@ -24,6 +31,7 @@ class AuthRouter {
|
||||
Navigator.of(context).push(
|
||||
PageRoutes.fade(
|
||||
() => SignUpScreen(router: getIt<AuthRouter>()),
|
||||
const RouteSettings(name: routerNameSignUp),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -41,6 +49,7 @@ class AuthRouter {
|
||||
workspaceSetting,
|
||||
key: ValueKey(profile.id),
|
||||
),
|
||||
const RouteSettings(name: routerNameHome),
|
||||
RouteDurations.slow.inMilliseconds * .001,
|
||||
),
|
||||
);
|
||||
@ -71,6 +80,7 @@ class SplashRoute {
|
||||
await Navigator.of(context).push(
|
||||
PageRoutes.fade(
|
||||
() => screen,
|
||||
const RouteSettings(name: routerNameWelcome),
|
||||
RouteDurations.slow.inMilliseconds * .001,
|
||||
),
|
||||
);
|
||||
@ -97,6 +107,7 @@ class SplashRoute {
|
||||
workspaceSetting,
|
||||
key: ValueKey(userProfile.id),
|
||||
),
|
||||
const RouteSettings(name: routerNameWelcome),
|
||||
RouteDurations.slow.inMilliseconds * .001,
|
||||
),
|
||||
);
|
||||
@ -107,6 +118,7 @@ class SplashRoute {
|
||||
context,
|
||||
PageRoutes.fade(
|
||||
() => SignInScreen(router: getIt<AuthRouter>()),
|
||||
const RouteSettings(name: routerNameSignIn),
|
||||
RouteDurations.slow.inMilliseconds * .001,
|
||||
),
|
||||
);
|
||||
@ -120,6 +132,7 @@ class SplashRoute {
|
||||
router: getIt<AuthRouter>(),
|
||||
authService: getIt<AuthService>(),
|
||||
),
|
||||
const RouteSettings(name: routerNameSkipLogIn),
|
||||
RouteDurations.slow.inMilliseconds * .001,
|
||||
),
|
||||
);
|
||||
|
@ -320,14 +320,16 @@ class ThirdPartySignInButton extends StatelessWidget {
|
||||
}
|
||||
|
||||
class ThirdPartySignInButtons extends StatelessWidget {
|
||||
final MainAxisAlignment mainAxisAlignment;
|
||||
const ThirdPartySignInButtons({
|
||||
this.mainAxisAlignment = MainAxisAlignment.center,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisAlignment: mainAxisAlignment,
|
||||
children: [
|
||||
ThirdPartySignInButton(
|
||||
icon: 'login/google-mark',
|
||||
|
@ -8,10 +8,9 @@ import 'package:protobuf/protobuf.dart';
|
||||
|
||||
part 'setting_supabase_bloc.freezed.dart';
|
||||
|
||||
class SettingSupabaseBloc
|
||||
extends Bloc<SettingSupabaseEvent, SettingSupabaseState> {
|
||||
SettingSupabaseBloc() : super(SettingSupabaseState.initial()) {
|
||||
on<SettingSupabaseEvent>((event, emit) async {
|
||||
class SyncSettingBloc extends Bloc<SyncSettingEvent, SyncSettingState> {
|
||||
SyncSettingBloc() : super(SyncSettingState.initial()) {
|
||||
on<SyncSettingEvent>((event, emit) async {
|
||||
await event.when(
|
||||
initial: () async {
|
||||
await getSupabaseConfig();
|
||||
@ -27,7 +26,7 @@ class SettingSupabaseBloc
|
||||
emit(state.copyWith(config: newConfig));
|
||||
}
|
||||
},
|
||||
didReceiveSupabseConfig: (SupabaseConfigPB config) {
|
||||
didReceiveSyncConfig: (SupabaseConfigPB config) {
|
||||
emit(state.copyWith(config: config));
|
||||
},
|
||||
);
|
||||
@ -43,7 +42,7 @@ class SettingSupabaseBloc
|
||||
result.fold(
|
||||
(config) {
|
||||
if (!isClosed) {
|
||||
add(SettingSupabaseEvent.didReceiveSupabseConfig(config));
|
||||
add(SyncSettingEvent.didReceiveSyncConfig(config));
|
||||
}
|
||||
},
|
||||
(r) => Log.error(r),
|
||||
@ -52,22 +51,22 @@ class SettingSupabaseBloc
|
||||
}
|
||||
|
||||
@freezed
|
||||
class SettingSupabaseEvent with _$SettingSupabaseEvent {
|
||||
const factory SettingSupabaseEvent.initial() = _Initial;
|
||||
const factory SettingSupabaseEvent.didReceiveSupabseConfig(
|
||||
class SyncSettingEvent with _$SyncSettingEvent {
|
||||
const factory SyncSettingEvent.initial() = _Initial;
|
||||
const factory SyncSettingEvent.didReceiveSyncConfig(
|
||||
SupabaseConfigPB config,
|
||||
) = _DidReceiveSupabaseConfig;
|
||||
const factory SettingSupabaseEvent.enableSync(bool enable) = _EnableSync;
|
||||
) = _DidSyncSupabaseConfig;
|
||||
const factory SyncSettingEvent.enableSync(bool enable) = _EnableSync;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class SettingSupabaseState with _$SettingSupabaseState {
|
||||
const factory SettingSupabaseState({
|
||||
class SyncSettingState with _$SyncSettingState {
|
||||
const factory SyncSettingState({
|
||||
SupabaseConfigPB? config,
|
||||
required Either<Unit, String> successOrFailure,
|
||||
}) = _SettingSupabaseState;
|
||||
}) = _SyncSettingState;
|
||||
|
||||
factory SettingSupabaseState.initial() => SettingSupabaseState(
|
||||
factory SyncSettingState.initial() => SyncSettingState(
|
||||
successOrFailure: left(unit),
|
||||
);
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ enum SettingsPage {
|
||||
language,
|
||||
files,
|
||||
user,
|
||||
supabaseSetting,
|
||||
syncSetting,
|
||||
shortcuts,
|
||||
}
|
||||
|
||||
|
@ -23,6 +23,7 @@ class SettingsUserViewBloc extends Bloc<SettingsUserEvent, SettingsUserState> {
|
||||
initial: () async {
|
||||
_userListener.start(onProfileUpdated: _profileUpdated);
|
||||
await _initUser();
|
||||
_loadHistoricalUsers();
|
||||
},
|
||||
didReceiveUserProfile: (UserProfilePB newUserProfile) {
|
||||
emit(state.copyWith(userProfile: newUserProfile));
|
||||
@ -51,6 +52,12 @@ class SettingsUserViewBloc extends Bloc<SettingsUserEvent, SettingsUserState> {
|
||||
);
|
||||
});
|
||||
},
|
||||
didLoadHistoricalUsers: (List<HistoricalUserPB> historicalUsers) {
|
||||
emit(state.copyWith(historicalUsers: historicalUsers));
|
||||
},
|
||||
openHistoricalUser: (HistoricalUserPB historicalUser) async {
|
||||
await _userService.openHistoricalUser(historicalUser);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
@ -66,10 +73,22 @@ class SettingsUserViewBloc extends Bloc<SettingsUserEvent, SettingsUserState> {
|
||||
result.fold((l) => null, (error) => Log.error(error));
|
||||
}
|
||||
|
||||
Future<void> _loadHistoricalUsers() async {
|
||||
final result = await _userService.loadHistoricalUsers();
|
||||
result.fold(
|
||||
(historicalUsers) {
|
||||
add(SettingsUserEvent.didLoadHistoricalUsers(historicalUsers));
|
||||
},
|
||||
(error) => Log.error(error),
|
||||
);
|
||||
}
|
||||
|
||||
void _profileUpdated(Either<UserProfilePB, FlowyError> userProfileOrFailed) {
|
||||
userProfileOrFailed.fold(
|
||||
(newUserProfile) =>
|
||||
add(SettingsUserEvent.didReceiveUserProfile(newUserProfile)),
|
||||
(newUserProfile) {
|
||||
add(SettingsUserEvent.didReceiveUserProfile(newUserProfile));
|
||||
_loadHistoricalUsers();
|
||||
},
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
}
|
||||
@ -86,18 +105,26 @@ class SettingsUserEvent with _$SettingsUserEvent {
|
||||
const factory SettingsUserEvent.didReceiveUserProfile(
|
||||
UserProfilePB newUserProfile,
|
||||
) = _DidReceiveUserProfile;
|
||||
const factory SettingsUserEvent.didLoadHistoricalUsers(
|
||||
List<HistoricalUserPB> historicalUsers,
|
||||
) = _DidLoadHistoricalUsers;
|
||||
const factory SettingsUserEvent.openHistoricalUser(
|
||||
HistoricalUserPB historicalUser,
|
||||
) = _OpenHistoricalUser;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class SettingsUserState with _$SettingsUserState {
|
||||
const factory SettingsUserState({
|
||||
required UserProfilePB userProfile,
|
||||
required List<HistoricalUserPB> historicalUsers,
|
||||
required Either<Unit, String> successOrFailure,
|
||||
}) = _SettingsUserState;
|
||||
|
||||
factory SettingsUserState.initial(UserProfilePB userProfile) =>
|
||||
SettingsUserState(
|
||||
userProfile: userProfile,
|
||||
historicalUsers: [],
|
||||
successOrFailure: left(unit),
|
||||
);
|
||||
}
|
||||
|
@ -105,12 +105,13 @@ class MenuUser extends StatelessWidget {
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
builder: (dialogContext) {
|
||||
return BlocProvider<DocumentAppearanceCubit>.value(
|
||||
value: BlocProvider.of<DocumentAppearanceCubit>(context),
|
||||
child: SettingsDialog(
|
||||
userProfile,
|
||||
didLogout: () async {
|
||||
Navigator.of(dialogContext).pop();
|
||||
Navigator.of(context).pop();
|
||||
await FlowyRunner.run(
|
||||
FlowyApp(),
|
||||
@ -118,6 +119,7 @@ class MenuUser extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
dismissDialog: () => Navigator.of(context).pop(),
|
||||
didOpenUser: () {},
|
||||
),
|
||||
);
|
||||
},
|
||||
|
@ -109,19 +109,30 @@ class SidebarUser extends StatelessWidget {
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
builder: (dialogContext) {
|
||||
return BlocProvider<DocumentAppearanceCubit>.value(
|
||||
value: BlocProvider.of<DocumentAppearanceCubit>(context),
|
||||
child: SettingsDialog(
|
||||
userProfile,
|
||||
didLogout: () async {
|
||||
Navigator.of(context).pop();
|
||||
// Pop the dialog using the dialog context
|
||||
Navigator.of(dialogContext).pop();
|
||||
|
||||
await FlowyRunner.run(
|
||||
FlowyApp(),
|
||||
integrationEnv(),
|
||||
);
|
||||
},
|
||||
dismissDialog: () => Navigator.of(context).pop(),
|
||||
didOpenUser: () async {
|
||||
// Pop the dialog using the dialog context
|
||||
Navigator.of(dialogContext).pop();
|
||||
|
||||
await FlowyRunner.run(
|
||||
FlowyApp(),
|
||||
integrationEnv(),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
|
@ -1,6 +1,6 @@
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/setting_supabase_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/sync_setting_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_customize_shortcuts_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_file_system_view.dart';
|
||||
@ -20,11 +20,13 @@ const _contentInsetPadding = EdgeInsets.fromLTRB(0.0, 12.0, 0.0, 16.0);
|
||||
class SettingsDialog extends StatelessWidget {
|
||||
final VoidCallback dismissDialog;
|
||||
final VoidCallback didLogout;
|
||||
final VoidCallback didOpenUser;
|
||||
final UserProfilePB user;
|
||||
SettingsDialog(
|
||||
this.user, {
|
||||
required this.dismissDialog,
|
||||
required this.didLogout,
|
||||
required this.didOpenUser,
|
||||
Key? key,
|
||||
}) : super(key: ValueKey(user.id));
|
||||
|
||||
@ -97,9 +99,10 @@ class SettingsDialog extends StatelessWidget {
|
||||
user,
|
||||
didLogin: () => dismissDialog(),
|
||||
didLogout: didLogout,
|
||||
didOpenUser: didOpenUser,
|
||||
);
|
||||
case SettingsPage.supabaseSetting:
|
||||
return const SupabaseSettingView();
|
||||
case SettingsPage.syncSetting:
|
||||
return const SyncSettingView();
|
||||
case SettingsPage.shortcuts:
|
||||
return const SettingsCustomizeShortcutsWrapper();
|
||||
default:
|
||||
|
@ -0,0 +1,110 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/application/user/settings_user_bloc.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class HistoricalUserList extends StatelessWidget {
|
||||
final VoidCallback didOpenUser;
|
||||
const HistoricalUserList({required this.didOpenUser, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<SettingsUserViewBloc, SettingsUserState>(
|
||||
builder: (context, state) {
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 200),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
FlowyText.medium(
|
||||
LocaleKeys.settings_menu_historicalUserList.tr(),
|
||||
fontSize: 13,
|
||||
),
|
||||
const Spacer(),
|
||||
Tooltip(
|
||||
message:
|
||||
LocaleKeys.settings_menu_historicalUserListTooltip.tr(),
|
||||
child: const Icon(
|
||||
Icons.question_mark_rounded,
|
||||
size: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemBuilder: (context, index) {
|
||||
final user = state.historicalUsers[index];
|
||||
return HistoricalUserItem(
|
||||
key: ValueKey(user.userId),
|
||||
user: user,
|
||||
isSelected: state.userProfile.id == user.userId,
|
||||
didOpenUser: didOpenUser,
|
||||
);
|
||||
},
|
||||
itemCount: state.historicalUsers.length,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class HistoricalUserItem extends StatelessWidget {
|
||||
final VoidCallback didOpenUser;
|
||||
final bool isSelected;
|
||||
final HistoricalUserPB user;
|
||||
const HistoricalUserItem({
|
||||
required this.user,
|
||||
required this.isSelected,
|
||||
required this.didOpenUser,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final icon = isSelected ? const FlowySvg(name: "grid/checkmark") : null;
|
||||
final isDisabled = isSelected || user.authType != AuthTypePB.Local;
|
||||
final outputFormat = DateFormat('MM/dd/yyyy');
|
||||
final date =
|
||||
DateTime.fromMillisecondsSinceEpoch(user.lastTime.toInt() * 1000);
|
||||
final lastTime = outputFormat.format(date);
|
||||
final desc = "${user.userName} ${user.authType} $lastTime";
|
||||
final child = SizedBox(
|
||||
height: 30,
|
||||
child: FlowyButton(
|
||||
disable: isDisabled,
|
||||
text: FlowyText.medium(desc),
|
||||
rightIcon: icon,
|
||||
onTap: () {
|
||||
if (user.userId ==
|
||||
context.read<SettingsUserViewBloc>().userProfile.id) {
|
||||
return;
|
||||
}
|
||||
context
|
||||
.read<SettingsUserViewBloc>()
|
||||
.add(SettingsUserEvent.openHistoricalUser(user));
|
||||
didOpenUser();
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
if (isSelected) {
|
||||
return child;
|
||||
} else {
|
||||
return Tooltip(
|
||||
message: LocaleKeys.settings_menu_openHistoricalUser.tr(),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
import 'package:appflowy/workspace/application/settings/setting_supabase_bloc.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class SupabaseSettingView extends StatelessWidget {
|
||||
const SupabaseSettingView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) =>
|
||||
SettingSupabaseBloc()..add(const SettingSupabaseEvent.initial()),
|
||||
child: BlocBuilder<SettingSupabaseBloc, SettingSupabaseState>(
|
||||
builder: (context, state) {
|
||||
return Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: Switch(
|
||||
onChanged: (bool value) {
|
||||
context.read<SettingSupabaseBloc>().add(
|
||||
SettingSupabaseEvent.enableSync(value),
|
||||
);
|
||||
},
|
||||
value: state.config?.enableSync ?? false,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/startup/entry_point.dart';
|
||||
import 'package:appflowy/startup/launch_configuration.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
@ -6,6 +7,8 @@ import 'package:appflowy/user/presentation/sign_in_screen.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/snap_bar.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@ -25,7 +28,15 @@ class SettingThirdPartyLogin extends StatelessWidget {
|
||||
(result) => _handleSuccessOrFail(result, context),
|
||||
);
|
||||
},
|
||||
builder: (_, __) => const ThirdPartySignInButtons(),
|
||||
builder: (_, __) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
FlowyText.medium(LocaleKeys.signIn_signInWith.tr()),
|
||||
const ThirdPartySignInButtons(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -64,9 +64,9 @@ class SettingsMenu extends StatelessWidget {
|
||||
context.read<SettingsDialogBloc>().state.userProfile.authType !=
|
||||
AuthTypePB.Local)
|
||||
SettingsMenuElement(
|
||||
page: SettingsPage.supabaseSetting,
|
||||
page: SettingsPage.syncSetting,
|
||||
selectedPage: currentPage,
|
||||
label: LocaleKeys.settings_menu_supabaseSetting.tr(),
|
||||
label: LocaleKeys.settings_menu_syncSetting.tr(),
|
||||
icon: Icons.sync,
|
||||
changeSelectedPage: changeSelectedPage,
|
||||
),
|
||||
|
@ -16,19 +16,25 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import 'historical_user.dart';
|
||||
import 'setting_third_party_login.dart';
|
||||
|
||||
const defaultUserAvatar = '1F600';
|
||||
const _iconSize = Size(60, 60);
|
||||
|
||||
class SettingsUserView extends StatelessWidget {
|
||||
// Called when the user login in the setting dialog
|
||||
final VoidCallback didLogin;
|
||||
// Called when the user logout in the setting dialog
|
||||
final VoidCallback didLogout;
|
||||
// Called when the user open a historical user in the setting dialog
|
||||
final VoidCallback didOpenUser;
|
||||
final UserProfilePB user;
|
||||
SettingsUserView(
|
||||
this.user, {
|
||||
required this.didLogin,
|
||||
required this.didLogout,
|
||||
required this.didOpenUser,
|
||||
Key? key,
|
||||
}) : super(key: ValueKey(user.id));
|
||||
|
||||
@ -47,6 +53,8 @@ class SettingsUserView extends StatelessWidget {
|
||||
_renderCurrentIcon(context),
|
||||
const VSpace(20),
|
||||
_renderCurrentOpenaiKey(context),
|
||||
const VSpace(20),
|
||||
_renderHistoricalUser(context),
|
||||
const Spacer(),
|
||||
_renderLoginOrLogoutButton(context, state),
|
||||
const VSpace(20),
|
||||
@ -56,21 +64,25 @@ class SettingsUserView extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
/// Renders either a login or logout button based on the user's authentication status.
|
||||
///
|
||||
/// This function checks the current user's authentication type and Supabase
|
||||
/// configuration to determine whether to render a third-party login button
|
||||
/// or a logout button.
|
||||
Widget _renderLoginOrLogoutButton(
|
||||
BuildContext context,
|
||||
SettingsUserState state,
|
||||
) {
|
||||
if (!isSupabaseEnabled) {
|
||||
return _renderLogoutButton(context);
|
||||
if (isSupabaseEnabled) {
|
||||
// If the user is logged in locally, render a third-party login button.
|
||||
if (state.userProfile.authType == AuthTypePB.Local) {
|
||||
return SettingThirdPartyLogin(
|
||||
didLogin: didLogin,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (state.userProfile.authType == AuthTypePB.Local) {
|
||||
return SettingThirdPartyLogin(
|
||||
didLogin: didLogin,
|
||||
);
|
||||
} else {
|
||||
return _renderLogoutButton(context);
|
||||
}
|
||||
return _renderLogoutButton(context);
|
||||
}
|
||||
|
||||
Widget _renderUserNameInput(BuildContext context) {
|
||||
@ -111,6 +123,16 @@ class SettingsUserView extends StatelessWidget {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _renderHistoricalUser(BuildContext context) {
|
||||
return BlocBuilder<SettingsUserViewBloc, SettingsUserState>(
|
||||
builder: (context, state) {
|
||||
return HistoricalUserList(
|
||||
didOpenUser: didOpenUser,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
|
@ -0,0 +1,36 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/application/settings/setting_supabase_bloc.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class SyncSettingView extends StatelessWidget {
|
||||
const SyncSettingView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) =>
|
||||
SyncSettingBloc()..add(const SyncSettingEvent.initial()),
|
||||
child: BlocBuilder<SyncSettingBloc, SyncSettingState>(
|
||||
builder: (context, state) {
|
||||
return Row(
|
||||
children: [
|
||||
FlowyText.medium(LocaleKeys.settings_menu_enableSync.tr()),
|
||||
const Spacer(),
|
||||
Switch(
|
||||
onChanged: (bool value) {
|
||||
context.read<SyncSettingBloc>().add(
|
||||
SyncSettingEvent.enableSync(value),
|
||||
);
|
||||
},
|
||||
value: state.config?.enableSync ?? false,
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -8,9 +8,10 @@ class PageRoutes {
|
||||
static const Curve kDefaultEaseFwd = Curves.easeOut;
|
||||
static const Curve kDefaultEaseReverse = Curves.easeOut;
|
||||
|
||||
static Route<T> fade<T>(PageBuilder pageBuilder,
|
||||
static Route<T> fade<T>(PageBuilder pageBuilder, RouteSettings? settings,
|
||||
[double duration = kDefaultDuration]) {
|
||||
return PageRouteBuilder<T>(
|
||||
settings: settings,
|
||||
transitionDuration: Duration(milliseconds: (duration * 1000).round()),
|
||||
pageBuilder: (context, animation, secondaryAnimation) => pageBuilder(),
|
||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
||||
|
@ -26,7 +26,8 @@
|
||||
"alreadyHaveAnAccount": "Already have an account?",
|
||||
"emailHint": "Email",
|
||||
"passwordHint": "Password",
|
||||
"repeatPasswordHint": "Repeat password"
|
||||
"repeatPasswordHint": "Repeat password",
|
||||
"signUpWith": "Sign up with:"
|
||||
},
|
||||
"signIn": {
|
||||
"loginTitle": "Login to @:appName",
|
||||
@ -38,7 +39,8 @@
|
||||
"passwordHint": "Password",
|
||||
"dontHaveAnAccount": "Don't have an account?",
|
||||
"repeatPasswordEmptyError": "Repeat password can't be empty",
|
||||
"unmatchedPasswordError": "Repeat password is not the same as password"
|
||||
"unmatchedPasswordError": "Repeat password is not the same as password",
|
||||
"signInWith": "Sign in with:"
|
||||
},
|
||||
"workspace": {
|
||||
"create": "Create workspace",
|
||||
@ -223,7 +225,11 @@
|
||||
"open": "Open Settings",
|
||||
"logout": "Logout",
|
||||
"logoutPrompt": "Are you sure to logout?",
|
||||
"supabaseSetting": "Supabase Setting"
|
||||
"syncSetting": "Sync Setting",
|
||||
"enableSync": "Enable sync",
|
||||
"historicalUserList": "User history",
|
||||
"historicalUserListTooltip": "This list shows your login history. You can click to login if it's a local user",
|
||||
"openHistoricalUser": "Click to open user"
|
||||
},
|
||||
"appearance": {
|
||||
"fontFamily": {
|
||||
|
@ -1,4 +1,5 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
use appflowy_integrate::collab_builder::{CollabStorageProvider, CollabStorageType};
|
||||
@ -37,14 +38,24 @@ pub enum ServerProviderType {
|
||||
/// Offline mode, no user authentication and the data is stored locally.
|
||||
Local = 0,
|
||||
/// Self-hosted server provider.
|
||||
/// The [AppFlowy-Server](https://github.com/AppFlowy-IO/AppFlowy-Server) is still a work in
|
||||
/// The [AppFlowy-Server](https://github.com/AppFlowy-IO/AppFlowy-Cloud) is still a work in
|
||||
/// progress.
|
||||
SelfHosted = 1,
|
||||
AppFlowyCloud = 1,
|
||||
/// Supabase server provider.
|
||||
/// It uses supabase's postgresql database to store data and user authentication.
|
||||
Supabase = 2,
|
||||
}
|
||||
|
||||
impl Display for ServerProviderType {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ServerProviderType::Local => write!(f, "Local"),
|
||||
ServerProviderType::AppFlowyCloud => write!(f, "AppFlowyCloud"),
|
||||
ServerProviderType::Supabase => write!(f, "Supabase"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The [AppFlowyServerProvider] provides list of [AppFlowyServer] base on the [AuthType]. Using
|
||||
/// the auth type, the [AppFlowyServerProvider] will create a new [AppFlowyServer] if it doesn't
|
||||
/// exist.
|
||||
@ -95,7 +106,7 @@ impl AppFlowyServerProvider {
|
||||
|
||||
Ok::<Arc<dyn AppFlowyServer>, FlowyError>(server)
|
||||
},
|
||||
ServerProviderType::SelfHosted => {
|
||||
ServerProviderType::AppFlowyCloud => {
|
||||
let config = self_host_server_configuration().map_err(|e| {
|
||||
FlowyError::new(
|
||||
ErrorCode::InvalidAuthConfig,
|
||||
@ -170,6 +181,10 @@ impl UserCloudServiceProvider for AppFlowyServerProvider {
|
||||
.user_service(),
|
||||
)
|
||||
}
|
||||
|
||||
fn service_name(&self) -> String {
|
||||
self.provider_type.read().to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl FolderCloudService for AppFlowyServerProvider {
|
||||
@ -336,7 +351,7 @@ impl From<AuthType> for ServerProviderType {
|
||||
fn from(auth_provider: AuthType) -> Self {
|
||||
match auth_provider {
|
||||
AuthType::Local => ServerProviderType::Local,
|
||||
AuthType::SelfHosted => ServerProviderType::SelfHosted,
|
||||
AuthType::SelfHosted => ServerProviderType::AppFlowyCloud,
|
||||
AuthType::Supabase => ServerProviderType::Supabase,
|
||||
}
|
||||
}
|
||||
|
@ -415,7 +415,7 @@ impl From<ServerProviderType> for CollabStorageType {
|
||||
fn from(server_provider: ServerProviderType) -> Self {
|
||||
match server_provider {
|
||||
ServerProviderType::Local => CollabStorageType::Local,
|
||||
ServerProviderType::SelfHosted => CollabStorageType::Local,
|
||||
ServerProviderType::AppFlowyCloud => CollabStorageType::Local,
|
||||
ServerProviderType::Supabase => CollabStorageType::Supabase,
|
||||
}
|
||||
}
|
||||
|
@ -215,8 +215,8 @@ pub enum ErrorCode {
|
||||
#[error("Postgres transaction error")]
|
||||
PgTransactionError = 71,
|
||||
|
||||
#[error("Enable supabase sync")]
|
||||
SupabaseSyncRequired = 72,
|
||||
#[error("Enable data sync")]
|
||||
DataSyncRequired = 72,
|
||||
|
||||
#[error("Conflict")]
|
||||
Conflict = 73,
|
||||
|
@ -6,6 +6,7 @@ use parking_lot::Mutex;
|
||||
|
||||
use flowy_user_deps::cloud::UserService;
|
||||
use flowy_user_deps::entities::*;
|
||||
use flowy_user_deps::DEFAULT_USER_NAME;
|
||||
use lib_infra::box_any::BoxAny;
|
||||
use lib_infra::future::FutureResult;
|
||||
|
||||
@ -28,9 +29,14 @@ impl UserService for LocalServerUserAuthServiceImpl {
|
||||
let uid = ID_GEN.lock().next_id();
|
||||
let workspace_id = uuid::Uuid::new_v4().to_string();
|
||||
let user_workspace = UserWorkspace::new(&workspace_id, uid);
|
||||
let user_name = if params.name.is_empty() {
|
||||
DEFAULT_USER_NAME()
|
||||
} else {
|
||||
params.name.clone()
|
||||
};
|
||||
Ok(SignUpResponse {
|
||||
user_id: uid,
|
||||
name: params.name,
|
||||
name: user_name,
|
||||
latest_workspace: user_workspace.clone(),
|
||||
user_workspaces: vec![user_workspace],
|
||||
is_new: true,
|
||||
|
@ -102,11 +102,14 @@ where
|
||||
_id: MsgId,
|
||||
update: Vec<u8>,
|
||||
) -> Result<(), Error> {
|
||||
let postgrest = self.0.try_get_postgrest()?;
|
||||
let workspace_id = object
|
||||
.get_workspace_id()
|
||||
.ok_or(anyhow::anyhow!("Invalid workspace id"))?;
|
||||
send_update(workspace_id, object, update, &postgrest).await
|
||||
if let Some(postgrest) = self.0.get_postgrest() {
|
||||
let workspace_id = object
|
||||
.get_workspace_id()
|
||||
.ok_or(anyhow::anyhow!("Invalid workspace id"))?;
|
||||
send_update(workspace_id, object, update, &postgrest).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_init_sync(
|
||||
|
@ -68,8 +68,8 @@ impl SupabaseServerService for SupabaseServerServiceImpl {
|
||||
.map(|server| server.postgrest.clone())
|
||||
.ok_or_else(|| {
|
||||
FlowyError::new(
|
||||
ErrorCode::SupabaseSyncRequired,
|
||||
"Supabase sync is disabled, please enable it first",
|
||||
ErrorCode::DataSyncRequired,
|
||||
"Data Sync is disabled, please enable it first",
|
||||
)
|
||||
.into()
|
||||
})
|
||||
|
@ -6,6 +6,7 @@ use uuid::Uuid;
|
||||
|
||||
use flowy_user_deps::cloud::*;
|
||||
use flowy_user_deps::entities::*;
|
||||
use flowy_user_deps::DEFAULT_USER_NAME;
|
||||
use lib_infra::box_any::BoxAny;
|
||||
use lib_infra::future::FutureResult;
|
||||
|
||||
@ -74,9 +75,15 @@ where
|
||||
.find(|user_workspace| user_workspace.id == user_profile.latest_workspace_id)
|
||||
.cloned();
|
||||
|
||||
let user_name = if user_profile.name.is_empty() {
|
||||
DEFAULT_USER_NAME()
|
||||
} else {
|
||||
user_profile.name
|
||||
};
|
||||
|
||||
Ok(SignUpResponse {
|
||||
user_id: user_profile.uid,
|
||||
name: user_profile.name,
|
||||
name: user_name,
|
||||
latest_workspace: latest_workspace.unwrap(),
|
||||
user_workspaces,
|
||||
is_new: is_new_user,
|
||||
@ -100,9 +107,10 @@ where
|
||||
.iter()
|
||||
.find(|user_workspace| user_workspace.id == user_profile.latest_workspace_id)
|
||||
.cloned();
|
||||
|
||||
Ok(SignInResponse {
|
||||
user_id: user_profile.uid,
|
||||
name: "".to_string(),
|
||||
name: DEFAULT_USER_NAME(),
|
||||
latest_workspace: latest_workspace.unwrap(),
|
||||
user_workspaces,
|
||||
email: None,
|
||||
|
@ -164,6 +164,7 @@ pub enum AuthType {
|
||||
/// It uses Supabase as the backend.
|
||||
Supabase = 2,
|
||||
}
|
||||
|
||||
impl Default for AuthType {
|
||||
fn default() -> Self {
|
||||
Self::Local
|
||||
|
@ -1,2 +1,4 @@
|
||||
pub mod cloud;
|
||||
pub mod entities;
|
||||
|
||||
pub const DEFAULT_USER_NAME: fn() -> String = || "Me".to_string();
|
||||
|
@ -6,6 +6,7 @@ use flowy_user_deps::entities::*;
|
||||
use crate::entities::parser::{UserEmail, UserIcon, UserName, UserOpenaiKey, UserPassword};
|
||||
use crate::entities::AuthTypePB;
|
||||
use crate::errors::ErrorCode;
|
||||
use crate::services::HistoricalUser;
|
||||
|
||||
#[derive(Default, ProtoBuf)]
|
||||
pub struct UserTokenPB {
|
||||
@ -205,3 +206,46 @@ pub struct RemoveWorkspaceUserPB {
|
||||
#[pb(index = 2)]
|
||||
pub workspace_id: String,
|
||||
}
|
||||
|
||||
#[derive(ProtoBuf, Default, Clone)]
|
||||
pub struct RepeatedHistoricalUserPB {
|
||||
#[pb(index = 1)]
|
||||
pub items: Vec<HistoricalUserPB>,
|
||||
}
|
||||
|
||||
#[derive(ProtoBuf, Default, Clone)]
|
||||
pub struct HistoricalUserPB {
|
||||
#[pb(index = 1)]
|
||||
pub user_id: i64,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub user_name: String,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub last_time: i64,
|
||||
|
||||
#[pb(index = 4)]
|
||||
pub auth_type: AuthTypePB,
|
||||
}
|
||||
|
||||
impl From<Vec<HistoricalUser>> for RepeatedHistoricalUserPB {
|
||||
fn from(historical_users: Vec<HistoricalUser>) -> Self {
|
||||
Self {
|
||||
items: historical_users
|
||||
.into_iter()
|
||||
.map(HistoricalUserPB::from)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HistoricalUser> for HistoricalUserPB {
|
||||
fn from(historical_user: HistoricalUser) -> Self {
|
||||
Self {
|
||||
user_id: historical_user.user_id,
|
||||
user_name: historical_user.user_name,
|
||||
last_time: historical_user.sign_in_timestamp,
|
||||
auth_type: historical_user.auth_type.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -260,3 +260,23 @@ pub async fn update_network_state_handler(
|
||||
.did_update_network(reachable);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all, err)]
|
||||
pub async fn get_historical_users_handler(
|
||||
session: AFPluginState<Weak<UserSession>>,
|
||||
) -> DataResult<RepeatedHistoricalUserPB, FlowyError> {
|
||||
let session = upgrade_session(session)?;
|
||||
let users = RepeatedHistoricalUserPB::from(session.get_historical_users());
|
||||
data_result_ok(users)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all, err)]
|
||||
pub async fn open_historical_users_handler(
|
||||
user: AFPluginData<HistoricalUserPB>,
|
||||
session: AFPluginState<Weak<UserSession>>,
|
||||
) -> Result<(), FlowyError> {
|
||||
let user = user.into_inner();
|
||||
let session = upgrade_session(session)?;
|
||||
session.open_historical_user(user.user_id)?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -47,6 +47,8 @@ pub fn init(user_session: Weak<UserSession>) -> AFPlugin {
|
||||
remove_user_from_workspace_handler,
|
||||
)
|
||||
.event(UserEvent::UpdateNetworkState, update_network_state_handler)
|
||||
.event(UserEvent::GetHistoricalUsers, get_historical_users_handler)
|
||||
.event(UserEvent::OpenHistoricalUser, open_historical_users_handler)
|
||||
}
|
||||
|
||||
pub struct SignUpContext {
|
||||
@ -85,6 +87,7 @@ pub trait UserCloudServiceProvider: Send + Sync + 'static {
|
||||
fn update_supabase_config(&self, supabase_config: &SupabaseConfiguration);
|
||||
fn set_auth_type(&self, auth_type: AuthType);
|
||||
fn get_user_service(&self) -> Result<Arc<dyn UserService>, FlowyError>;
|
||||
fn service_name(&self) -> String;
|
||||
}
|
||||
|
||||
impl<T> UserCloudServiceProvider for Arc<T>
|
||||
@ -102,6 +105,10 @@ where
|
||||
fn get_user_service(&self) -> Result<Arc<dyn UserService>, FlowyError> {
|
||||
(**self).get_user_service()
|
||||
}
|
||||
|
||||
fn service_name(&self) -> String {
|
||||
(**self).service_name()
|
||||
}
|
||||
}
|
||||
|
||||
/// Acts as a placeholder [UserStatusCallback] for the user session, but does not perform any function
|
||||
@ -208,4 +215,10 @@ pub enum UserEvent {
|
||||
|
||||
#[event(input = "NetworkStatePB")]
|
||||
UpdateNetworkState = 24,
|
||||
|
||||
#[event(output = "RepeatedHistoricalUserPB")]
|
||||
GetHistoricalUsers = 25,
|
||||
|
||||
#[event(input = "HistoricalUserPB")]
|
||||
OpenHistoricalUser = 26,
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use serde_json::Value;
|
||||
|
||||
use flowy_user_deps::entities::{SignInResponse, UserWorkspace};
|
||||
use flowy_user_deps::entities::{SignInResponse, SignUpResponse, UserWorkspace};
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct Session {
|
||||
@ -102,6 +102,15 @@ impl std::convert::From<Session> for String {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&SignUpResponse> for Session {
|
||||
fn from(value: &SignUpResponse) -> Self {
|
||||
Session {
|
||||
user_id: value.user_id,
|
||||
user_workspace: value.latest_workspace.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -1,3 +1,5 @@
|
||||
use std::convert::TryFrom;
|
||||
use std::string::ToString;
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
use appflowy_integrate::RocksCollabDB;
|
||||
@ -152,27 +154,30 @@ impl UserSession {
|
||||
params: BoxAny,
|
||||
auth_type: AuthType,
|
||||
) -> Result<UserProfile, FlowyError> {
|
||||
let resp: SignInResponse = self
|
||||
let response: SignInResponse = self
|
||||
.cloud_services
|
||||
.get_user_service()?
|
||||
.sign_in(params)
|
||||
.await?;
|
||||
|
||||
let session: Session = resp.clone().into();
|
||||
let session: Session = response.clone().into();
|
||||
let uid = session.user_id;
|
||||
self.set_session(Some(session))?;
|
||||
self.log_user(uid, self.user_dir(uid));
|
||||
self.set_current_session(Some(session))?;
|
||||
|
||||
let user_workspace = resp.latest_workspace.clone();
|
||||
self.log_user(uid, response.name.clone(), &auth_type, self.user_dir(uid));
|
||||
|
||||
let user_workspace = response.latest_workspace.clone();
|
||||
save_user_workspaces(
|
||||
self.db_pool(uid)?,
|
||||
resp
|
||||
response
|
||||
.user_workspaces
|
||||
.iter()
|
||||
.map(|user_workspace| UserWorkspaceTable::from((uid, user_workspace)))
|
||||
.flat_map(|user_workspace| UserWorkspaceTable::try_from((uid, user_workspace)).ok())
|
||||
.collect(),
|
||||
)?;
|
||||
let user_profile: UserProfile = self.save_user(uid, (resp, auth_type).into()).await?.into();
|
||||
let user_profile: UserProfile = self
|
||||
.save_user(uid, (response, auth_type).into())
|
||||
.await?
|
||||
.into();
|
||||
if let Err(e) = self
|
||||
.user_status_callback
|
||||
.read()
|
||||
@ -226,19 +231,16 @@ impl UserSession {
|
||||
is_new: response.is_new,
|
||||
local_folder: None,
|
||||
};
|
||||
let new_session = Session {
|
||||
user_id: response.user_id,
|
||||
user_workspace: response.latest_workspace.clone(),
|
||||
};
|
||||
let uid = new_session.user_id;
|
||||
self.set_session(Some(new_session.clone()))?;
|
||||
self.log_user(uid, self.user_dir(uid));
|
||||
let new_session = Session::from(&response);
|
||||
self.set_current_session(Some(new_session.clone()))?;
|
||||
let uid = response.user_id;
|
||||
self.log_user(uid, response.name.clone(), &auth_type, self.user_dir(uid));
|
||||
save_user_workspaces(
|
||||
self.db_pool(uid)?,
|
||||
response
|
||||
.user_workspaces
|
||||
.iter()
|
||||
.map(|user_workspace| UserWorkspaceTable::from((uid, user_workspace)))
|
||||
.flat_map(|user_workspace| UserWorkspaceTable::try_from((uid, user_workspace)).ok())
|
||||
.collect(),
|
||||
)?;
|
||||
let user_table = self
|
||||
@ -289,7 +291,7 @@ impl UserSession {
|
||||
pub async fn sign_out(&self) -> Result<(), FlowyError> {
|
||||
let session = self.get_session()?;
|
||||
self.database.close(session.user_id)?;
|
||||
self.set_session(None)?;
|
||||
self.set_current_session(None)?;
|
||||
|
||||
let server = self.cloud_services.get_user_service()?;
|
||||
tokio::spawn(async move {
|
||||
@ -513,7 +515,7 @@ impl UserSession {
|
||||
pool,
|
||||
new_user_workspaces
|
||||
.iter()
|
||||
.map(|user_workspace| UserWorkspaceTable::from((uid, user_workspace)))
|
||||
.flat_map(|user_workspace| UserWorkspaceTable::try_from((uid, user_workspace)).ok())
|
||||
.collect(),
|
||||
);
|
||||
|
||||
@ -561,8 +563,8 @@ impl UserSession {
|
||||
})
|
||||
}
|
||||
|
||||
fn set_session(&self, session: Option<Session>) -> Result<(), FlowyError> {
|
||||
tracing::debug!("Set user session: {:?}", session);
|
||||
fn set_current_session(&self, session: Option<Session>) -> Result<(), FlowyError> {
|
||||
tracing::debug!("Set current user: {:?}", session);
|
||||
match &session {
|
||||
None => self
|
||||
.store_preferences
|
||||
@ -577,13 +579,15 @@ impl UserSession {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn log_user(&self, uid: i64, storage_path: String) {
|
||||
fn log_user(&self, uid: i64, user_name: String, auth_type: &AuthType, storage_path: String) {
|
||||
let mut logger_users = self
|
||||
.store_preferences
|
||||
.get_object::<HistoricalUsers>(HISTORICAL_USER)
|
||||
.unwrap_or_default();
|
||||
logger_users.add_user(HistoricalUser {
|
||||
user_id: uid,
|
||||
user_name,
|
||||
auth_type: auth_type.clone(),
|
||||
sign_in_timestamp: timestamp(),
|
||||
storage_path,
|
||||
});
|
||||
@ -593,11 +597,27 @@ impl UserSession {
|
||||
}
|
||||
|
||||
pub fn get_historical_users(&self) -> Vec<HistoricalUser> {
|
||||
self
|
||||
let mut users = self
|
||||
.store_preferences
|
||||
.get_object::<HistoricalUsers>(HISTORICAL_USER)
|
||||
.unwrap_or_default()
|
||||
.users
|
||||
.users;
|
||||
users.sort_by(|a, b| b.sign_in_timestamp.cmp(&a.sign_in_timestamp));
|
||||
users
|
||||
}
|
||||
|
||||
pub fn open_historical_user(&self, uid: i64) -> FlowyResult<()> {
|
||||
let conn = self.db_connection(uid)?;
|
||||
let row = user_workspace_table::dsl::user_workspace_table
|
||||
.filter(user_workspace_table::uid.eq(uid))
|
||||
.first::<UserWorkspaceTable>(&*conn)?;
|
||||
let user_workspace = UserWorkspace::from(row);
|
||||
let session = Session {
|
||||
user_id: uid,
|
||||
user_workspace,
|
||||
};
|
||||
self.set_current_session(Some(session))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the current user session.
|
||||
@ -691,6 +711,12 @@ impl HistoricalUsers {
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct HistoricalUser {
|
||||
pub user_id: i64,
|
||||
#[serde(default = "flowy_user_deps::DEFAULT_USER_NAME")]
|
||||
pub user_name: String,
|
||||
#[serde(default = "DEFAULT_AUTH_TYPE")]
|
||||
pub auth_type: AuthType,
|
||||
pub sign_in_timestamp: i64,
|
||||
pub storage_path: String,
|
||||
}
|
||||
|
||||
const DEFAULT_AUTH_TYPE: fn() -> AuthType = || AuthType::Local;
|
||||
|
@ -1,4 +1,6 @@
|
||||
use chrono::{TimeZone, Utc};
|
||||
use flowy_error::FlowyError;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use flowy_sqlite::schema::user_workspace_table;
|
||||
use flowy_user_deps::entities::UserWorkspace;
|
||||
@ -13,15 +15,24 @@ pub struct UserWorkspaceTable {
|
||||
pub database_storage_id: String,
|
||||
}
|
||||
|
||||
impl From<(i64, &UserWorkspace)> for UserWorkspaceTable {
|
||||
fn from(value: (i64, &UserWorkspace)) -> Self {
|
||||
Self {
|
||||
impl TryFrom<(i64, &UserWorkspace)> for UserWorkspaceTable {
|
||||
type Error = FlowyError;
|
||||
|
||||
fn try_from(value: (i64, &UserWorkspace)) -> Result<Self, Self::Error> {
|
||||
if value.1.id.is_empty() {
|
||||
return Err(FlowyError::invalid_data().context("The id is empty"));
|
||||
}
|
||||
if value.1.database_storage_id.is_empty() {
|
||||
return Err(FlowyError::invalid_data().context("The database storage id is empty"));
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
id: value.1.id.clone(),
|
||||
name: value.1.name.clone(),
|
||||
uid: value.0,
|
||||
created_at: value.1.created_at.timestamp(),
|
||||
database_storage_id: value.1.database_storage_id.clone(),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,7 +45,7 @@ impl From<UserWorkspaceTable> for UserWorkspace {
|
||||
.timestamp_opt(value.created_at, 0)
|
||||
.single()
|
||||
.unwrap_or_default(),
|
||||
database_storage_id: "".to_string(),
|
||||
database_storage_id: value.database_storage_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ script = [
|
||||
"""
|
||||
cd rust-lib/
|
||||
rustup show
|
||||
echo cargo build --package=dart-ffi --target ${RUST_COMPILE_TARGET} --features "${FLUTTER_DESKTOP_FEATURES}"
|
||||
echo RUSTFLAGS="-C target-cpu=native -C link-arg=-mmacosx-version-min=11.0" cargo build --package=dart-ffi --target ${RUST_COMPILE_TARGET} --features "${FLUTTER_DESKTOP_FEATURES}"
|
||||
RUSTFLAGS="-C target-cpu=native -C link-arg=-mmacosx-version-min=11.0" cargo build --package=dart-ffi --target ${RUST_COMPILE_TARGET} --features "${FLUTTER_DESKTOP_FEATURES}"
|
||||
cd ../
|
||||
""",
|
||||
|
Loading…
Reference in New Issue
Block a user