Browse Source

refactor: add setting documentation and support save key/value in setting

appflowy 2 years ago
parent
commit
908d005737

+ 4 - 4
frontend/app_flowy/lib/plugins/board/presentation/board_page.dart

@@ -104,8 +104,8 @@ class _BoardContentState extends State<BoardContent> {
 
   Widget _buildBoard(BuildContext context) {
     return ChangeNotifierProvider.value(
-      value: Provider.of<AppearanceSettingModel>(context, listen: true),
-      child: Selector<AppearanceSettingModel, AppTheme>(
+      value: Provider.of<AppearanceSetting>(context, listen: true),
+      child: Selector<AppearanceSetting, AppTheme>(
         selector: (ctx, notifier) => notifier.theme,
         builder: (ctx, theme, child) => Expanded(
           child: AppFlowyBoard(
@@ -331,8 +331,8 @@ class _ToolbarBlocAdaptor extends StatelessWidget {
         );
 
         return ChangeNotifierProvider.value(
-          value: Provider.of<AppearanceSettingModel>(context, listen: true),
-          child: Selector<AppearanceSettingModel, AppTheme>(
+          value: Provider.of<AppearanceSetting>(context, listen: true),
+          child: Selector<AppearanceSetting, AppTheme>(
             selector: (ctx, notifier) => notifier.theme,
             builder: (ctx, theme, child) {
               return BoardToolbar(toolbarContext: toolbarContext);

+ 2 - 2
frontend/app_flowy/lib/plugins/doc/document.dart

@@ -131,8 +131,8 @@ class DocumentShareButton extends StatelessWidget {
         child: BlocBuilder<DocShareBloc, DocShareState>(
           builder: (context, state) {
             return ChangeNotifierProvider.value(
-              value: Provider.of<AppearanceSettingModel>(context, listen: true),
-              child: Selector<AppearanceSettingModel, Locale>(
+              value: Provider.of<AppearanceSetting>(context, listen: true),
+              child: Selector<AppearanceSetting, Locale>(
                 selector: (ctx, notifier) => notifier.locale,
                 builder: (ctx, _, child) => ConstrainedBox(
                   constraints: const BoxConstraints.expand(

+ 1 - 1
frontend/app_flowy/lib/plugins/doc/document_page.dart

@@ -134,7 +134,7 @@ class _DocumentPageState extends State<DocumentPage> {
 
   Widget _renderToolbar(quill.QuillController controller) {
     return ChangeNotifierProvider.value(
-      value: Provider.of<AppearanceSettingModel>(context, listen: true),
+      value: Provider.of<AppearanceSetting>(context, listen: true),
       child: EditorToolbar.basic(
         controller: controller,
       ),

+ 4 - 4
frontend/app_flowy/lib/plugins/trash/menu.dart

@@ -33,8 +33,8 @@ class MenuTrash extends StatelessWidget {
   Widget _render(BuildContext context) {
     return Row(children: [
       ChangeNotifierProvider.value(
-        value: Provider.of<AppearanceSettingModel>(context, listen: true),
-        child: Selector<AppearanceSettingModel, AppTheme>(
+        value: Provider.of<AppearanceSetting>(context, listen: true),
+        child: Selector<AppearanceSetting, AppTheme>(
           selector: (ctx, notifier) => notifier.theme,
           builder: (ctx, theme, child) => SizedBox(
               width: 16,
@@ -44,8 +44,8 @@ class MenuTrash extends StatelessWidget {
       ),
       const HSpace(6),
       ChangeNotifierProvider.value(
-        value: Provider.of<AppearanceSettingModel>(context, listen: true),
-        child: Selector<AppearanceSettingModel, Locale>(
+        value: Provider.of<AppearanceSetting>(context, listen: true),
+        child: Selector<AppearanceSetting, Locale>(
           selector: (ctx, notifier) => notifier.locale,
           builder: (ctx, _, child) =>
               FlowyText.medium(LocaleKeys.trash_text.tr(), fontSize: 12),

+ 5 - 5
frontend/app_flowy/lib/startup/tasks/app_widget.dart

@@ -17,8 +17,8 @@ class InitAppWidgetTask extends LaunchTask {
   @override
   Future<void> initialize(LaunchContext context) async {
     final widget = context.getIt<EntryPoint>().create();
-    final setting = await UserSettingsService().getAppearanceSettings();
-    final settingModel = AppearanceSettingModel(setting);
+    final setting = await SettingsFFIService().getAppearanceSetting();
+    final settingModel = AppearanceSetting(setting);
     final app = ApplicationWidget(
       settingModel: settingModel,
       child: widget,
@@ -58,7 +58,7 @@ class InitAppWidgetTask extends LaunchTask {
 
 class ApplicationWidget extends StatelessWidget {
   final Widget child;
-  final AppearanceSettingModel settingModel;
+  final AppearanceSetting settingModel;
 
   const ApplicationWidget({
     Key? key,
@@ -75,10 +75,10 @@ class ApplicationWidget extends StatelessWidget {
         const minWidth = 600.0;
         setWindowMinSize(const Size(minWidth, minWidth / ratio));
         settingModel.readLocaleWhenAppLaunch(context);
-        AppTheme theme = context.select<AppearanceSettingModel, AppTheme>(
+        AppTheme theme = context.select<AppearanceSetting, AppTheme>(
           (value) => value.theme,
         );
-        Locale locale = context.select<AppearanceSettingModel, Locale>(
+        Locale locale = context.select<AppearanceSetting, Locale>(
           (value) => value.locale,
         );
 

+ 5 - 4
frontend/app_flowy/lib/user/application/user_settings_service.dart

@@ -4,8 +4,8 @@ import 'package:flowy_sdk/flowy_sdk.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-user/user_setting.pb.dart';
 
-class UserSettingsService {
-  Future<AppearanceSettingsPB> getAppearanceSettings() async {
+class SettingsFFIService {
+  Future<AppearanceSettingsPB> getAppearanceSetting() async {
     final result = await UserEventGetAppearanceSetting().send();
 
     return result.fold(
@@ -18,7 +18,8 @@ class UserSettingsService {
     );
   }
 
-  Future<Either<Unit, FlowyError>> setAppearanceSettings(AppearanceSettingsPB settings) {
-    return UserEventSetAppearanceSetting(settings).send();
+  Future<Either<Unit, FlowyError>> setAppearanceSetting(
+      AppearanceSettingsPB setting) {
+    return UserEventSetAppearanceSetting(setting).send();
   }
 }

+ 79 - 37
frontend/app_flowy/lib/workspace/application/appearance.dart

@@ -8,72 +8,114 @@ import 'package:flowy_sdk/protobuf/flowy-user/user_setting.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:easy_localization/easy_localization.dart';
 
-class AppearanceSettingModel extends ChangeNotifier with EquatableMixin {
-  AppearanceSettingsPB setting;
+/// [AppearanceSetting] is used to modify the appear setting of AppFlowy application. Including the [Locale], [AppTheme], etc.
+class AppearanceSetting extends ChangeNotifier with EquatableMixin {
+  final AppearanceSettingsPB _setting;
   AppTheme _theme;
   Locale _locale;
-  Timer? _saveOperation;
+  Timer? _debounceSaveOperation;
 
-  AppearanceSettingModel(this.setting)
-      : _theme = AppTheme.fromName(name: setting.theme),
-        _locale =
-            Locale(setting.locale.languageCode, setting.locale.countryCode);
+  AppearanceSetting(AppearanceSettingsPB setting)
+      : _setting = setting,
+        _theme = AppTheme.fromName(name: setting.theme),
+        _locale = Locale(
+          setting.locale.languageCode,
+          setting.locale.countryCode,
+        );
 
+  /// Returns the current [AppTheme]
   AppTheme get theme => _theme;
-  Locale get locale => _locale;
 
-  Future<void> save() async {
-    _saveOperation?.cancel();
-    _saveOperation = Timer(const Duration(seconds: 2), () async {
-      await UserSettingsService().setAppearanceSettings(setting);
-    });
-  }
+  /// Returns the current [Locale]
+  Locale get locale => _locale;
 
-  @override
-  List<Object> get props {
-    return [setting.hashCode];
-  }
+  /// Updates the current theme and notify the listeners the theme was changed.
+  /// Do nothing if the passed in themeType equal to the current theme type.
+  ///
+  void setTheme(ThemeType themeType) {
+    if (_theme.ty == themeType) {
+      return;
+    }
 
-  void swapTheme() {
-    final themeType =
-        (_theme.ty == ThemeType.light ? ThemeType.dark : ThemeType.light);
+    _theme = AppTheme.fromType(themeType);
+    _setting.theme = themeTypeToString(themeType);
+    _saveAppearSetting();
 
-    if (_theme.ty != themeType) {
-      _theme = AppTheme.fromType(themeType);
-      setting.theme = themeTypeToString(themeType);
-      notifyListeners();
-      save();
-    }
+    notifyListeners();
   }
 
+  /// Updates the current locale and notify the listeners the locale was changed
+  /// Fallback to [en] locale If the newLocale is not supported.
+  ///
   void setLocale(BuildContext context, Locale newLocale) {
     if (!context.supportedLocales.contains(newLocale)) {
-      Log.warn("Unsupported locale: $newLocale");
+      Log.warn("Unsupported locale: $newLocale, Fallback to locale: en");
       newLocale = const Locale('en');
-      Log.debug("Fallback to locale: $newLocale");
     }
 
     context.setLocale(newLocale);
 
     if (_locale != newLocale) {
       _locale = newLocale;
-      setting.locale.languageCode = _locale.languageCode;
-      setting.locale.countryCode = _locale.countryCode ?? "";
+      _setting.locale.languageCode = _locale.languageCode;
+      _setting.locale.countryCode = _locale.countryCode ?? "";
+      _saveAppearSetting();
+
       notifyListeners();
-      save();
     }
   }
 
-  void readLocaleWhenAppLaunch(BuildContext context) {
-    if (setting.resetAsDefault) {
-      setting.resetAsDefault = false;
-      save();
+  /// Saves key/value setting to disk.
+  /// Removes the key if the passed in value is null
+  void setKeyValue(String key, String? value) {
+    if (key.isEmpty) {
+      Log.warn("The key should not be empty");
+      return;
+    }
 
+    if (value == null) {
+      _setting.settingKeyValue.remove(key);
+    }
+
+    if (_setting.settingKeyValue[key] != value) {
+      if (value == null) {
+        _setting.settingKeyValue.remove(key);
+      } else {
+        _setting.settingKeyValue[key] = value;
+      }
+
+      _saveAppearSetting();
+      notifyListeners();
+    }
+  }
+
+  /// Called when the application launch.
+  /// Uses the device locale when open the application for the first time
+  void readLocaleWhenAppLaunch(BuildContext context) {
+    if (_setting.resetToDefault) {
+      _setting.resetToDefault = false;
+      _saveAppearSetting();
       setLocale(context, context.deviceLocale);
       return;
     }
 
-    // when opening app the first time
     setLocale(context, _locale);
   }
+
+  Future<void> _saveAppearSetting() async {
+    _debounceSaveOperation?.cancel();
+    _debounceSaveOperation = Timer(
+      const Duration(seconds: 1),
+      () {
+        SettingsFFIService().setAppearanceSetting(_setting).then((result) {
+          result.fold((l) => null, (error) => Log.error(error));
+        });
+      },
+    );
+  }
+
+  @override
+  List<Object> get props {
+    return [_setting.hashCode];
+  }
 }

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

@@ -89,8 +89,7 @@ class _MenuAppState extends State<MenuApp> {
                 hasIcon: false,
               ),
               header: ChangeNotifierProvider.value(
-                value:
-                    Provider.of<AppearanceSettingModel>(context, listen: true),
+                value: Provider.of<AppearanceSetting>(context, listen: true),
                 child: MenuAppHeader(widget.app),
               ),
               expanded: ViewSection(appViewData: viewDataContext),

+ 1 - 2
frontend/app_flowy/lib/workspace/presentation/settings/settings_dialog.dart

@@ -33,8 +33,7 @@ class SettingsDialog extends StatelessWidget {
           ..add(const SettingsDialogEvent.initial()),
         child: BlocBuilder<SettingsDialogBloc, SettingsDialogState>(
             builder: (context, state) => ChangeNotifierProvider.value(
-                  value: Provider.of<AppearanceSettingModel>(context,
-                      listen: true),
+                  value: Provider.of<AppearanceSetting>(context, listen: true),
                   child: FlowyDialog(
                     title: Text(
                       LocaleKeys.settings_title.tr(),

+ 11 - 4
frontend/app_flowy/lib/workspace/presentation/settings/widgets/settings_appearance_view.dart

@@ -13,7 +13,7 @@ class SettingsAppearanceView extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    final theme = context.watch<AppTheme>();
+    final theme = context.read<AppTheme>();
 
     return SingleChildScrollView(
       child: Column(
@@ -30,9 +30,7 @@ class SettingsAppearanceView extends StatelessWidget {
               ),
               Toggle(
                 value: theme.isDark,
-                onChanged: (val) {
-                  context.read<AppearanceSettingModel>().swapTheme();
-                },
+                onChanged: (_) => setTheme(context),
                 style: ToggleStyle.big(theme),
               ),
               Text(
@@ -48,4 +46,13 @@ class SettingsAppearanceView extends StatelessWidget {
       ),
     );
   }
+
+  void setTheme(BuildContext context) {
+    final theme = context.read<AppTheme>();
+    if (theme.isDark) {
+      context.read<AppearanceSetting>().setTheme(ThemeType.light);
+    } else {
+      context.read<AppearanceSetting>().setTheme(ThemeType.dark);
+    }
+  }
 }

+ 5 - 4
frontend/app_flowy/lib/workspace/presentation/settings/widgets/settings_language_view.dart

@@ -13,7 +13,7 @@ class SettingsLanguageView extends StatelessWidget {
   Widget build(BuildContext context) {
     context.watch<AppTheme>();
     return ChangeNotifierProvider.value(
-      value: Provider.of<AppearanceSettingModel>(context, listen: true),
+      value: Provider.of<AppearanceSetting>(context, listen: true),
       child: SingleChildScrollView(
         child: Column(
           crossAxisAlignment: CrossAxisAlignment.start,
@@ -43,7 +43,8 @@ class LanguageSelectorDropdown extends StatefulWidget {
   }) : super(key: key);
 
   @override
-  State<LanguageSelectorDropdown> createState() => _LanguageSelectorDropdownState();
+  State<LanguageSelectorDropdown> createState() =>
+      _LanguageSelectorDropdownState();
 }
 
 class _LanguageSelectorDropdownState extends State<LanguageSelectorDropdown> {
@@ -77,10 +78,10 @@ class _LanguageSelectorDropdownState extends State<LanguageSelectorDropdown> {
         ),
         child: DropdownButtonHideUnderline(
           child: DropdownButton<Locale>(
-            value: context.read<AppearanceSettingModel>().locale,
+            value: context.read<AppearanceSetting>().locale,
             onChanged: (val) {
               setState(() {
-                context.read<AppearanceSettingModel>().setLocale(context, val!);
+                context.read<AppearanceSetting>().setLocale(context, val!);
               });
             },
             icon: const Visibility(

+ 8 - 2
frontend/rust-lib/flowy-user/src/entities/user_setting.rs

@@ -1,5 +1,6 @@
 use flowy_derive::ProtoBuf;
 use serde::{Deserialize, Serialize};
+use std::collections::HashMap;
 
 #[derive(ProtoBuf, Default, Debug, Clone)]
 pub struct UserPreferencesPB {
@@ -21,7 +22,11 @@ pub struct AppearanceSettingsPB {
 
     #[pb(index = 3)]
     #[serde(default = "DEFAULT_RESET_VALUE")]
-    pub reset_as_default: bool,
+    pub reset_to_default: bool,
+
+    #[pb(index = 4)]
+    #[serde(default)]
+    pub setting_key_value: HashMap<String, String>,
 }
 
 const DEFAULT_RESET_VALUE: fn() -> bool = || APPEARANCE_RESET_AS_DEFAULT;
@@ -52,7 +57,8 @@ impl std::default::Default for AppearanceSettingsPB {
         AppearanceSettingsPB {
             theme: APPEARANCE_DEFAULT_THEME.to_owned(),
             locale: LocaleSettingsPB::default(),
-            reset_as_default: APPEARANCE_RESET_AS_DEFAULT,
+            reset_to_default: APPEARANCE_RESET_AS_DEFAULT,
+            setting_key_value: HashMap::default(),
         }
     }
 }