refactor: login session (#3149)

This commit is contained in:
Nathan.fooo 2023-08-09 10:13:49 +08:00 committed by GitHub
parent 9330df4ce1
commit a5eaf15548
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 236 additions and 139 deletions

View File

@ -0,0 +1,64 @@
import 'package:appflowy/user/application/user_service.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-user/auth.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'historical_user_bloc.freezed.dart';
class HistoricalUserBloc
extends Bloc<HistoricalUserEvent, HistoricalUserState> {
HistoricalUserBloc() : super(HistoricalUserState.initial()) {
on<HistoricalUserEvent>((event, emit) async {
await event.when(
initial: () async {
await _loadHistoricalUsers();
},
didLoadHistoricalUsers: (List<HistoricalUserPB> historicalUsers) {
emit(state.copyWith(historicalUsers: historicalUsers));
},
openHistoricalUser: (HistoricalUserPB historicalUser) async {
await UserBackendService.openHistoricalUser(historicalUser);
emit(state.copyWith(openedHistoricalUser: historicalUser));
},
);
});
}
Future<void> _loadHistoricalUsers() async {
final result = await UserBackendService.loadHistoricalUsers();
result.fold(
(historicalUsers) {
historicalUsers
.retainWhere((element) => element.authType == AuthTypePB.Local);
add(HistoricalUserEvent.didLoadHistoricalUsers(historicalUsers));
},
(error) => Log.error(error),
);
}
}
@freezed
class HistoricalUserEvent with _$HistoricalUserEvent {
const factory HistoricalUserEvent.initial() = _Initial;
const factory HistoricalUserEvent.didLoadHistoricalUsers(
List<HistoricalUserPB> historicalUsers,
) = _DidLoadHistoricalUsers;
const factory HistoricalUserEvent.openHistoricalUser(
HistoricalUserPB historicalUser,
) = _OpenHistoricalUser;
}
@freezed
class HistoricalUserState with _$HistoricalUserState {
const factory HistoricalUserState({
required List<HistoricalUserPB> historicalUsers,
required HistoricalUserPB? openedHistoricalUser,
}) = _HistoricalUserState;
factory HistoricalUserState.initial() => const HistoricalUserState(
historicalUsers: [],
openedHistoricalUser: null,
);
}

View File

@ -70,7 +70,7 @@ class UserBackendService {
return UserEventInitUser().send();
}
Future<Either<List<HistoricalUserPB>, FlowyError>>
static Future<Either<List<HistoricalUserPB>, FlowyError>>
loadHistoricalUsers() async {
return UserEventGetHistoricalUsers().send().then(
(result) {
@ -82,7 +82,7 @@ class UserBackendService {
);
}
Future<Either<Unit, FlowyError>> openHistoricalUser(
static Future<Either<Unit, FlowyError>> openHistoricalUser(
HistoricalUserPB user,
) async {
return UserEventOpenHistoricalUser(user).send();

View File

@ -0,0 +1,100 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/user/application/historical_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 BlocProvider(
create: (context) => HistoricalUserBloc()
..add(
const HistoricalUserEvent.initial(),
),
child: BlocBuilder<HistoricalUserBloc, HistoricalUserState>(
builder: (context, state) {
if (state.historicalUsers.isEmpty) {
return const SizedBox.shrink();
} else {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Opacity(
opacity: 0.6,
child: FlowyText.regular(
LocaleKeys.settings_menu_historicalUserListTooltip.tr(),
fontSize: 13,
maxLines: null,
),
),
const VSpace(6),
Expanded(
child: ListView.builder(
itemBuilder: (context, index) {
final user = state.historicalUsers[index];
return HistoricalUserItem(
key: ValueKey(user.userId),
user: user,
isSelected: false,
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 hh:mm a');
final date =
DateTime.fromMillisecondsSinceEpoch(user.lastTime.toInt() * 1000);
final lastTime = outputFormat.format(date);
final desc = "${user.userName}\t ${user.authType}\t$lastTime";
final child = SizedBox(
height: 30,
child: FlowyButton(
disable: isDisabled,
text: FlowyText.medium(
desc,
fontSize: 12,
),
rightIcon: icon,
onTap: () {
context
.read<HistoricalUserBloc>()
.add(HistoricalUserEvent.openHistoricalUser(user));
didOpenUser();
},
),
);
return child;
}
}

View File

@ -1,7 +1,9 @@
import 'package:appflowy/core/config/kv.dart';
import 'package:appflowy/core/config/kv_keys.dart';
import 'package:appflowy/core/frameless_window.dart';
import 'package:appflowy/startup/entry_point.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/historical_user_bloc.dart';
import 'package:appflowy/user/application/sign_in_bloc.dart';
import 'package:appflowy/user/presentation/router.dart';
import 'package:appflowy/user/presentation/widgets/background.dart';
@ -118,7 +120,18 @@ class SignInForm extends StatelessWidget {
: [
const VSpace(indicatorMinHeight * 2.0)
], // add the same space when there's no loading status.
const VSpace(20)
// ConstrainedBox(
// constraints: const BoxConstraints(maxHeight: 140),
// child: HistoricalUserList(
// didOpenUser: () async {
// await FlowyRunner.run(
// FlowyApp(),
// integrationEnv(),
// );
// },
// ),
// ),
const VSpace(20),
],
),
);
@ -183,14 +196,49 @@ class SignInAsGuestButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return RoundedTextButton(
title: LocaleKeys.signIn_loginAsGuestButtonText.tr(),
height: 48,
borderRadius: Corners.s6Border,
onPressed: () {
getIt<KeyValueStorage>().set(KVKeys.loginType, 'local');
context.read<SignInBloc>().add(const SignInEvent.signedInAsGuest());
},
return BlocProvider(
create: (context) => HistoricalUserBloc()
..add(
const HistoricalUserEvent.initial(),
),
child: BlocListener<HistoricalUserBloc, HistoricalUserState>(
listenWhen: (previous, current) =>
previous.openedHistoricalUser != current.openedHistoricalUser,
listener: (context, state) async {
await FlowyRunner.run(
FlowyApp(),
integrationEnv(),
);
},
child: BlocBuilder<HistoricalUserBloc, HistoricalUserState>(
builder: (context, state) {
if (state.historicalUsers.isEmpty) {
return RoundedTextButton(
title: LocaleKeys.signIn_loginAsGuestButtonText.tr(),
height: 48,
borderRadius: Corners.s6Border,
onPressed: () {
getIt<KeyValueStorage>().set(KVKeys.loginType, 'local');
context
.read<SignInBloc>()
.add(const SignInEvent.signedInAsGuest());
},
);
} else {
return RoundedTextButton(
title: LocaleKeys.signIn_continueAnonymousUser.tr(),
height: 48,
borderRadius: Corners.s6Border,
onPressed: () {
final bloc = context.read<HistoricalUserBloc>();
final user = bloc.state.historicalUsers.first;
bloc.add(HistoricalUserEvent.openHistoricalUser(user));
},
);
}
},
),
),
);
}
}

View File

@ -56,7 +56,7 @@ class SettingsUserViewBloc extends Bloc<SettingsUserEvent, SettingsUserState> {
emit(state.copyWith(historicalUsers: historicalUsers));
},
openHistoricalUser: (HistoricalUserPB historicalUser) async {
await _userService.openHistoricalUser(historicalUser);
await UserBackendService.openHistoricalUser(historicalUser);
},
);
});
@ -74,7 +74,7 @@ class SettingsUserViewBloc extends Bloc<SettingsUserEvent, SettingsUserState> {
}
Future<void> _loadHistoricalUsers() async {
final result = await _userService.loadHistoricalUsers();
final result = await UserBackendService.loadHistoricalUsers();
result.fold(
(historicalUsers) {
add(SettingsUserEvent.didLoadHistoricalUsers(historicalUsers));

View File

@ -1,110 +0,0 @@
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,
);
}
}
}

View File

@ -31,7 +31,11 @@ class SettingThirdPartyLogin extends StatelessWidget {
builder: (_, __) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
FlowyText.medium(LocaleKeys.signIn_signInWith.tr()),
FlowyText.medium(
LocaleKeys.signIn_signInWith.tr(),
fontSize: 16,
),
const VSpace(6),
const ThirdPartySignInButtons(
mainAxisAlignment: MainAxisAlignment.start,
),

View File

@ -16,7 +16,6 @@ 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';
@ -56,7 +55,7 @@ class SettingsUserView extends StatelessWidget {
const VSpace(20),
_renderCurrentOpenaiKey(context),
const VSpace(20),
_renderHistoricalUser(context),
// _renderHistoricalUser(context),
_renderLoginOrLogoutButton(context, state),
const VSpace(20),
],
@ -129,16 +128,6 @@ class SettingsUserView extends StatelessWidget {
),
);
}
Widget _renderHistoricalUser(BuildContext context) {
return BlocBuilder<SettingsUserViewBloc, SettingsUserState>(
builder: (context, state) {
return HistoricalUserList(
didOpenUser: didOpenUser,
);
},
);
}
}
@visibleForTesting

View File

@ -33,6 +33,7 @@
"loginTitle": "Login to @:appName",
"loginButtonText": "Login",
"loginAsGuestButtonText": "Get Started",
"continueAnonymousUser": "Continue in an anonymous session",
"buttonText": "Sign In",
"forgotPassword": "Forgot Password?",
"emailHint": "Email",
@ -227,9 +228,9 @@
"logoutPrompt": "Are you sure to logout?",
"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"
"historicalUserList": "User login history",
"historicalUserListTooltip": "This list displays your anonymous accounts. You can click on an account to view its details. Anonymous accounts are created by clicking the 'Get Started' button",
"openHistoricalUser": "Click to open the anonymous account"
},
"appearance": {
"fontFamily": {

View File

@ -616,6 +616,7 @@ impl UserSession {
user_id: uid,
user_workspace,
};
self.cloud_services.set_auth_type(AuthType::Local);
self.set_current_session(Some(session))?;
Ok(())
}