diff --git a/frontend/appflowy_flutter/integration_test/document/document_text_direction_test.dart b/frontend/appflowy_flutter/integration_test/document/document_text_direction_test.dart index 5499143881..eff018d3ca 100644 --- a/frontend/appflowy_flutter/integration_test/document/document_text_direction_test.dart +++ b/frontend/appflowy_flutter/integration_test/document/document_text_direction_test.dart @@ -1,4 +1,4 @@ -import 'package:appflowy/workspace/application/appearance.dart'; +import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; diff --git a/frontend/appflowy_flutter/integration_test/util/settings.dart b/frontend/appflowy_flutter/integration_test/util/settings.dart index 1fd885de0a..68bc4d5d6d 100644 --- a/frontend/appflowy_flutter/integration_test/util/settings.dart +++ b/frontend/appflowy_flutter/integration_test/util/settings.dart @@ -1,5 +1,5 @@ import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/workspace/application/appearance.dart'; +import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart'; import 'package:appflowy/workspace/application/settings/prelude.dart'; import 'package:appflowy/workspace/presentation/settings/settings_dialog.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/settings_menu_element.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart index ecf17cd59a..20e86108bd 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart @@ -7,7 +7,7 @@ import 'package:appflowy/plugins/inline_actions/handlers/inline_page_reference.d import 'package:appflowy/plugins/inline_actions/handlers/reminder_reference.dart'; import 'package:appflowy/plugins/inline_actions/inline_actions_command.dart'; import 'package:appflowy/plugins/inline_actions/inline_actions_service.dart'; -import 'package:appflowy/workspace/application/appearance.dart'; +import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart'; import 'package:appflowy/workspace/application/settings/shortcuts/settings_shortcuts_service.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/emoji_picker/emoji_picker.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/block_action_option_button.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/block_action_option_button.dart index 265e3c6310..8a262e671c 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/block_action_option_button.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/block_action_option_button.dart @@ -2,7 +2,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_button.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/option_action.dart'; -import 'package:appflowy/workspace/application/appearance.dart'; +import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart'; import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mention_date_block.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mention_date_block.dart index afbc8f625e..2f5eda11b4 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mention_date_block.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mention_date_block.dart @@ -2,7 +2,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_block.dart'; import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart'; import 'package:appflowy/user/application/reminder/reminder_bloc.dart'; -import 'package:appflowy/workspace/application/appearance.dart'; +import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart'; import 'package:appflowy/workspace/application/settings/date_time/date_format_ext.dart'; import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/date_picker_dialog.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; diff --git a/frontend/appflowy_flutter/lib/startup/deps_resolver.dart b/frontend/appflowy_flutter/lib/startup/deps_resolver.dart index 34d138af4b..0c99885763 100644 --- a/frontend/appflowy_flutter/lib/startup/deps_resolver.dart +++ b/frontend/appflowy_flutter/lib/startup/deps_resolver.dart @@ -24,6 +24,7 @@ import 'package:appflowy/user/presentation/router.dart'; import 'package:appflowy/workspace/application/edit_panel/edit_panel_bloc.dart'; import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart'; import 'package:appflowy/workspace/application/local_notifications/notification_action_bloc.dart'; +import 'package:appflowy/workspace/application/settings/notifications/notification_settings_cubit.dart'; import 'package:appflowy/workspace/application/settings/prelude.dart'; import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart'; import 'package:appflowy/workspace/application/user/prelude.dart'; @@ -168,14 +169,22 @@ void _resolveHomeDeps(GetIt getIt) { getIt.registerLazySingleton(() => TabsBloc()); - getIt.registerSingleton(ReminderBloc()); + getIt.registerSingleton( + NotificationSettingsCubit(), + ); + + getIt.registerSingleton( + ReminderBloc(notificationSettings: getIt()), + ); } void _resolveFolderDeps(GetIt getIt) { //workspace getIt.registerFactoryParam( - (user, workspaceId) => - WorkspaceListener(user: user, workspaceId: workspaceId), + (user, workspaceId) => WorkspaceListener( + user: user, + workspaceId: workspaceId, + ), ); getIt.registerFactoryParam( diff --git a/frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart b/frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart index c4cb655a76..fd981bb109 100644 --- a/frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart +++ b/frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart @@ -1,19 +1,23 @@ -import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart'; -import 'package:appflowy/workspace/application/local_notifications/notification_service.dart'; -import 'package:appflowy/startup/tasks/prelude.dart'; -import 'package:appflowy_backend/log.dart'; -import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; -import 'package:appflowy_editor/appflowy_editor.dart' hide Log; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra/theme.dart'; -import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:appflowy/workspace/application/settings/notifications/notification_settings_cubit.dart'; + +import 'prelude.dart'; + import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:go_router/go_router.dart'; -import '../../user/application/user_settings_service.dart'; -import '../../workspace/application/appearance.dart'; -import '../startup.dart'; +import 'package:go_router/go_router.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:appflowy_editor/appflowy_editor.dart' hide Log; +import 'package:appflowy_backend/log.dart'; +import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; +import 'package:flowy_infra/theme.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; + +import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart'; +import 'package:appflowy/workspace/application/local_notifications/notification_service.dart'; +import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart'; +import 'package:appflowy/user/application/user_settings_service.dart'; +import 'package:appflowy/startup/startup.dart'; class InitAppWidgetTask extends LaunchTask { const InitAppWidgetTask(); @@ -95,8 +99,8 @@ class ApplicationWidget extends StatefulWidget { }); final Widget child; - final AppearanceSettingsPB appearanceSetting; final AppTheme appTheme; + final AppearanceSettingsPB appearanceSetting; final DateTimeSettingsPB dateTimeSettings; @override @@ -125,6 +129,9 @@ class _ApplicationWidgetState extends State { widget.appTheme, )..readLocaleWhenAppLaunch(context), ), + BlocProvider( + create: (_) => getIt(), + ), BlocProvider( create: (_) => DocumentAppearanceCubit()..fetch(), ), diff --git a/frontend/appflowy_flutter/lib/user/application/reminder/reminder_bloc.dart b/frontend/appflowy_flutter/lib/user/application/reminder/reminder_bloc.dart index 400c6e7db5..6ec98ac558 100644 --- a/frontend/appflowy_flutter/lib/user/application/reminder/reminder_bloc.dart +++ b/frontend/appflowy_flutter/lib/user/application/reminder/reminder_bloc.dart @@ -6,6 +6,7 @@ import 'package:appflowy/user/application/reminder/reminder_service.dart'; import 'package:appflowy/workspace/application/local_notifications/notification_action.dart'; import 'package:appflowy/workspace/application/local_notifications/notification_action_bloc.dart'; import 'package:appflowy/workspace/application/local_notifications/notification_service.dart'; +import 'package:appflowy/workspace/application/settings/notifications/notification_settings_cubit.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; import 'package:bloc/bloc.dart'; @@ -18,11 +19,16 @@ import 'package:freezed_annotation/freezed_annotation.dart'; part 'reminder_bloc.freezed.dart'; class ReminderBloc extends Bloc { + final NotificationSettingsCubit _notificationSettings; + late final NotificationActionBloc actionBloc; late final ReminderService reminderService; late final Timer timer; - ReminderBloc() : super(ReminderState()) { + ReminderBloc({ + required NotificationSettingsCubit notificationSettings, + }) : _notificationSettings = notificationSettings, + super(ReminderState()) { actionBloc = getIt(); reminderService = const ReminderService(); timer = _periodicCheck(); @@ -124,16 +130,18 @@ class ReminderBloc extends Bloc { ); if (scheduledAt.isBefore(now)) { - NotificationMessage( - identifier: reminder.id, - title: LocaleKeys.reminderNotification_title.tr(), - body: LocaleKeys.reminderNotification_message.tr(), - onClick: () => actionBloc.add( - NotificationActionEvent.performAction( - action: NotificationAction(objectId: reminder.objectId), + if (_notificationSettings.state.isNotificationsEnabled) { + NotificationMessage( + identifier: reminder.id, + title: LocaleKeys.reminderNotification_title.tr(), + body: LocaleKeys.reminderNotification_message.tr(), + onClick: () => actionBloc.add( + NotificationActionEvent.performAction( + action: NotificationAction(objectId: reminder.objectId), + ), ), - ), - ); + ); + } add( ReminderEvent.update( diff --git a/frontend/appflowy_flutter/lib/user/application/user_settings_service.dart b/frontend/appflowy_flutter/lib/user/application/user_settings_service.dart index 718d37b42e..ebf15da954 100644 --- a/frontend/appflowy_flutter/lib/user/application/user_settings_service.dart +++ b/frontend/appflowy_flutter/lib/user/application/user_settings_service.dart @@ -41,4 +41,20 @@ class UserSettingsBackendService { ) async { return (await UserEventSetDateTimeSettings(settings).send()).swap(); } + + Future> setNotificationSettings( + NotificationSettingsPB settings, + ) async { + return (await UserEventSetNotificationSettings(settings).send()).swap(); + } + + Future getNotificationSettings() async { + final result = await UserEventGetNotificationSettings().send(); + + return result.fold( + (NotificationSettingsPB setting) => setting, + (error) => + throw FlowySDKException(ExceptionType.AppearanceSettingsIsEmpty), + ); + } } diff --git a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/third_party_sign_in_buttons.dart b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/third_party_sign_in_buttons.dart index c2a650a5ce..51359d3fa2 100644 --- a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/third_party_sign_in_buttons.dart +++ b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/third_party_sign_in_buttons.dart @@ -6,7 +6,7 @@ import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/user/application/sign_in_bloc.dart'; import 'package:appflowy/user/presentation/presentation.dart'; import 'package:appflowy/util/platform_extension.dart'; -import 'package:appflowy/workspace/application/appearance.dart'; +import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/size.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; diff --git a/frontend/appflowy_flutter/lib/user/presentation/screens/skip_log_in_screen.dart b/frontend/appflowy_flutter/lib/user/presentation/screens/skip_log_in_screen.dart index dc50bdf937..9bcc4e5ff7 100644 --- a/frontend/appflowy_flutter/lib/user/presentation/screens/skip_log_in_screen.dart +++ b/frontend/appflowy_flutter/lib/user/presentation/screens/skip_log_in_screen.dart @@ -8,7 +8,7 @@ import 'package:appflowy/user/application/auth/auth_service.dart'; import 'package:appflowy/user/application/historical_user_bloc.dart'; import 'package:appflowy/user/presentation/router.dart'; import 'package:appflowy/user/presentation/widgets/widgets.dart'; -import 'package:appflowy/workspace/application/appearance.dart'; +import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/settings_language_view.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:easy_localization/easy_localization.dart'; diff --git a/frontend/appflowy_flutter/lib/workspace/application/home/home_setting_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/home/home_setting_bloc.dart index 3075ddc834..3277947aac 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/home/home_setting_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/home/home_setting_bloc.dart @@ -1,5 +1,5 @@ import 'package:appflowy/user/application/user_listener.dart'; -import 'package:appflowy/workspace/application/appearance.dart'; +import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart'; import 'package:appflowy/workspace/application/edit_panel/edit_context.dart'; import 'package:appflowy_backend/protobuf/flowy-folder2/workspace.pb.dart' show WorkspaceSettingPB; diff --git a/frontend/appflowy_flutter/lib/workspace/application/appearance.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/appearance/appearance_cubit.dart similarity index 90% rename from frontend/appflowy_flutter/lib/workspace/application/appearance.dart rename to frontend/appflowy_flutter/lib/workspace/application/settings/appearance/appearance_cubit.dart index 2551c0d880..b97d9e0622 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/appearance.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/settings/appearance/appearance_cubit.dart @@ -16,33 +16,40 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:google_fonts/google_fonts.dart'; -part 'appearance.freezed.dart'; +part 'appearance_cubit.freezed.dart'; const _white = Color(0xFFFFFFFF); /// [AppearanceSettingsCubit] is used to modify the appearance of AppFlowy. -/// It includes the [AppTheme], [ThemeMode], [TextStyles] and [Locale]. +/// It includes: +/// - [AppTheme] +/// - [ThemeMode] +/// - [TextStyle]'s +/// - [Locale] +/// - [UserDateFormatPB] +/// - [UserTimeFormatPB] +/// class AppearanceSettingsCubit extends Cubit { - final AppearanceSettingsPB _setting; + final AppearanceSettingsPB _appearanceSettings; final DateTimeSettingsPB _dateTimeSettings; AppearanceSettingsCubit( - AppearanceSettingsPB setting, + AppearanceSettingsPB appearanceSettings, DateTimeSettingsPB dateTimeSettings, AppTheme appTheme, - ) : _setting = setting, + ) : _appearanceSettings = appearanceSettings, _dateTimeSettings = dateTimeSettings, super( AppearanceSettingsState.initial( appTheme, - setting.themeMode, - setting.font, - setting.monospaceFont, - setting.layoutDirection, - setting.textDirection, - setting.locale, - setting.isMenuCollapsed, - setting.menuOffset, + appearanceSettings.themeMode, + appearanceSettings.font, + appearanceSettings.monospaceFont, + appearanceSettings.layoutDirection, + appearanceSettings.textDirection, + appearanceSettings.locale, + appearanceSettings.isMenuCollapsed, + appearanceSettings.menuOffset, dateTimeSettings.dateFormat, dateTimeSettings.timeFormat, dateTimeSettings.timezoneId, @@ -52,7 +59,7 @@ class AppearanceSettingsCubit extends Cubit { /// Update selected theme in the user's settings and emit an updated state /// with the AppTheme named [themeName]. Future setTheme(String themeName) async { - _setting.theme = themeName; + _appearanceSettings.theme = themeName; _saveAppearanceSettings(); emit(state.copyWith(appTheme: await AppTheme.fromName(themeName))); } @@ -63,7 +70,7 @@ class AppearanceSettingsCubit extends Cubit { /// Update the theme mode in the user's settings and emit an updated state. void setThemeMode(ThemeMode themeMode) { - _setting.themeMode = _themeModeToPB(themeMode); + _appearanceSettings.themeMode = _themeModeToPB(themeMode); _saveAppearanceSettings(); emit(state.copyWith(themeMode: themeMode)); } @@ -81,13 +88,13 @@ class AppearanceSettingsCubit extends Cubit { } void setLayoutDirection(LayoutDirection layoutDirection) { - _setting.layoutDirection = layoutDirection.toLayoutDirectionPB(); + _appearanceSettings.layoutDirection = layoutDirection.toLayoutDirectionPB(); _saveAppearanceSettings(); emit(state.copyWith(layoutDirection: layoutDirection)); } void setTextDirection(AppFlowyTextDirection? textDirection) { - _setting.textDirection = + _appearanceSettings.textDirection = textDirection?.toTextDirectionPB() ?? TextDirectionPB.FALLBACK; _saveAppearanceSettings(); emit(state.copyWith(textDirection: textDirection)); @@ -96,7 +103,7 @@ class AppearanceSettingsCubit extends Cubit { /// Update selected font in the user's settings and emit an updated state /// with the font name. void setFontFamily(String fontFamilyName) { - _setting.font = fontFamilyName; + _appearanceSettings.font = fontFamilyName; _saveAppearanceSettings(); emit(state.copyWith(font: fontFamilyName)); } @@ -118,8 +125,8 @@ class AppearanceSettingsCubit extends Cubit { }); if (state.locale != newLocale) { - _setting.locale.languageCode = newLocale.languageCode; - _setting.locale.countryCode = newLocale.countryCode ?? ""; + _appearanceSettings.locale.languageCode = newLocale.languageCode; + _appearanceSettings.locale.countryCode = newLocale.countryCode ?? ""; _saveAppearanceSettings(); emit(state.copyWith(locale: newLocale)); } @@ -127,13 +134,13 @@ class AppearanceSettingsCubit extends Cubit { // Saves the menus current visibility void saveIsMenuCollapsed(bool collapsed) { - _setting.isMenuCollapsed = collapsed; + _appearanceSettings.isMenuCollapsed = collapsed; _saveAppearanceSettings(); } // Saves the current resize offset of the menu void saveMenuOffset(double offset) { - _setting.menuOffset = offset; + _appearanceSettings.menuOffset = offset; _saveAppearanceSettings(); } @@ -146,14 +153,14 @@ class AppearanceSettingsCubit extends Cubit { } if (value == null) { - _setting.settingKeyValue.remove(key); + _appearanceSettings.settingKeyValue.remove(key); } - if (_setting.settingKeyValue[key] != value) { + if (_appearanceSettings.settingKeyValue[key] != value) { if (value == null) { - _setting.settingKeyValue.remove(key); + _appearanceSettings.settingKeyValue.remove(key); } else { - _setting.settingKeyValue[key] = value; + _appearanceSettings.settingKeyValue[key] = value; } } _saveAppearanceSettings(); @@ -164,14 +171,14 @@ class AppearanceSettingsCubit extends Cubit { Log.warn("The key should not be empty"); return null; } - return _setting.settingKeyValue[key]; + return _appearanceSettings.settingKeyValue[key]; } /// Called when the application launches. /// Uses the device locale when the application is opened for the first time. void readLocaleWhenAppLaunch(BuildContext context) { - if (_setting.resetToDefault) { - _setting.resetToDefault = false; + if (_appearanceSettings.resetToDefault) { + _appearanceSettings.resetToDefault = false; _saveAppearanceSettings(); setLocale(context, context.deviceLocale); return; @@ -204,7 +211,9 @@ class AppearanceSettingsCubit extends Cubit { } Future _saveAppearanceSettings() async { - UserSettingsBackendService().setAppearanceSetting(_setting).then((result) { + UserSettingsBackendService() + .setAppearanceSetting(_appearanceSettings) + .then((result) { result.fold( (l) => null, (error) => Log.error(error), diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/notifications/notification_settings_cubit.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/notifications/notification_settings_cubit.dart new file mode 100644 index 0000000000..75874cf432 --- /dev/null +++ b/frontend/appflowy_flutter/lib/workspace/application/settings/notifications/notification_settings_cubit.dart @@ -0,0 +1,62 @@ +import 'dart:async'; + +import 'package:appflowy/user/application/user_settings_service.dart'; +import 'package:appflowy_backend/log.dart'; +import 'package:appflowy_backend/protobuf/flowy-user/user_setting.pb.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'notification_settings_cubit.freezed.dart'; + +class NotificationSettingsCubit extends Cubit { + final Completer _initCompleter = Completer(); + + late final NotificationSettingsPB _notificationSettings; + + NotificationSettingsCubit() : super(NotificationSettingsState.initial()) { + UserSettingsBackendService() + .getNotificationSettings() + .then((notificationSettings) { + _notificationSettings = notificationSettings; + _initCompleter.complete(); + }); + } + + Future toggleNotificationsEnabled() async { + await _initCompleter.future; + + _notificationSettings.notificationsEnabled = !state.isNotificationsEnabled; + _saveNotificationSettings(); + + emit( + state.copyWith( + isNotificationsEnabled: _notificationSettings.notificationsEnabled, + ), + ); + } + + Future _saveNotificationSettings() async { + await _initCompleter.future; + + UserSettingsBackendService() + .setNotificationSettings(_notificationSettings) + .then((result) { + result.fold( + (error) => Log.error(error), + (r) => null, + ); + }); + } +} + +@freezed +class NotificationSettingsState with _$NotificationSettingsState { + const NotificationSettingsState._(); + + const factory NotificationSettingsState({ + required bool isNotificationsEnabled, + }) = _NotificationSettingsState; + + factory NotificationSettingsState.initial() => + const NotificationSettingsState(isNotificationsEnabled: true); +} diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/settings_dialog_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/settings_dialog_bloc.dart index 51597845cf..2097361465 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/settings/settings_dialog_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/settings/settings_dialog_bloc.dart @@ -13,6 +13,7 @@ enum SettingsPage { language, files, user, + notifications, syncSetting, shortcuts, } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/desktop_home_screen.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/desktop_home_screen.dart index 44b0a40989..f2433ac657 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/desktop_home_screen.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/desktop_home_screen.dart @@ -3,7 +3,7 @@ import 'package:appflowy/startup/plugin/plugin.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/user/application/auth/auth_service.dart'; import 'package:appflowy/user/application/reminder/reminder_bloc.dart'; -import 'package:appflowy/workspace/application/appearance.dart'; +import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart'; import 'package:appflowy/workspace/application/home/home_bloc.dart'; import 'package:appflowy/workspace/application/home/home_service.dart'; import 'package:appflowy/workspace/application/home/home_setting_bloc.dart'; diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/hotkeys.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/hotkeys.dart index 5f265bb335..70b9846bbd 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/hotkeys.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/hotkeys.dart @@ -1,5 +1,5 @@ import 'dart:io'; -import 'package:appflowy/workspace/application/appearance.dart'; +import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart'; import 'package:appflowy/workspace/application/home/home_setting_bloc.dart'; import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart'; import 'package:flutter/material.dart'; diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/settings_dialog.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/settings_dialog.dart index 0b441ca929..82f096347f 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/settings_dialog.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/settings_dialog.dart @@ -1,5 +1,6 @@ import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/workspace/presentation/settings/widgets/settings_notifications_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_customize_shortcuts_view.dart'; @@ -101,6 +102,8 @@ class SettingsDialog extends StatelessWidget { didLogout: didLogout, didOpenUser: didOpenUser, ); + case SettingsPage.notifications: + return const SettingsNotificationsView(); case SettingsPage.syncSetting: return SyncSettingView(userId: user.id.toString()); case SettingsPage.shortcuts: diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/brightness_setting.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/brightness_setting.dart index 4b122cd9fe..f7d5483cf5 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/brightness_setting.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/brightness_setting.dart @@ -2,7 +2,7 @@ import 'dart:io'; import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/workspace/application/appearance.dart'; +import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/color_scheme.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/color_scheme.dart index ec50454411..88b3792f5a 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/color_scheme.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/color_scheme.dart @@ -1,6 +1,6 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/workspace/application/appearance.dart'; +import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart'; import 'package:appflowy/workspace/presentation/home/toast.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance/theme_setting_entry_template.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/theme_upload/theme_upload_view.dart'; diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/date_format_setting.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/date_format_setting.dart index 89b0758314..87bf347c44 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/date_format_setting.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/date_format_setting.dart @@ -1,6 +1,6 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/workspace/application/appearance.dart'; +import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart'; import 'package:appflowy_backend/protobuf/flowy-user/date_time.pbenum.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/direction_setting.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/direction_setting.dart index d96e5e338f..134af35e2e 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/direction_setting.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/direction_setting.dart @@ -1,7 +1,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart'; -import 'package:appflowy/workspace/application/appearance.dart'; +import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/font_family_setting.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/font_family_setting.dart index 5a981f5d99..fa09806971 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/font_family_setting.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/font_family_setting.dart @@ -2,7 +2,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart'; import 'package:appflowy/util/google_font_family_extension.dart'; -import 'package:appflowy/workspace/application/appearance.dart'; +import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart'; import 'package:appflowy/workspace/application/appearance_defaults.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:collection/collection.dart'; diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/time_format_setting.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/time_format_setting.dart index 462d63e7e9..b9f710186c 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/time_format_setting.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/time_format_setting.dart @@ -1,6 +1,6 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/workspace/application/appearance.dart'; +import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart'; import 'package:appflowy_backend/protobuf/flowy-user/date_time.pbenum.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance_view.dart index 165ae9ec8f..bcc840a09b 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance_view.dart @@ -1,4 +1,4 @@ -import 'package:appflowy/workspace/application/appearance.dart'; +import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance/create_file_setting.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance/date_format_setting.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance/time_format_setting.dart'; diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_language_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_language_view.dart index b57799a398..e27e674c78 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_language_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_language_view.dart @@ -1,6 +1,6 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/workspace/application/appearance.dart'; +import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_menu.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_menu.dart index 1f890de3c1..1418a891c9 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_menu.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_menu.dart @@ -61,6 +61,13 @@ class SettingsMenu extends StatelessWidget { icon: Icons.account_box_outlined, changeSelectedPage: changeSelectedPage, ), + SettingsMenuElement( + page: SettingsPage.notifications, + selectedPage: currentPage, + label: LocaleKeys.settings_menu_notifications.tr(), + icon: Icons.notifications_outlined, + changeSelectedPage: changeSelectedPage, + ), if (showSyncSetting) const SizedBox( height: 10, diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_notifications_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_notifications_view.dart new file mode 100644 index 0000000000..3b9a40478e --- /dev/null +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_notifications_view.dart @@ -0,0 +1,45 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/workspace/application/settings/notifications/notification_settings_cubit.dart'; +import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance/theme_setting_entry_template.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class SettingsNotificationsView extends StatelessWidget { + const SettingsNotificationsView({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + ThemeSettingEntryTemplateWidget( + label: LocaleKeys + .settings_notifications_enableNotifications_label + .tr(), + hint: LocaleKeys.settings_notifications_enableNotifications_hint + .tr(), + trailing: [ + Switch( + value: state.isNotificationsEnabled, + splashRadius: 0, + activeColor: Theme.of(context).colorScheme.primary, + onChanged: (value) { + context + .read() + .toggleNotificationsEnabled(); + }, + ) + ], + ), + ], + ), + ); + }, + ); + } +} diff --git a/frontend/appflowy_flutter/test/bloc_test/app_setting_test/appearance_test.dart b/frontend/appflowy_flutter/test/bloc_test/app_setting_test/appearance_test.dart index 0e97df8c20..0b58a239c4 100644 --- a/frontend/appflowy_flutter/test/bloc_test/app_setting_test/appearance_test.dart +++ b/frontend/appflowy_flutter/test/bloc_test/app_setting_test/appearance_test.dart @@ -1,5 +1,5 @@ import 'package:appflowy/user/application/user_settings_service.dart'; -import 'package:appflowy/workspace/application/appearance.dart'; +import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart'; import 'package:bloc_test/bloc_test.dart'; import 'package:appflowy_backend/protobuf/flowy-user/user_setting.pb.dart'; import 'package:flowy_infra/theme.dart'; @@ -18,6 +18,7 @@ void main() { group('$AppearanceSettingsCubit', () { late AppearanceSettingsPB appearanceSetting; late DateTimeSettingsPB dateTimeSettings; + setUp(() async { appearanceSetting = await UserSettingsBackendService().getAppearanceSetting(); diff --git a/frontend/appflowy_flutter/test/widget_test/theme_font_family_setting_test.dart b/frontend/appflowy_flutter/test/widget_test/theme_font_family_setting_test.dart index 09f2f7b073..52b42f7445 100644 --- a/frontend/appflowy_flutter/test/widget_test/theme_font_family_setting_test.dart +++ b/frontend/appflowy_flutter/test/widget_test/theme_font_family_setting_test.dart @@ -1,5 +1,5 @@ import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart'; -import 'package:appflowy/workspace/application/appearance.dart'; +import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance/font_family_setting.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index ed8907947b..67e9e4d6ae 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -241,6 +241,7 @@ "language": "Language", "user": "User", "files": "Files", + "notifications": "Notifications", "open": "Open Settings", "logout": "Logout", "logoutPrompt": "Are you sure to logout?", @@ -256,6 +257,12 @@ "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" }, + "notifications": { + "enableNotifications": { + "label": "Enable notifications", + "hint": "Turn off to stop local notifications from appearing." + } + }, "appearance": { "resetSetting": "Reset this setting", "fontFamily": { diff --git a/frontend/rust-lib/flowy-user/src/entities/user_setting.rs b/frontend/rust-lib/flowy-user/src/entities/user_setting.rs index e961fca155..6dab6b4583 100644 --- a/frontend/rust-lib/flowy-user/src/entities/user_setting.rs +++ b/frontend/rust-lib/flowy-user/src/entities/user_setting.rs @@ -237,3 +237,17 @@ impl std::default::Default for DateTimeSettingsPB { } } } + +#[derive(ProtoBuf, Serialize, Deserialize, Debug, Clone)] +pub struct NotificationSettingsPB { + #[pb(index = 1)] + pub notifications_enabled: bool, +} + +impl std::default::Default for NotificationSettingsPB { + fn default() -> Self { + NotificationSettingsPB { + notifications_enabled: true, + } + } +} diff --git a/frontend/rust-lib/flowy-user/src/event_handler.rs b/frontend/rust-lib/flowy-user/src/event_handler.rs index 67884e2ad5..de92c4fb56 100644 --- a/frontend/rust-lib/flowy-user/src/event_handler.rs +++ b/frontend/rust-lib/flowy-user/src/event_handler.rs @@ -195,7 +195,7 @@ pub async fn get_date_time_settings( Ok(setting) => setting, Err(e) => { tracing::error!( - "Deserialize AppearanceSettings failed: {:?}, fallback to default", + "Deserialize DateTimeSettings failed: {:?}, fallback to default", e ); DateTimeSettingsPB::default() @@ -206,6 +206,42 @@ pub async fn get_date_time_settings( } } +const NOTIFICATION_SETTINGS_CACHE_KEY: &str = "notification_settings"; + +#[tracing::instrument(level = "debug", skip_all, err)] +pub async fn set_notification_settings( + store_preferences: AFPluginState>, + data: AFPluginData, +) -> Result<(), FlowyError> { + let store_preferences = upgrade_store_preferences(store_preferences)?; + let setting = data.into_inner(); + store_preferences.set_object(NOTIFICATION_SETTINGS_CACHE_KEY, setting)?; + Ok(()) +} + +#[tracing::instrument(level = "debug", skip_all, err)] +pub async fn get_notification_settings( + store_preferences: AFPluginState>, +) -> DataResult { + let store_preferences = upgrade_store_preferences(store_preferences)?; + match store_preferences.get_str(NOTIFICATION_SETTINGS_CACHE_KEY) { + None => data_result_ok(NotificationSettingsPB::default()), + Some(s) => { + let setting = match serde_json::from_str(&s) { + Ok(setting) => setting, + Err(e) => { + tracing::error!( + "Deserialize NotificationSettings failed: {:?}, fallback to default", + e + ); + NotificationSettingsPB::default() + }, + }; + data_result_ok(setting) + }, + } +} + #[tracing::instrument(level = "debug", skip_all, err)] pub async fn get_user_setting( manager: AFPluginState>, diff --git a/frontend/rust-lib/flowy-user/src/event_map.rs b/frontend/rust-lib/flowy-user/src/event_map.rs index 86f77fa2ee..22b700f300 100644 --- a/frontend/rust-lib/flowy-user/src/event_map.rs +++ b/frontend/rust-lib/flowy-user/src/event_map.rs @@ -65,6 +65,14 @@ pub fn init(user_session: Weak) -> AFPlugin { .event(UserEvent::ResetWorkspace, reset_workspace_handler) .event(UserEvent::SetDateTimeSettings, set_date_time_settings) .event(UserEvent::GetDateTimeSettings, get_date_time_settings) + .event( + UserEvent::SetNotificationSettings, + set_notification_settings, + ) + .event( + UserEvent::GetNotificationSettings, + get_notification_settings, + ) } pub struct SignUpContext { @@ -317,4 +325,10 @@ pub enum UserEvent { /// Retrieve the Date/Time formats #[event(output = "DateTimeSettingsPB")] GetDateTimeSettings = 34, + + #[event(input = "NotificationSettingsPB")] + SetNotificationSettings = 35, + + #[event(output = "NotificationSettingsPB")] + GetNotificationSettings = 36, }