mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00: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>(
|
child: BlocBuilder<AppearanceSettingsCubit, AppearanceSettingsState>(
|
||||||
builder: (context, state) => MaterialApp(
|
builder: (context, state) => MaterialApp(
|
||||||
|
key: UniqueKey(),
|
||||||
builder: overlayManagerBuilder(),
|
builder: overlayManagerBuilder(),
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
theme: state.lightTheme,
|
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({
|
Completer<Either<FlowyError, UserProfilePB>> supabaseLoginCompleter({
|
||||||
required Future<Either<FlowyError, UserProfilePB>> Function(
|
required Future<Either<FlowyError, UserProfilePB>> Function(
|
||||||
String userId,
|
String userId,
|
||||||
@ -227,16 +243,15 @@ Completer<Either<FlowyError, UserProfilePB>> supabaseLoginCompleter({
|
|||||||
|
|
||||||
subscription = auth.onAuthStateChange.listen((event) async {
|
subscription = auth.onAuthStateChange.listen((event) async {
|
||||||
final user = event.session?.user;
|
final user = event.session?.user;
|
||||||
if (event.event != AuthChangeEvent.signedIn || user == null) {
|
if (event.event == AuthChangeEvent.signedIn && user != null) {
|
||||||
completer.complete(left(AuthError.supabaseSignInWithOauthError));
|
|
||||||
} else {
|
|
||||||
final response = await onSuccess(
|
final response = await onSuccess(
|
||||||
user.id,
|
user.id,
|
||||||
user.email ?? user.newEmail ?? '',
|
user.email ?? user.newEmail ?? '',
|
||||||
);
|
);
|
||||||
|
// Only cancel the subscription if the Event is signedIn.
|
||||||
|
subscription.cancel();
|
||||||
completer.complete(response);
|
completer.complete(response);
|
||||||
}
|
}
|
||||||
subscription.cancel();
|
|
||||||
});
|
});
|
||||||
return completer;
|
return completer;
|
||||||
}
|
}
|
||||||
|
@ -70,6 +70,24 @@ class UserBackendService {
|
|||||||
return UserEventInitUser().send();
|
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() {
|
Future<Either<List<WorkspacePB>, FlowyError>> getWorkspaces() {
|
||||||
final request = WorkspaceIdPB.create();
|
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:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
|
||||||
import 'package:flutter/material.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 {
|
class AuthRouter {
|
||||||
void pushForgetPasswordScreen(BuildContext context) {}
|
void pushForgetPasswordScreen(BuildContext context) {}
|
||||||
|
|
||||||
@ -24,6 +31,7 @@ class AuthRouter {
|
|||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
PageRoutes.fade(
|
PageRoutes.fade(
|
||||||
() => SignUpScreen(router: getIt<AuthRouter>()),
|
() => SignUpScreen(router: getIt<AuthRouter>()),
|
||||||
|
const RouteSettings(name: routerNameSignUp),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -41,6 +49,7 @@ class AuthRouter {
|
|||||||
workspaceSetting,
|
workspaceSetting,
|
||||||
key: ValueKey(profile.id),
|
key: ValueKey(profile.id),
|
||||||
),
|
),
|
||||||
|
const RouteSettings(name: routerNameHome),
|
||||||
RouteDurations.slow.inMilliseconds * .001,
|
RouteDurations.slow.inMilliseconds * .001,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -71,6 +80,7 @@ class SplashRoute {
|
|||||||
await Navigator.of(context).push(
|
await Navigator.of(context).push(
|
||||||
PageRoutes.fade(
|
PageRoutes.fade(
|
||||||
() => screen,
|
() => screen,
|
||||||
|
const RouteSettings(name: routerNameWelcome),
|
||||||
RouteDurations.slow.inMilliseconds * .001,
|
RouteDurations.slow.inMilliseconds * .001,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -97,6 +107,7 @@ class SplashRoute {
|
|||||||
workspaceSetting,
|
workspaceSetting,
|
||||||
key: ValueKey(userProfile.id),
|
key: ValueKey(userProfile.id),
|
||||||
),
|
),
|
||||||
|
const RouteSettings(name: routerNameWelcome),
|
||||||
RouteDurations.slow.inMilliseconds * .001,
|
RouteDurations.slow.inMilliseconds * .001,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -107,6 +118,7 @@ class SplashRoute {
|
|||||||
context,
|
context,
|
||||||
PageRoutes.fade(
|
PageRoutes.fade(
|
||||||
() => SignInScreen(router: getIt<AuthRouter>()),
|
() => SignInScreen(router: getIt<AuthRouter>()),
|
||||||
|
const RouteSettings(name: routerNameSignIn),
|
||||||
RouteDurations.slow.inMilliseconds * .001,
|
RouteDurations.slow.inMilliseconds * .001,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -120,6 +132,7 @@ class SplashRoute {
|
|||||||
router: getIt<AuthRouter>(),
|
router: getIt<AuthRouter>(),
|
||||||
authService: getIt<AuthService>(),
|
authService: getIt<AuthService>(),
|
||||||
),
|
),
|
||||||
|
const RouteSettings(name: routerNameSkipLogIn),
|
||||||
RouteDurations.slow.inMilliseconds * .001,
|
RouteDurations.slow.inMilliseconds * .001,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -320,14 +320,16 @@ class ThirdPartySignInButton extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ThirdPartySignInButtons extends StatelessWidget {
|
class ThirdPartySignInButtons extends StatelessWidget {
|
||||||
|
final MainAxisAlignment mainAxisAlignment;
|
||||||
const ThirdPartySignInButtons({
|
const ThirdPartySignInButtons({
|
||||||
|
this.mainAxisAlignment = MainAxisAlignment.center,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: mainAxisAlignment,
|
||||||
children: [
|
children: [
|
||||||
ThirdPartySignInButton(
|
ThirdPartySignInButton(
|
||||||
icon: 'login/google-mark',
|
icon: 'login/google-mark',
|
||||||
|
@ -8,10 +8,9 @@ import 'package:protobuf/protobuf.dart';
|
|||||||
|
|
||||||
part 'setting_supabase_bloc.freezed.dart';
|
part 'setting_supabase_bloc.freezed.dart';
|
||||||
|
|
||||||
class SettingSupabaseBloc
|
class SyncSettingBloc extends Bloc<SyncSettingEvent, SyncSettingState> {
|
||||||
extends Bloc<SettingSupabaseEvent, SettingSupabaseState> {
|
SyncSettingBloc() : super(SyncSettingState.initial()) {
|
||||||
SettingSupabaseBloc() : super(SettingSupabaseState.initial()) {
|
on<SyncSettingEvent>((event, emit) async {
|
||||||
on<SettingSupabaseEvent>((event, emit) async {
|
|
||||||
await event.when(
|
await event.when(
|
||||||
initial: () async {
|
initial: () async {
|
||||||
await getSupabaseConfig();
|
await getSupabaseConfig();
|
||||||
@ -27,7 +26,7 @@ class SettingSupabaseBloc
|
|||||||
emit(state.copyWith(config: newConfig));
|
emit(state.copyWith(config: newConfig));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
didReceiveSupabseConfig: (SupabaseConfigPB config) {
|
didReceiveSyncConfig: (SupabaseConfigPB config) {
|
||||||
emit(state.copyWith(config: config));
|
emit(state.copyWith(config: config));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -43,7 +42,7 @@ class SettingSupabaseBloc
|
|||||||
result.fold(
|
result.fold(
|
||||||
(config) {
|
(config) {
|
||||||
if (!isClosed) {
|
if (!isClosed) {
|
||||||
add(SettingSupabaseEvent.didReceiveSupabseConfig(config));
|
add(SyncSettingEvent.didReceiveSyncConfig(config));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(r) => Log.error(r),
|
(r) => Log.error(r),
|
||||||
@ -52,22 +51,22 @@ class SettingSupabaseBloc
|
|||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class SettingSupabaseEvent with _$SettingSupabaseEvent {
|
class SyncSettingEvent with _$SyncSettingEvent {
|
||||||
const factory SettingSupabaseEvent.initial() = _Initial;
|
const factory SyncSettingEvent.initial() = _Initial;
|
||||||
const factory SettingSupabaseEvent.didReceiveSupabseConfig(
|
const factory SyncSettingEvent.didReceiveSyncConfig(
|
||||||
SupabaseConfigPB config,
|
SupabaseConfigPB config,
|
||||||
) = _DidReceiveSupabaseConfig;
|
) = _DidSyncSupabaseConfig;
|
||||||
const factory SettingSupabaseEvent.enableSync(bool enable) = _EnableSync;
|
const factory SyncSettingEvent.enableSync(bool enable) = _EnableSync;
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class SettingSupabaseState with _$SettingSupabaseState {
|
class SyncSettingState with _$SyncSettingState {
|
||||||
const factory SettingSupabaseState({
|
const factory SyncSettingState({
|
||||||
SupabaseConfigPB? config,
|
SupabaseConfigPB? config,
|
||||||
required Either<Unit, String> successOrFailure,
|
required Either<Unit, String> successOrFailure,
|
||||||
}) = _SettingSupabaseState;
|
}) = _SyncSettingState;
|
||||||
|
|
||||||
factory SettingSupabaseState.initial() => SettingSupabaseState(
|
factory SyncSettingState.initial() => SyncSettingState(
|
||||||
successOrFailure: left(unit),
|
successOrFailure: left(unit),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ enum SettingsPage {
|
|||||||
language,
|
language,
|
||||||
files,
|
files,
|
||||||
user,
|
user,
|
||||||
supabaseSetting,
|
syncSetting,
|
||||||
shortcuts,
|
shortcuts,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ class SettingsUserViewBloc extends Bloc<SettingsUserEvent, SettingsUserState> {
|
|||||||
initial: () async {
|
initial: () async {
|
||||||
_userListener.start(onProfileUpdated: _profileUpdated);
|
_userListener.start(onProfileUpdated: _profileUpdated);
|
||||||
await _initUser();
|
await _initUser();
|
||||||
|
_loadHistoricalUsers();
|
||||||
},
|
},
|
||||||
didReceiveUserProfile: (UserProfilePB newUserProfile) {
|
didReceiveUserProfile: (UserProfilePB newUserProfile) {
|
||||||
emit(state.copyWith(userProfile: 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));
|
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) {
|
void _profileUpdated(Either<UserProfilePB, FlowyError> userProfileOrFailed) {
|
||||||
userProfileOrFailed.fold(
|
userProfileOrFailed.fold(
|
||||||
(newUserProfile) =>
|
(newUserProfile) {
|
||||||
add(SettingsUserEvent.didReceiveUserProfile(newUserProfile)),
|
add(SettingsUserEvent.didReceiveUserProfile(newUserProfile));
|
||||||
|
_loadHistoricalUsers();
|
||||||
|
},
|
||||||
(err) => Log.error(err),
|
(err) => Log.error(err),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -86,18 +105,26 @@ class SettingsUserEvent with _$SettingsUserEvent {
|
|||||||
const factory SettingsUserEvent.didReceiveUserProfile(
|
const factory SettingsUserEvent.didReceiveUserProfile(
|
||||||
UserProfilePB newUserProfile,
|
UserProfilePB newUserProfile,
|
||||||
) = _DidReceiveUserProfile;
|
) = _DidReceiveUserProfile;
|
||||||
|
const factory SettingsUserEvent.didLoadHistoricalUsers(
|
||||||
|
List<HistoricalUserPB> historicalUsers,
|
||||||
|
) = _DidLoadHistoricalUsers;
|
||||||
|
const factory SettingsUserEvent.openHistoricalUser(
|
||||||
|
HistoricalUserPB historicalUser,
|
||||||
|
) = _OpenHistoricalUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class SettingsUserState with _$SettingsUserState {
|
class SettingsUserState with _$SettingsUserState {
|
||||||
const factory SettingsUserState({
|
const factory SettingsUserState({
|
||||||
required UserProfilePB userProfile,
|
required UserProfilePB userProfile,
|
||||||
|
required List<HistoricalUserPB> historicalUsers,
|
||||||
required Either<Unit, String> successOrFailure,
|
required Either<Unit, String> successOrFailure,
|
||||||
}) = _SettingsUserState;
|
}) = _SettingsUserState;
|
||||||
|
|
||||||
factory SettingsUserState.initial(UserProfilePB userProfile) =>
|
factory SettingsUserState.initial(UserProfilePB userProfile) =>
|
||||||
SettingsUserState(
|
SettingsUserState(
|
||||||
userProfile: userProfile,
|
userProfile: userProfile,
|
||||||
|
historicalUsers: [],
|
||||||
successOrFailure: left(unit),
|
successOrFailure: left(unit),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -105,12 +105,13 @@ class MenuUser extends StatelessWidget {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (dialogContext) {
|
||||||
return BlocProvider<DocumentAppearanceCubit>.value(
|
return BlocProvider<DocumentAppearanceCubit>.value(
|
||||||
value: BlocProvider.of<DocumentAppearanceCubit>(context),
|
value: BlocProvider.of<DocumentAppearanceCubit>(context),
|
||||||
child: SettingsDialog(
|
child: SettingsDialog(
|
||||||
userProfile,
|
userProfile,
|
||||||
didLogout: () async {
|
didLogout: () async {
|
||||||
|
Navigator.of(dialogContext).pop();
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
await FlowyRunner.run(
|
await FlowyRunner.run(
|
||||||
FlowyApp(),
|
FlowyApp(),
|
||||||
@ -118,6 +119,7 @@ class MenuUser extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
dismissDialog: () => Navigator.of(context).pop(),
|
dismissDialog: () => Navigator.of(context).pop(),
|
||||||
|
didOpenUser: () {},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -109,19 +109,30 @@ class SidebarUser extends StatelessWidget {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (dialogContext) {
|
||||||
return BlocProvider<DocumentAppearanceCubit>.value(
|
return BlocProvider<DocumentAppearanceCubit>.value(
|
||||||
value: BlocProvider.of<DocumentAppearanceCubit>(context),
|
value: BlocProvider.of<DocumentAppearanceCubit>(context),
|
||||||
child: SettingsDialog(
|
child: SettingsDialog(
|
||||||
userProfile,
|
userProfile,
|
||||||
didLogout: () async {
|
didLogout: () async {
|
||||||
Navigator.of(context).pop();
|
// Pop the dialog using the dialog context
|
||||||
|
Navigator.of(dialogContext).pop();
|
||||||
|
|
||||||
await FlowyRunner.run(
|
await FlowyRunner.run(
|
||||||
FlowyApp(),
|
FlowyApp(),
|
||||||
integrationEnv(),
|
integrationEnv(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
dismissDialog: () => Navigator.of(context).pop(),
|
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/startup/startup.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.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_appearance_view.dart';
|
||||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_customize_shortcuts_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';
|
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 {
|
class SettingsDialog extends StatelessWidget {
|
||||||
final VoidCallback dismissDialog;
|
final VoidCallback dismissDialog;
|
||||||
final VoidCallback didLogout;
|
final VoidCallback didLogout;
|
||||||
|
final VoidCallback didOpenUser;
|
||||||
final UserProfilePB user;
|
final UserProfilePB user;
|
||||||
SettingsDialog(
|
SettingsDialog(
|
||||||
this.user, {
|
this.user, {
|
||||||
required this.dismissDialog,
|
required this.dismissDialog,
|
||||||
required this.didLogout,
|
required this.didLogout,
|
||||||
|
required this.didOpenUser,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: ValueKey(user.id));
|
}) : super(key: ValueKey(user.id));
|
||||||
|
|
||||||
@ -97,9 +99,10 @@ class SettingsDialog extends StatelessWidget {
|
|||||||
user,
|
user,
|
||||||
didLogin: () => dismissDialog(),
|
didLogin: () => dismissDialog(),
|
||||||
didLogout: didLogout,
|
didLogout: didLogout,
|
||||||
|
didOpenUser: didOpenUser,
|
||||||
);
|
);
|
||||||
case SettingsPage.supabaseSetting:
|
case SettingsPage.syncSetting:
|
||||||
return const SupabaseSettingView();
|
return const SyncSettingView();
|
||||||
case SettingsPage.shortcuts:
|
case SettingsPage.shortcuts:
|
||||||
return const SettingsCustomizeShortcutsWrapper();
|
return const SettingsCustomizeShortcutsWrapper();
|
||||||
default:
|
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/entry_point.dart';
|
||||||
import 'package:appflowy/startup/launch_configuration.dart';
|
import 'package:appflowy/startup/launch_configuration.dart';
|
||||||
import 'package:appflowy/startup/startup.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-error/errors.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||||
import 'package:dartz/dartz.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:flowy_infra_ui/style_widget/snap_bar.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
@ -25,7 +28,15 @@ class SettingThirdPartyLogin extends StatelessWidget {
|
|||||||
(result) => _handleSuccessOrFail(result, context),
|
(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 !=
|
context.read<SettingsDialogBloc>().state.userProfile.authType !=
|
||||||
AuthTypePB.Local)
|
AuthTypePB.Local)
|
||||||
SettingsMenuElement(
|
SettingsMenuElement(
|
||||||
page: SettingsPage.supabaseSetting,
|
page: SettingsPage.syncSetting,
|
||||||
selectedPage: currentPage,
|
selectedPage: currentPage,
|
||||||
label: LocaleKeys.settings_menu_supabaseSetting.tr(),
|
label: LocaleKeys.settings_menu_syncSetting.tr(),
|
||||||
icon: Icons.sync,
|
icon: Icons.sync,
|
||||||
changeSelectedPage: changeSelectedPage,
|
changeSelectedPage: changeSelectedPage,
|
||||||
),
|
),
|
||||||
|
@ -16,19 +16,25 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
import 'historical_user.dart';
|
||||||
import 'setting_third_party_login.dart';
|
import 'setting_third_party_login.dart';
|
||||||
|
|
||||||
const defaultUserAvatar = '1F600';
|
const defaultUserAvatar = '1F600';
|
||||||
const _iconSize = Size(60, 60);
|
const _iconSize = Size(60, 60);
|
||||||
|
|
||||||
class SettingsUserView extends StatelessWidget {
|
class SettingsUserView extends StatelessWidget {
|
||||||
|
// Called when the user login in the setting dialog
|
||||||
final VoidCallback didLogin;
|
final VoidCallback didLogin;
|
||||||
|
// Called when the user logout in the setting dialog
|
||||||
final VoidCallback didLogout;
|
final VoidCallback didLogout;
|
||||||
|
// Called when the user open a historical user in the setting dialog
|
||||||
|
final VoidCallback didOpenUser;
|
||||||
final UserProfilePB user;
|
final UserProfilePB user;
|
||||||
SettingsUserView(
|
SettingsUserView(
|
||||||
this.user, {
|
this.user, {
|
||||||
required this.didLogin,
|
required this.didLogin,
|
||||||
required this.didLogout,
|
required this.didLogout,
|
||||||
|
required this.didOpenUser,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: ValueKey(user.id));
|
}) : super(key: ValueKey(user.id));
|
||||||
|
|
||||||
@ -47,6 +53,8 @@ class SettingsUserView extends StatelessWidget {
|
|||||||
_renderCurrentIcon(context),
|
_renderCurrentIcon(context),
|
||||||
const VSpace(20),
|
const VSpace(20),
|
||||||
_renderCurrentOpenaiKey(context),
|
_renderCurrentOpenaiKey(context),
|
||||||
|
const VSpace(20),
|
||||||
|
_renderHistoricalUser(context),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
_renderLoginOrLogoutButton(context, state),
|
_renderLoginOrLogoutButton(context, state),
|
||||||
const VSpace(20),
|
const VSpace(20),
|
||||||
@ -56,23 +64,27 @@ 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(
|
Widget _renderLoginOrLogoutButton(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
SettingsUserState state,
|
SettingsUserState state,
|
||||||
) {
|
) {
|
||||||
if (!isSupabaseEnabled) {
|
if (isSupabaseEnabled) {
|
||||||
return _renderLogoutButton(context);
|
// If the user is logged in locally, render a third-party login button.
|
||||||
}
|
|
||||||
|
|
||||||
if (state.userProfile.authType == AuthTypePB.Local) {
|
if (state.userProfile.authType == AuthTypePB.Local) {
|
||||||
return SettingThirdPartyLogin(
|
return SettingThirdPartyLogin(
|
||||||
didLogin: didLogin,
|
didLogin: didLogin,
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
return _renderLogoutButton(context);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return _renderLogoutButton(context);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _renderUserNameInput(BuildContext context) {
|
Widget _renderUserNameInput(BuildContext context) {
|
||||||
final String name =
|
final String name =
|
||||||
context.read<SettingsUserViewBloc>().state.userProfile.name;
|
context.read<SettingsUserViewBloc>().state.userProfile.name;
|
||||||
@ -111,6 +123,16 @@ class SettingsUserView extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _renderHistoricalUser(BuildContext context) {
|
||||||
|
return BlocBuilder<SettingsUserViewBloc, SettingsUserState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return HistoricalUserList(
|
||||||
|
didOpenUser: didOpenUser,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@visibleForTesting
|
@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 kDefaultEaseFwd = Curves.easeOut;
|
||||||
static const Curve kDefaultEaseReverse = 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]) {
|
[double duration = kDefaultDuration]) {
|
||||||
return PageRouteBuilder<T>(
|
return PageRouteBuilder<T>(
|
||||||
|
settings: settings,
|
||||||
transitionDuration: Duration(milliseconds: (duration * 1000).round()),
|
transitionDuration: Duration(milliseconds: (duration * 1000).round()),
|
||||||
pageBuilder: (context, animation, secondaryAnimation) => pageBuilder(),
|
pageBuilder: (context, animation, secondaryAnimation) => pageBuilder(),
|
||||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
||||||
|
@ -26,7 +26,8 @@
|
|||||||
"alreadyHaveAnAccount": "Already have an account?",
|
"alreadyHaveAnAccount": "Already have an account?",
|
||||||
"emailHint": "Email",
|
"emailHint": "Email",
|
||||||
"passwordHint": "Password",
|
"passwordHint": "Password",
|
||||||
"repeatPasswordHint": "Repeat password"
|
"repeatPasswordHint": "Repeat password",
|
||||||
|
"signUpWith": "Sign up with:"
|
||||||
},
|
},
|
||||||
"signIn": {
|
"signIn": {
|
||||||
"loginTitle": "Login to @:appName",
|
"loginTitle": "Login to @:appName",
|
||||||
@ -38,7 +39,8 @@
|
|||||||
"passwordHint": "Password",
|
"passwordHint": "Password",
|
||||||
"dontHaveAnAccount": "Don't have an account?",
|
"dontHaveAnAccount": "Don't have an account?",
|
||||||
"repeatPasswordEmptyError": "Repeat password can't be empty",
|
"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": {
|
"workspace": {
|
||||||
"create": "Create workspace",
|
"create": "Create workspace",
|
||||||
@ -223,7 +225,11 @@
|
|||||||
"open": "Open Settings",
|
"open": "Open Settings",
|
||||||
"logout": "Logout",
|
"logout": "Logout",
|
||||||
"logoutPrompt": "Are you sure to 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": {
|
"appearance": {
|
||||||
"fontFamily": {
|
"fontFamily": {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
|
|
||||||
use appflowy_integrate::collab_builder::{CollabStorageProvider, CollabStorageType};
|
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.
|
/// Offline mode, no user authentication and the data is stored locally.
|
||||||
Local = 0,
|
Local = 0,
|
||||||
/// Self-hosted server provider.
|
/// 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.
|
/// progress.
|
||||||
SelfHosted = 1,
|
AppFlowyCloud = 1,
|
||||||
/// Supabase server provider.
|
/// Supabase server provider.
|
||||||
/// It uses supabase's postgresql database to store data and user authentication.
|
/// It uses supabase's postgresql database to store data and user authentication.
|
||||||
Supabase = 2,
|
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 [AppFlowyServerProvider] provides list of [AppFlowyServer] base on the [AuthType]. Using
|
||||||
/// the auth type, the [AppFlowyServerProvider] will create a new [AppFlowyServer] if it doesn't
|
/// the auth type, the [AppFlowyServerProvider] will create a new [AppFlowyServer] if it doesn't
|
||||||
/// exist.
|
/// exist.
|
||||||
@ -95,7 +106,7 @@ impl AppFlowyServerProvider {
|
|||||||
|
|
||||||
Ok::<Arc<dyn AppFlowyServer>, FlowyError>(server)
|
Ok::<Arc<dyn AppFlowyServer>, FlowyError>(server)
|
||||||
},
|
},
|
||||||
ServerProviderType::SelfHosted => {
|
ServerProviderType::AppFlowyCloud => {
|
||||||
let config = self_host_server_configuration().map_err(|e| {
|
let config = self_host_server_configuration().map_err(|e| {
|
||||||
FlowyError::new(
|
FlowyError::new(
|
||||||
ErrorCode::InvalidAuthConfig,
|
ErrorCode::InvalidAuthConfig,
|
||||||
@ -170,6 +181,10 @@ impl UserCloudServiceProvider for AppFlowyServerProvider {
|
|||||||
.user_service(),
|
.user_service(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn service_name(&self) -> String {
|
||||||
|
self.provider_type.read().to_string()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FolderCloudService for AppFlowyServerProvider {
|
impl FolderCloudService for AppFlowyServerProvider {
|
||||||
@ -336,7 +351,7 @@ impl From<AuthType> for ServerProviderType {
|
|||||||
fn from(auth_provider: AuthType) -> Self {
|
fn from(auth_provider: AuthType) -> Self {
|
||||||
match auth_provider {
|
match auth_provider {
|
||||||
AuthType::Local => ServerProviderType::Local,
|
AuthType::Local => ServerProviderType::Local,
|
||||||
AuthType::SelfHosted => ServerProviderType::SelfHosted,
|
AuthType::SelfHosted => ServerProviderType::AppFlowyCloud,
|
||||||
AuthType::Supabase => ServerProviderType::Supabase,
|
AuthType::Supabase => ServerProviderType::Supabase,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -415,7 +415,7 @@ impl From<ServerProviderType> for CollabStorageType {
|
|||||||
fn from(server_provider: ServerProviderType) -> Self {
|
fn from(server_provider: ServerProviderType) -> Self {
|
||||||
match server_provider {
|
match server_provider {
|
||||||
ServerProviderType::Local => CollabStorageType::Local,
|
ServerProviderType::Local => CollabStorageType::Local,
|
||||||
ServerProviderType::SelfHosted => CollabStorageType::Local,
|
ServerProviderType::AppFlowyCloud => CollabStorageType::Local,
|
||||||
ServerProviderType::Supabase => CollabStorageType::Supabase,
|
ServerProviderType::Supabase => CollabStorageType::Supabase,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -215,8 +215,8 @@ pub enum ErrorCode {
|
|||||||
#[error("Postgres transaction error")]
|
#[error("Postgres transaction error")]
|
||||||
PgTransactionError = 71,
|
PgTransactionError = 71,
|
||||||
|
|
||||||
#[error("Enable supabase sync")]
|
#[error("Enable data sync")]
|
||||||
SupabaseSyncRequired = 72,
|
DataSyncRequired = 72,
|
||||||
|
|
||||||
#[error("Conflict")]
|
#[error("Conflict")]
|
||||||
Conflict = 73,
|
Conflict = 73,
|
||||||
|
@ -6,6 +6,7 @@ use parking_lot::Mutex;
|
|||||||
|
|
||||||
use flowy_user_deps::cloud::UserService;
|
use flowy_user_deps::cloud::UserService;
|
||||||
use flowy_user_deps::entities::*;
|
use flowy_user_deps::entities::*;
|
||||||
|
use flowy_user_deps::DEFAULT_USER_NAME;
|
||||||
use lib_infra::box_any::BoxAny;
|
use lib_infra::box_any::BoxAny;
|
||||||
use lib_infra::future::FutureResult;
|
use lib_infra::future::FutureResult;
|
||||||
|
|
||||||
@ -28,9 +29,14 @@ impl UserService for LocalServerUserAuthServiceImpl {
|
|||||||
let uid = ID_GEN.lock().next_id();
|
let uid = ID_GEN.lock().next_id();
|
||||||
let workspace_id = uuid::Uuid::new_v4().to_string();
|
let workspace_id = uuid::Uuid::new_v4().to_string();
|
||||||
let user_workspace = UserWorkspace::new(&workspace_id, uid);
|
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 {
|
Ok(SignUpResponse {
|
||||||
user_id: uid,
|
user_id: uid,
|
||||||
name: params.name,
|
name: user_name,
|
||||||
latest_workspace: user_workspace.clone(),
|
latest_workspace: user_workspace.clone(),
|
||||||
user_workspaces: vec![user_workspace],
|
user_workspaces: vec![user_workspace],
|
||||||
is_new: true,
|
is_new: true,
|
||||||
|
@ -102,11 +102,14 @@ where
|
|||||||
_id: MsgId,
|
_id: MsgId,
|
||||||
update: Vec<u8>,
|
update: Vec<u8>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let postgrest = self.0.try_get_postgrest()?;
|
if let Some(postgrest) = self.0.get_postgrest() {
|
||||||
let workspace_id = object
|
let workspace_id = object
|
||||||
.get_workspace_id()
|
.get_workspace_id()
|
||||||
.ok_or(anyhow::anyhow!("Invalid workspace id"))?;
|
.ok_or(anyhow::anyhow!("Invalid workspace id"))?;
|
||||||
send_update(workspace_id, object, update, &postgrest).await
|
send_update(workspace_id, object, update, &postgrest).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send_init_sync(
|
async fn send_init_sync(
|
||||||
|
@ -68,8 +68,8 @@ impl SupabaseServerService for SupabaseServerServiceImpl {
|
|||||||
.map(|server| server.postgrest.clone())
|
.map(|server| server.postgrest.clone())
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
FlowyError::new(
|
FlowyError::new(
|
||||||
ErrorCode::SupabaseSyncRequired,
|
ErrorCode::DataSyncRequired,
|
||||||
"Supabase sync is disabled, please enable it first",
|
"Data Sync is disabled, please enable it first",
|
||||||
)
|
)
|
||||||
.into()
|
.into()
|
||||||
})
|
})
|
||||||
|
@ -6,6 +6,7 @@ use uuid::Uuid;
|
|||||||
|
|
||||||
use flowy_user_deps::cloud::*;
|
use flowy_user_deps::cloud::*;
|
||||||
use flowy_user_deps::entities::*;
|
use flowy_user_deps::entities::*;
|
||||||
|
use flowy_user_deps::DEFAULT_USER_NAME;
|
||||||
use lib_infra::box_any::BoxAny;
|
use lib_infra::box_any::BoxAny;
|
||||||
use lib_infra::future::FutureResult;
|
use lib_infra::future::FutureResult;
|
||||||
|
|
||||||
@ -74,9 +75,15 @@ where
|
|||||||
.find(|user_workspace| user_workspace.id == user_profile.latest_workspace_id)
|
.find(|user_workspace| user_workspace.id == user_profile.latest_workspace_id)
|
||||||
.cloned();
|
.cloned();
|
||||||
|
|
||||||
|
let user_name = if user_profile.name.is_empty() {
|
||||||
|
DEFAULT_USER_NAME()
|
||||||
|
} else {
|
||||||
|
user_profile.name
|
||||||
|
};
|
||||||
|
|
||||||
Ok(SignUpResponse {
|
Ok(SignUpResponse {
|
||||||
user_id: user_profile.uid,
|
user_id: user_profile.uid,
|
||||||
name: user_profile.name,
|
name: user_name,
|
||||||
latest_workspace: latest_workspace.unwrap(),
|
latest_workspace: latest_workspace.unwrap(),
|
||||||
user_workspaces,
|
user_workspaces,
|
||||||
is_new: is_new_user,
|
is_new: is_new_user,
|
||||||
@ -100,9 +107,10 @@ where
|
|||||||
.iter()
|
.iter()
|
||||||
.find(|user_workspace| user_workspace.id == user_profile.latest_workspace_id)
|
.find(|user_workspace| user_workspace.id == user_profile.latest_workspace_id)
|
||||||
.cloned();
|
.cloned();
|
||||||
|
|
||||||
Ok(SignInResponse {
|
Ok(SignInResponse {
|
||||||
user_id: user_profile.uid,
|
user_id: user_profile.uid,
|
||||||
name: "".to_string(),
|
name: DEFAULT_USER_NAME(),
|
||||||
latest_workspace: latest_workspace.unwrap(),
|
latest_workspace: latest_workspace.unwrap(),
|
||||||
user_workspaces,
|
user_workspaces,
|
||||||
email: None,
|
email: None,
|
||||||
|
@ -164,6 +164,7 @@ pub enum AuthType {
|
|||||||
/// It uses Supabase as the backend.
|
/// It uses Supabase as the backend.
|
||||||
Supabase = 2,
|
Supabase = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for AuthType {
|
impl Default for AuthType {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::Local
|
Self::Local
|
||||||
|
@ -1,2 +1,4 @@
|
|||||||
pub mod cloud;
|
pub mod cloud;
|
||||||
pub mod entities;
|
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::parser::{UserEmail, UserIcon, UserName, UserOpenaiKey, UserPassword};
|
||||||
use crate::entities::AuthTypePB;
|
use crate::entities::AuthTypePB;
|
||||||
use crate::errors::ErrorCode;
|
use crate::errors::ErrorCode;
|
||||||
|
use crate::services::HistoricalUser;
|
||||||
|
|
||||||
#[derive(Default, ProtoBuf)]
|
#[derive(Default, ProtoBuf)]
|
||||||
pub struct UserTokenPB {
|
pub struct UserTokenPB {
|
||||||
@ -205,3 +206,46 @@ pub struct RemoveWorkspaceUserPB {
|
|||||||
#[pb(index = 2)]
|
#[pb(index = 2)]
|
||||||
pub workspace_id: String,
|
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);
|
.did_update_network(reachable);
|
||||||
Ok(())
|
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,
|
remove_user_from_workspace_handler,
|
||||||
)
|
)
|
||||||
.event(UserEvent::UpdateNetworkState, update_network_state_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 {
|
pub struct SignUpContext {
|
||||||
@ -85,6 +87,7 @@ pub trait UserCloudServiceProvider: Send + Sync + 'static {
|
|||||||
fn update_supabase_config(&self, supabase_config: &SupabaseConfiguration);
|
fn update_supabase_config(&self, supabase_config: &SupabaseConfiguration);
|
||||||
fn set_auth_type(&self, auth_type: AuthType);
|
fn set_auth_type(&self, auth_type: AuthType);
|
||||||
fn get_user_service(&self) -> Result<Arc<dyn UserService>, FlowyError>;
|
fn get_user_service(&self) -> Result<Arc<dyn UserService>, FlowyError>;
|
||||||
|
fn service_name(&self) -> String;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> UserCloudServiceProvider for Arc<T>
|
impl<T> UserCloudServiceProvider for Arc<T>
|
||||||
@ -102,6 +105,10 @@ where
|
|||||||
fn get_user_service(&self) -> Result<Arc<dyn UserService>, FlowyError> {
|
fn get_user_service(&self) -> Result<Arc<dyn UserService>, FlowyError> {
|
||||||
(**self).get_user_service()
|
(**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
|
/// 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")]
|
#[event(input = "NetworkStatePB")]
|
||||||
UpdateNetworkState = 24,
|
UpdateNetworkState = 24,
|
||||||
|
|
||||||
|
#[event(output = "RepeatedHistoricalUserPB")]
|
||||||
|
GetHistoricalUsers = 25,
|
||||||
|
|
||||||
|
#[event(input = "HistoricalUserPB")]
|
||||||
|
OpenHistoricalUser = 26,
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ use serde::Deserialize;
|
|||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use flowy_user_deps::entities::{SignInResponse, UserWorkspace};
|
use flowy_user_deps::entities::{SignInResponse, SignUpResponse, UserWorkspace};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
pub struct Session {
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::string::ToString;
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
|
|
||||||
use appflowy_integrate::RocksCollabDB;
|
use appflowy_integrate::RocksCollabDB;
|
||||||
@ -152,27 +154,30 @@ impl UserSession {
|
|||||||
params: BoxAny,
|
params: BoxAny,
|
||||||
auth_type: AuthType,
|
auth_type: AuthType,
|
||||||
) -> Result<UserProfile, FlowyError> {
|
) -> Result<UserProfile, FlowyError> {
|
||||||
let resp: SignInResponse = self
|
let response: SignInResponse = self
|
||||||
.cloud_services
|
.cloud_services
|
||||||
.get_user_service()?
|
.get_user_service()?
|
||||||
.sign_in(params)
|
.sign_in(params)
|
||||||
.await?;
|
.await?;
|
||||||
|
let session: Session = response.clone().into();
|
||||||
let session: Session = resp.clone().into();
|
|
||||||
let uid = session.user_id;
|
let uid = session.user_id;
|
||||||
self.set_session(Some(session))?;
|
self.set_current_session(Some(session))?;
|
||||||
self.log_user(uid, self.user_dir(uid));
|
|
||||||
|
|
||||||
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(
|
save_user_workspaces(
|
||||||
self.db_pool(uid)?,
|
self.db_pool(uid)?,
|
||||||
resp
|
response
|
||||||
.user_workspaces
|
.user_workspaces
|
||||||
.iter()
|
.iter()
|
||||||
.map(|user_workspace| UserWorkspaceTable::from((uid, user_workspace)))
|
.flat_map(|user_workspace| UserWorkspaceTable::try_from((uid, user_workspace)).ok())
|
||||||
.collect(),
|
.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
|
if let Err(e) = self
|
||||||
.user_status_callback
|
.user_status_callback
|
||||||
.read()
|
.read()
|
||||||
@ -226,19 +231,16 @@ impl UserSession {
|
|||||||
is_new: response.is_new,
|
is_new: response.is_new,
|
||||||
local_folder: None,
|
local_folder: None,
|
||||||
};
|
};
|
||||||
let new_session = Session {
|
let new_session = Session::from(&response);
|
||||||
user_id: response.user_id,
|
self.set_current_session(Some(new_session.clone()))?;
|
||||||
user_workspace: response.latest_workspace.clone(),
|
let uid = response.user_id;
|
||||||
};
|
self.log_user(uid, response.name.clone(), &auth_type, self.user_dir(uid));
|
||||||
let uid = new_session.user_id;
|
|
||||||
self.set_session(Some(new_session.clone()))?;
|
|
||||||
self.log_user(uid, self.user_dir(uid));
|
|
||||||
save_user_workspaces(
|
save_user_workspaces(
|
||||||
self.db_pool(uid)?,
|
self.db_pool(uid)?,
|
||||||
response
|
response
|
||||||
.user_workspaces
|
.user_workspaces
|
||||||
.iter()
|
.iter()
|
||||||
.map(|user_workspace| UserWorkspaceTable::from((uid, user_workspace)))
|
.flat_map(|user_workspace| UserWorkspaceTable::try_from((uid, user_workspace)).ok())
|
||||||
.collect(),
|
.collect(),
|
||||||
)?;
|
)?;
|
||||||
let user_table = self
|
let user_table = self
|
||||||
@ -289,7 +291,7 @@ impl UserSession {
|
|||||||
pub async fn sign_out(&self) -> Result<(), FlowyError> {
|
pub async fn sign_out(&self) -> Result<(), FlowyError> {
|
||||||
let session = self.get_session()?;
|
let session = self.get_session()?;
|
||||||
self.database.close(session.user_id)?;
|
self.database.close(session.user_id)?;
|
||||||
self.set_session(None)?;
|
self.set_current_session(None)?;
|
||||||
|
|
||||||
let server = self.cloud_services.get_user_service()?;
|
let server = self.cloud_services.get_user_service()?;
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
@ -513,7 +515,7 @@ impl UserSession {
|
|||||||
pool,
|
pool,
|
||||||
new_user_workspaces
|
new_user_workspaces
|
||||||
.iter()
|
.iter()
|
||||||
.map(|user_workspace| UserWorkspaceTable::from((uid, user_workspace)))
|
.flat_map(|user_workspace| UserWorkspaceTable::try_from((uid, user_workspace)).ok())
|
||||||
.collect(),
|
.collect(),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -561,8 +563,8 @@ impl UserSession {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_session(&self, session: Option<Session>) -> Result<(), FlowyError> {
|
fn set_current_session(&self, session: Option<Session>) -> Result<(), FlowyError> {
|
||||||
tracing::debug!("Set user session: {:?}", session);
|
tracing::debug!("Set current user: {:?}", session);
|
||||||
match &session {
|
match &session {
|
||||||
None => self
|
None => self
|
||||||
.store_preferences
|
.store_preferences
|
||||||
@ -577,13 +579,15 @@ impl UserSession {
|
|||||||
Ok(())
|
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
|
let mut logger_users = self
|
||||||
.store_preferences
|
.store_preferences
|
||||||
.get_object::<HistoricalUsers>(HISTORICAL_USER)
|
.get_object::<HistoricalUsers>(HISTORICAL_USER)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
logger_users.add_user(HistoricalUser {
|
logger_users.add_user(HistoricalUser {
|
||||||
user_id: uid,
|
user_id: uid,
|
||||||
|
user_name,
|
||||||
|
auth_type: auth_type.clone(),
|
||||||
sign_in_timestamp: timestamp(),
|
sign_in_timestamp: timestamp(),
|
||||||
storage_path,
|
storage_path,
|
||||||
});
|
});
|
||||||
@ -593,11 +597,27 @@ impl UserSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_historical_users(&self) -> Vec<HistoricalUser> {
|
pub fn get_historical_users(&self) -> Vec<HistoricalUser> {
|
||||||
self
|
let mut users = self
|
||||||
.store_preferences
|
.store_preferences
|
||||||
.get_object::<HistoricalUsers>(HISTORICAL_USER)
|
.get_object::<HistoricalUsers>(HISTORICAL_USER)
|
||||||
.unwrap_or_default()
|
.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.
|
/// Returns the current user session.
|
||||||
@ -691,6 +711,12 @@ impl HistoricalUsers {
|
|||||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||||
pub struct HistoricalUser {
|
pub struct HistoricalUser {
|
||||||
pub user_id: i64,
|
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 sign_in_timestamp: i64,
|
||||||
pub storage_path: String,
|
pub storage_path: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DEFAULT_AUTH_TYPE: fn() -> AuthType = || AuthType::Local;
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
use chrono::{TimeZone, Utc};
|
use chrono::{TimeZone, Utc};
|
||||||
|
use flowy_error::FlowyError;
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
use flowy_sqlite::schema::user_workspace_table;
|
use flowy_sqlite::schema::user_workspace_table;
|
||||||
use flowy_user_deps::entities::UserWorkspace;
|
use flowy_user_deps::entities::UserWorkspace;
|
||||||
@ -13,15 +15,24 @@ pub struct UserWorkspaceTable {
|
|||||||
pub database_storage_id: String,
|
pub database_storage_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<(i64, &UserWorkspace)> for UserWorkspaceTable {
|
impl TryFrom<(i64, &UserWorkspace)> for UserWorkspaceTable {
|
||||||
fn from(value: (i64, &UserWorkspace)) -> Self {
|
type Error = FlowyError;
|
||||||
Self {
|
|
||||||
|
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(),
|
id: value.1.id.clone(),
|
||||||
name: value.1.name.clone(),
|
name: value.1.name.clone(),
|
||||||
uid: value.0,
|
uid: value.0,
|
||||||
created_at: value.1.created_at.timestamp(),
|
created_at: value.1.created_at.timestamp(),
|
||||||
database_storage_id: value.1.database_storage_id.clone(),
|
database_storage_id: value.1.database_storage_id.clone(),
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,7 +45,7 @@ impl From<UserWorkspaceTable> for UserWorkspace {
|
|||||||
.timestamp_opt(value.created_at, 0)
|
.timestamp_opt(value.created_at, 0)
|
||||||
.single()
|
.single()
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
database_storage_id: "".to_string(),
|
database_storage_id: value.database_storage_id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ script = [
|
|||||||
"""
|
"""
|
||||||
cd rust-lib/
|
cd rust-lib/
|
||||||
rustup show
|
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}"
|
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 ../
|
cd ../
|
||||||
""",
|
""",
|
||||||
|
Loading…
Reference in New Issue
Block a user