Merge pull request #590 from ENsu/add_workspace_settings

Add workspace settings
This commit is contained in:
Nathan.fooo 2022-07-22 09:36:52 +08:00 committed by GitHub
commit b0f26f9450
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 283 additions and 56 deletions

View File

@ -141,6 +141,7 @@
"menu": { "menu": {
"appearance": "Appearance", "appearance": "Appearance",
"language": "Language", "language": "Language",
"user": "User",
"open": "Open Settings" "open": "Open Settings"
}, },
"appearance": { "appearance": {

View File

@ -5,10 +5,12 @@ import 'package:app_flowy/workspace/application/app/prelude.dart';
import 'package:app_flowy/workspace/application/doc/prelude.dart'; import 'package:app_flowy/workspace/application/doc/prelude.dart';
import 'package:app_flowy/workspace/application/grid/prelude.dart'; import 'package:app_flowy/workspace/application/grid/prelude.dart';
import 'package:app_flowy/workspace/application/trash/prelude.dart'; import 'package:app_flowy/workspace/application/trash/prelude.dart';
import 'package:app_flowy/workspace/application/user/prelude.dart';
import 'package:app_flowy/workspace/application/workspace/prelude.dart'; import 'package:app_flowy/workspace/application/workspace/prelude.dart';
import 'package:app_flowy/workspace/application/edit_pannel/edit_pannel_bloc.dart'; import 'package:app_flowy/workspace/application/edit_pannel/edit_pannel_bloc.dart';
import 'package:app_flowy/workspace/application/view/prelude.dart'; import 'package:app_flowy/workspace/application/view/prelude.dart';
import 'package:app_flowy/workspace/application/menu/prelude.dart'; import 'package:app_flowy/workspace/application/menu/prelude.dart';
import 'package:app_flowy/workspace/application/settings/prelude.dart';
import 'package:app_flowy/user/application/prelude.dart'; import 'package:app_flowy/user/application/prelude.dart';
import 'package:app_flowy/user/presentation/router.dart'; import 'package:app_flowy/user/presentation/router.dart';
import 'package:app_flowy/workspace/presentation/home/home_stack.dart'; import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
@ -101,6 +103,16 @@ void _resolveFolderDeps(GetIt getIt) {
(user, _) => MenuUserBloc(user), (user, _) => MenuUserBloc(user),
); );
//Settings
getIt.registerFactoryParam<SettingsDialogBloc, UserProfilePB, void>(
(user, _) => SettingsDialogBloc(user),
);
//User
getIt.registerFactoryParam<SettingsUserViewBloc, UserProfilePB, void>(
(user, _) => SettingsUserViewBloc(user),
);
// AppPB // AppPB
getIt.registerFactoryParam<AppBloc, AppPB, void>( getIt.registerFactoryParam<AppBloc, AppPB, void>(
(app, _) => AppBloc( (app, _) => AppBloc(

View File

@ -0,0 +1 @@
export 'settings_dialog_bloc.dart';

View File

@ -0,0 +1,67 @@
import 'package:app_flowy/user/application/user_listener.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-user/user_profile.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:dartz/dartz.dart';
part 'settings_dialog_bloc.freezed.dart';
class SettingsDialogBloc extends Bloc<SettingsDialogEvent, SettingsDialogState> {
final UserListener _userListener;
final UserProfilePB userProfile;
SettingsDialogBloc(this.userProfile)
: _userListener = UserListener(userProfile: userProfile),
super(SettingsDialogState.initial(userProfile)) {
on<SettingsDialogEvent>((event, emit) async {
await event.when(
initial: () async {
_userListener.start(onProfileUpdated: _profileUpdated);
},
didReceiveUserProfile: (UserProfilePB newUserProfile) {
emit(state.copyWith(userProfile: newUserProfile));
},
setViewIndex: (int viewIndex) {
emit(state.copyWith(viewIndex: viewIndex));
},
);
});
}
@override
Future<void> close() async {
await _userListener.stop();
super.close();
}
void _profileUpdated(Either<UserProfilePB, FlowyError> userProfileOrFailed) {
userProfileOrFailed.fold(
(newUserProfile) => add(SettingsDialogEvent.didReceiveUserProfile(newUserProfile)),
(err) => Log.error(err),
);
}
}
@freezed
class SettingsDialogEvent with _$SettingsDialogEvent {
const factory SettingsDialogEvent.initial() = _Initial;
const factory SettingsDialogEvent.didReceiveUserProfile(UserProfilePB newUserProfile) = _DidReceiveUserProfile;
const factory SettingsDialogEvent.setViewIndex(int index) = _SetViewIndex;
}
@freezed
class SettingsDialogState with _$SettingsDialogState {
const factory SettingsDialogState({
required UserProfilePB userProfile,
required Either<Unit, String> successOrFailure,
required int viewIndex,
}) = _SettingsDialogState;
factory SettingsDialogState.initial(UserProfilePB userProfile) => SettingsDialogState(
userProfile: userProfile,
successOrFailure: left(unit),
viewIndex: 0,
);
}

View File

@ -0,0 +1 @@
export 'settings_user_bloc.dart';

View File

@ -0,0 +1,79 @@
import 'package:app_flowy/user/application/user_listener.dart';
import 'package:app_flowy/user/application/user_service.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-user/user_profile.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:dartz/dartz.dart';
part 'settings_user_bloc.freezed.dart';
class SettingsUserViewBloc extends Bloc<SettingsUserEvent, SettingsUserState> {
final UserService _userService;
final UserListener _userListener;
final UserProfilePB userProfile;
SettingsUserViewBloc(this.userProfile)
: _userListener = UserListener(userProfile: userProfile),
_userService = UserService(userId: userProfile.id),
super(SettingsUserState.initial(userProfile)) {
on<SettingsUserEvent>((event, emit) async {
await event.when(
initial: () async {
_userListener.start(onProfileUpdated: _profileUpdated);
await _initUser();
},
didReceiveUserProfile: (UserProfilePB newUserProfile) {
emit(state.copyWith(userProfile: newUserProfile));
},
updateUserName: (String name) {
_userService.updateUserProfile(name: name).then((result) {
result.fold(
(l) => null,
(err) => Log.error(err),
);
});
},
);
});
}
@override
Future<void> close() async {
await _userListener.stop();
super.close();
}
Future<void> _initUser() async {
final result = await _userService.initUser();
result.fold((l) => null, (error) => Log.error(error));
}
void _profileUpdated(Either<UserProfilePB, FlowyError> userProfileOrFailed) {
userProfileOrFailed.fold(
(newUserProfile) => add(SettingsUserEvent.didReceiveUserProfile(newUserProfile)),
(err) => Log.error(err),
);
}
}
@freezed
class SettingsUserEvent with _$SettingsUserEvent {
const factory SettingsUserEvent.initial() = _Initial;
const factory SettingsUserEvent.updateUserName(String name) = _UpdateUserName;
const factory SettingsUserEvent.didReceiveUserProfile(UserProfilePB newUserProfile) = _DidReceiveUserProfile;
}
@freezed
class SettingsUserState with _$SettingsUserState {
const factory SettingsUserState({
required UserProfilePB userProfile,
required Either<Unit, String> successOrFailure,
}) = _SettingsUserState;
factory SettingsUserState.initial(UserProfilePB userProfile) => SettingsUserState(
userProfile: userProfile,
successOrFailure: left(unit),
);
}

View File

@ -67,6 +67,7 @@ class MenuUser extends StatelessWidget {
Widget _renderSettingsButton(BuildContext context) { Widget _renderSettingsButton(BuildContext context) {
final theme = context.watch<AppTheme>(); final theme = context.watch<AppTheme>();
final userProfile = context.read<MenuUserBloc>().state.userProfile;
return Tooltip( return Tooltip(
message: LocaleKeys.settings_menu_open.tr(), message: LocaleKeys.settings_menu_open.tr(),
child: IconButton( child: IconButton(
@ -74,7 +75,7 @@ class MenuUser extends StatelessWidget {
showDialog( showDialog(
context: context, context: context,
builder: (context) { builder: (context) {
return const SettingsDialog(); return SettingsDialog(userProfile);
}, },
); );
}, },

View File

@ -1,70 +1,75 @@
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/generated/locale_keys.g.dart'; import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:app_flowy/workspace/application/appearance.dart'; import 'package:app_flowy/workspace/application/appearance.dart';
import 'package:app_flowy/workspace/presentation/settings/widgets/settings_appearance_view.dart'; import 'package:app_flowy/workspace/presentation/settings/widgets/settings_appearance_view.dart';
import 'package:app_flowy/workspace/presentation/settings/widgets/settings_language_view.dart'; import 'package:app_flowy/workspace/presentation/settings/widgets/settings_language_view.dart';
import 'package:app_flowy/workspace/presentation/settings/widgets/settings_user_view.dart';
import 'package:app_flowy/workspace/presentation/settings/widgets/settings_menu.dart'; import 'package:app_flowy/workspace/presentation/settings/widgets/settings_menu.dart';
import 'package:app_flowy/workspace/application/settings/settings_dialog_bloc.dart';
import 'package:flowy_sdk/protobuf/flowy-user/user_profile.pb.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class SettingsDialog extends StatefulWidget { class SettingsDialog extends StatelessWidget {
const SettingsDialog({Key? key}) : super(key: key); final UserProfilePB user;
SettingsDialog(this.user, {Key? key}) : super(key: ValueKey(user.id));
@override Widget getSettingsView(int index, UserProfilePB user) {
State<SettingsDialog> createState() => _SettingsDialogState(); final List<Widget> settingsViews = [
} const SettingsAppearanceView(),
const SettingsLanguageView(),
class _SettingsDialogState extends State<SettingsDialog> { SettingsUserView(user),
int _selectedViewIndex = 0; ];
return settingsViews[index];
final List<Widget> settingsViews = const [ }
SettingsAppearanceView(),
SettingsLanguageView(),
];
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ChangeNotifierProvider.value( return BlocProvider<SettingsDialogBloc>(
value: Provider.of<AppearanceSettingModel>(context, listen: true), create: (context) => getIt<SettingsDialogBloc>(param1: user)..add(const SettingsDialogEvent.initial()),
child: AlertDialog( child: BlocBuilder<SettingsDialogBloc, SettingsDialogState>(
shape: RoundedRectangleBorder( builder: (context, state) => ChangeNotifierProvider.value(
borderRadius: BorderRadius.circular(10), value: Provider.of<AppearanceSettingModel>(context, listen: true),
), child: AlertDialog(
title: Text( shape: RoundedRectangleBorder(
LocaleKeys.settings_title.tr(), borderRadius: BorderRadius.circular(10),
style: const TextStyle( ),
fontWeight: FontWeight.bold, title: Text(
), LocaleKeys.settings_title.tr(),
), style: const TextStyle(
content: ConstrainedBox( fontWeight: FontWeight.bold,
constraints: const BoxConstraints( ),
maxHeight: 600, ),
minWidth: 600, content: ConstrainedBox(
maxWidth: 1000, constraints: const BoxConstraints(
), maxHeight: 600,
child: Row( minWidth: 600,
crossAxisAlignment: CrossAxisAlignment.start, maxWidth: 1000,
children: [ ),
SizedBox( child: Row(
width: 200, crossAxisAlignment: CrossAxisAlignment.start,
child: SettingsMenu( children: [
changeSelectedIndex: (index) { SizedBox(
setState(() { width: 200,
_selectedViewIndex = index; child: SettingsMenu(
}); changeSelectedIndex: (index) {
}, context.read<SettingsDialogBloc>().add(SettingsDialogEvent.setViewIndex(index));
currentIndex: _selectedViewIndex, },
), currentIndex: context.read<SettingsDialogBloc>().state.viewIndex,
), ),
const VerticalDivider(), ),
const SizedBox(width: 10), const VerticalDivider(),
Expanded( const SizedBox(width: 10),
child: settingsViews[_selectedViewIndex], Expanded(
) child: getSettingsView(context.read<SettingsDialogBloc>().state.viewIndex,
], context.read<SettingsDialogBloc>().state.userProfile),
), )
), ],
), ),
); ),
),
)));
} }
} }

View File

@ -34,6 +34,16 @@ class SettingsMenu extends StatelessWidget {
icon: Icons.translate, icon: Icons.translate,
changeSelectedIndex: changeSelectedIndex, changeSelectedIndex: changeSelectedIndex,
), ),
const SizedBox(
height: 10,
),
SettingsMenuElement(
index: 2,
currentIndex: currentIndex,
label: LocaleKeys.settings_menu_user.tr(),
icon: Icons.account_box_outlined,
changeSelectedIndex: changeSelectedIndex,
),
], ],
); );
} }

View File

@ -0,0 +1,50 @@
import 'package:app_flowy/startup/startup.dart';
import 'package:flutter/material.dart';
import 'package:app_flowy/workspace/application/user/settings_user_bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flowy_sdk/protobuf/flowy-user/user_profile.pb.dart';
class SettingsUserView extends StatelessWidget {
final UserProfilePB user;
SettingsUserView(this.user, {Key? key}) : super(key: ValueKey(user.id));
@override
Widget build(BuildContext context) {
return BlocProvider<SettingsUserViewBloc>(
create: (context) => getIt<SettingsUserViewBloc>(param1: user)..add(const SettingsUserEvent.initial()),
child: BlocBuilder<SettingsUserViewBloc, SettingsUserState>(
builder: (context, state) => SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [_renderUserNameInput(context)],
),
),
),
);
}
Widget _renderUserNameInput(BuildContext context) {
String name = context.read<SettingsUserViewBloc>().state.userProfile.name;
return _UserNameInput(name);
}
}
class _UserNameInput extends StatelessWidget {
final String name;
const _UserNameInput(
this.name, {
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return TextField(
controller: TextEditingController()..text = name,
decoration: const InputDecoration(
labelText: 'Name',
),
onSubmitted: (val) {
context.read<SettingsUserViewBloc>().add(SettingsUserEvent.updateUserName(val));
});
}
}