瀏覽代碼

Merge pull request #590 from ENsu/add_workspace_settings

Add workspace settings
Nathan.fooo 2 年之前
父節點
當前提交
b0f26f9450

+ 1 - 0
frontend/app_flowy/assets/translations/en.json

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

+ 12 - 0
frontend/app_flowy/lib/startup/deps_resolver.dart

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

+ 1 - 0
frontend/app_flowy/lib/workspace/application/settings/prelude.dart

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

+ 67 - 0
frontend/app_flowy/lib/workspace/application/settings/settings_dialog_bloc.dart

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

+ 1 - 0
frontend/app_flowy/lib/workspace/application/user/prelude.dart

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

+ 79 - 0
frontend/app_flowy/lib/workspace/application/user/settings_user_bloc.dart

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

+ 2 - 1
frontend/app_flowy/lib/workspace/presentation/home/menu/menu_user.dart

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

+ 60 - 55
frontend/app_flowy/lib/workspace/presentation/settings/settings_dialog.dart

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

+ 10 - 0
frontend/app_flowy/lib/workspace/presentation/settings/widgets/settings_menu.dart

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

+ 50 - 0
frontend/app_flowy/lib/workspace/presentation/settings/widgets/settings_user_view.dart

@@ -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));
+        });
+  }
+}