feat: toggle notifications on/off (#3672)

This commit is contained in:
Mathias Mogensen 2023-10-12 06:54:08 +02:00 committed by GitHub
parent bc8f35d7db
commit ebe112581d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 317 additions and 78 deletions

View File

@ -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:appflowy_editor/appflowy_editor.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';

View File

@ -1,5 +1,5 @@
import 'package:appflowy/generated/locale_keys.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/application/settings/prelude.dart'; import 'package:appflowy/workspace/application/settings/prelude.dart';
import 'package:appflowy/workspace/presentation/settings/settings_dialog.dart'; import 'package:appflowy/workspace/presentation/settings/settings_dialog.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_menu_element.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/settings_menu_element.dart';

View File

@ -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/handlers/reminder_reference.dart';
import 'package:appflowy/plugins/inline_actions/inline_actions_command.dart'; import 'package:appflowy/plugins/inline_actions/inline_actions_command.dart';
import 'package:appflowy/plugins/inline_actions/inline_actions_service.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/application/settings/shortcuts/settings_shortcuts_service.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/emoji_picker/emoji_picker.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/emoji_picker/emoji_picker.dart';
import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor/appflowy_editor.dart';

View File

@ -2,7 +2,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.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/block_action_button.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/option_action.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/workspace/presentation/widgets/pop_up_action.dart';
import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:appflowy_popover/appflowy_popover.dart';

View File

@ -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/editor_plugins/mention/mention_block.dart';
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.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/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/application/settings/date_time/date_format_ext.dart';
import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/date_picker_dialog.dart'; import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/date_picker_dialog.dart';
import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor/appflowy_editor.dart';

View File

@ -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/edit_panel/edit_panel_bloc.dart';
import 'package:appflowy/workspace/application/favorite/favorite_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/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/settings/prelude.dart';
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart'; import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
import 'package:appflowy/workspace/application/user/prelude.dart'; import 'package:appflowy/workspace/application/user/prelude.dart';
@ -168,14 +169,22 @@ void _resolveHomeDeps(GetIt getIt) {
getIt.registerLazySingleton<TabsBloc>(() => TabsBloc()); getIt.registerLazySingleton<TabsBloc>(() => TabsBloc());
getIt.registerSingleton<ReminderBloc>(ReminderBloc()); getIt.registerSingleton<NotificationSettingsCubit>(
NotificationSettingsCubit(),
);
getIt.registerSingleton<ReminderBloc>(
ReminderBloc(notificationSettings: getIt<NotificationSettingsCubit>()),
);
} }
void _resolveFolderDeps(GetIt getIt) { void _resolveFolderDeps(GetIt getIt) {
//workspace //workspace
getIt.registerFactoryParam<WorkspaceListener, UserProfilePB, String>( getIt.registerFactoryParam<WorkspaceListener, UserProfilePB, String>(
(user, workspaceId) => (user, workspaceId) => WorkspaceListener(
WorkspaceListener(user: user, workspaceId: workspaceId), user: user,
workspaceId: workspaceId,
),
); );
getIt.registerFactoryParam<ViewBloc, ViewPB, void>( getIt.registerFactoryParam<ViewBloc, ViewPB, void>(

View File

@ -1,19 +1,23 @@
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart'; import 'package:appflowy/workspace/application/settings/notifications/notification_settings_cubit.dart';
import 'package:appflowy/workspace/application/local_notifications/notification_service.dart';
import 'package:appflowy/startup/tasks/prelude.dart'; import '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:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import '../../user/application/user_settings_service.dart'; import 'package:go_router/go_router.dart';
import '../../workspace/application/appearance.dart'; import 'package:easy_localization/easy_localization.dart';
import '../startup.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 { class InitAppWidgetTask extends LaunchTask {
const InitAppWidgetTask(); const InitAppWidgetTask();
@ -95,8 +99,8 @@ class ApplicationWidget extends StatefulWidget {
}); });
final Widget child; final Widget child;
final AppearanceSettingsPB appearanceSetting;
final AppTheme appTheme; final AppTheme appTheme;
final AppearanceSettingsPB appearanceSetting;
final DateTimeSettingsPB dateTimeSettings; final DateTimeSettingsPB dateTimeSettings;
@override @override
@ -125,6 +129,9 @@ class _ApplicationWidgetState extends State<ApplicationWidget> {
widget.appTheme, widget.appTheme,
)..readLocaleWhenAppLaunch(context), )..readLocaleWhenAppLaunch(context),
), ),
BlocProvider<NotificationSettingsCubit>(
create: (_) => getIt<NotificationSettingsCubit>(),
),
BlocProvider<DocumentAppearanceCubit>( BlocProvider<DocumentAppearanceCubit>(
create: (_) => DocumentAppearanceCubit()..fetch(), create: (_) => DocumentAppearanceCubit()..fetch(),
), ),

View File

@ -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.dart';
import 'package:appflowy/workspace/application/local_notifications/notification_action_bloc.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/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/log.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
@ -18,11 +19,16 @@ import 'package:freezed_annotation/freezed_annotation.dart';
part 'reminder_bloc.freezed.dart'; part 'reminder_bloc.freezed.dart';
class ReminderBloc extends Bloc<ReminderEvent, ReminderState> { class ReminderBloc extends Bloc<ReminderEvent, ReminderState> {
final NotificationSettingsCubit _notificationSettings;
late final NotificationActionBloc actionBloc; late final NotificationActionBloc actionBloc;
late final ReminderService reminderService; late final ReminderService reminderService;
late final Timer timer; late final Timer timer;
ReminderBloc() : super(ReminderState()) { ReminderBloc({
required NotificationSettingsCubit notificationSettings,
}) : _notificationSettings = notificationSettings,
super(ReminderState()) {
actionBloc = getIt<NotificationActionBloc>(); actionBloc = getIt<NotificationActionBloc>();
reminderService = const ReminderService(); reminderService = const ReminderService();
timer = _periodicCheck(); timer = _periodicCheck();
@ -124,16 +130,18 @@ class ReminderBloc extends Bloc<ReminderEvent, ReminderState> {
); );
if (scheduledAt.isBefore(now)) { if (scheduledAt.isBefore(now)) {
NotificationMessage( if (_notificationSettings.state.isNotificationsEnabled) {
identifier: reminder.id, NotificationMessage(
title: LocaleKeys.reminderNotification_title.tr(), identifier: reminder.id,
body: LocaleKeys.reminderNotification_message.tr(), title: LocaleKeys.reminderNotification_title.tr(),
onClick: () => actionBloc.add( body: LocaleKeys.reminderNotification_message.tr(),
NotificationActionEvent.performAction( onClick: () => actionBloc.add(
action: NotificationAction(objectId: reminder.objectId), NotificationActionEvent.performAction(
action: NotificationAction(objectId: reminder.objectId),
),
), ),
), );
); }
add( add(
ReminderEvent.update( ReminderEvent.update(

View File

@ -41,4 +41,20 @@ class UserSettingsBackendService {
) async { ) async {
return (await UserEventSetDateTimeSettings(settings).send()).swap(); return (await UserEventSetDateTimeSettings(settings).send()).swap();
} }
Future<Either<FlowyError, Unit>> setNotificationSettings(
NotificationSettingsPB settings,
) async {
return (await UserEventSetNotificationSettings(settings).send()).swap();
}
Future<NotificationSettingsPB> getNotificationSettings() async {
final result = await UserEventGetNotificationSettings().send();
return result.fold(
(NotificationSettingsPB setting) => setting,
(error) =>
throw FlowySDKException(ExceptionType.AppearanceSettingsIsEmpty),
);
}
} }

View File

@ -6,7 +6,7 @@ import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/sign_in_bloc.dart'; import 'package:appflowy/user/application/sign_in_bloc.dart';
import 'package:appflowy/user/presentation/presentation.dart'; import 'package:appflowy/user/presentation/presentation.dart';
import 'package:appflowy/util/platform_extension.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:easy_localization/easy_localization.dart';
import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/size.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';

View File

@ -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/application/historical_user_bloc.dart';
import 'package:appflowy/user/presentation/router.dart'; import 'package:appflowy/user/presentation/router.dart';
import 'package:appflowy/user/presentation/widgets/widgets.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/workspace/presentation/settings/widgets/settings_language_view.dart';
import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';

View File

@ -1,5 +1,5 @@
import 'package:appflowy/user/application/user_listener.dart'; 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/workspace/application/edit_panel/edit_context.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/workspace.pb.dart' import 'package:appflowy_backend/protobuf/flowy-folder2/workspace.pb.dart'
show WorkspaceSettingPB; show WorkspaceSettingPB;

View File

@ -16,33 +16,40 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
part 'appearance.freezed.dart'; part 'appearance_cubit.freezed.dart';
const _white = Color(0xFFFFFFFF); const _white = Color(0xFFFFFFFF);
/// [AppearanceSettingsCubit] is used to modify the appearance of AppFlowy. /// [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<AppearanceSettingsState> { class AppearanceSettingsCubit extends Cubit<AppearanceSettingsState> {
final AppearanceSettingsPB _setting; final AppearanceSettingsPB _appearanceSettings;
final DateTimeSettingsPB _dateTimeSettings; final DateTimeSettingsPB _dateTimeSettings;
AppearanceSettingsCubit( AppearanceSettingsCubit(
AppearanceSettingsPB setting, AppearanceSettingsPB appearanceSettings,
DateTimeSettingsPB dateTimeSettings, DateTimeSettingsPB dateTimeSettings,
AppTheme appTheme, AppTheme appTheme,
) : _setting = setting, ) : _appearanceSettings = appearanceSettings,
_dateTimeSettings = dateTimeSettings, _dateTimeSettings = dateTimeSettings,
super( super(
AppearanceSettingsState.initial( AppearanceSettingsState.initial(
appTheme, appTheme,
setting.themeMode, appearanceSettings.themeMode,
setting.font, appearanceSettings.font,
setting.monospaceFont, appearanceSettings.monospaceFont,
setting.layoutDirection, appearanceSettings.layoutDirection,
setting.textDirection, appearanceSettings.textDirection,
setting.locale, appearanceSettings.locale,
setting.isMenuCollapsed, appearanceSettings.isMenuCollapsed,
setting.menuOffset, appearanceSettings.menuOffset,
dateTimeSettings.dateFormat, dateTimeSettings.dateFormat,
dateTimeSettings.timeFormat, dateTimeSettings.timeFormat,
dateTimeSettings.timezoneId, dateTimeSettings.timezoneId,
@ -52,7 +59,7 @@ class AppearanceSettingsCubit extends Cubit<AppearanceSettingsState> {
/// Update selected theme in the user's settings and emit an updated state /// Update selected theme in the user's settings and emit an updated state
/// with the AppTheme named [themeName]. /// with the AppTheme named [themeName].
Future<void> setTheme(String themeName) async { Future<void> setTheme(String themeName) async {
_setting.theme = themeName; _appearanceSettings.theme = themeName;
_saveAppearanceSettings(); _saveAppearanceSettings();
emit(state.copyWith(appTheme: await AppTheme.fromName(themeName))); emit(state.copyWith(appTheme: await AppTheme.fromName(themeName)));
} }
@ -63,7 +70,7 @@ class AppearanceSettingsCubit extends Cubit<AppearanceSettingsState> {
/// Update the theme mode in the user's settings and emit an updated state. /// Update the theme mode in the user's settings and emit an updated state.
void setThemeMode(ThemeMode themeMode) { void setThemeMode(ThemeMode themeMode) {
_setting.themeMode = _themeModeToPB(themeMode); _appearanceSettings.themeMode = _themeModeToPB(themeMode);
_saveAppearanceSettings(); _saveAppearanceSettings();
emit(state.copyWith(themeMode: themeMode)); emit(state.copyWith(themeMode: themeMode));
} }
@ -81,13 +88,13 @@ class AppearanceSettingsCubit extends Cubit<AppearanceSettingsState> {
} }
void setLayoutDirection(LayoutDirection layoutDirection) { void setLayoutDirection(LayoutDirection layoutDirection) {
_setting.layoutDirection = layoutDirection.toLayoutDirectionPB(); _appearanceSettings.layoutDirection = layoutDirection.toLayoutDirectionPB();
_saveAppearanceSettings(); _saveAppearanceSettings();
emit(state.copyWith(layoutDirection: layoutDirection)); emit(state.copyWith(layoutDirection: layoutDirection));
} }
void setTextDirection(AppFlowyTextDirection? textDirection) { void setTextDirection(AppFlowyTextDirection? textDirection) {
_setting.textDirection = _appearanceSettings.textDirection =
textDirection?.toTextDirectionPB() ?? TextDirectionPB.FALLBACK; textDirection?.toTextDirectionPB() ?? TextDirectionPB.FALLBACK;
_saveAppearanceSettings(); _saveAppearanceSettings();
emit(state.copyWith(textDirection: textDirection)); emit(state.copyWith(textDirection: textDirection));
@ -96,7 +103,7 @@ class AppearanceSettingsCubit extends Cubit<AppearanceSettingsState> {
/// Update selected font in the user's settings and emit an updated state /// Update selected font in the user's settings and emit an updated state
/// with the font name. /// with the font name.
void setFontFamily(String fontFamilyName) { void setFontFamily(String fontFamilyName) {
_setting.font = fontFamilyName; _appearanceSettings.font = fontFamilyName;
_saveAppearanceSettings(); _saveAppearanceSettings();
emit(state.copyWith(font: fontFamilyName)); emit(state.copyWith(font: fontFamilyName));
} }
@ -118,8 +125,8 @@ class AppearanceSettingsCubit extends Cubit<AppearanceSettingsState> {
}); });
if (state.locale != newLocale) { if (state.locale != newLocale) {
_setting.locale.languageCode = newLocale.languageCode; _appearanceSettings.locale.languageCode = newLocale.languageCode;
_setting.locale.countryCode = newLocale.countryCode ?? ""; _appearanceSettings.locale.countryCode = newLocale.countryCode ?? "";
_saveAppearanceSettings(); _saveAppearanceSettings();
emit(state.copyWith(locale: newLocale)); emit(state.copyWith(locale: newLocale));
} }
@ -127,13 +134,13 @@ class AppearanceSettingsCubit extends Cubit<AppearanceSettingsState> {
// Saves the menus current visibility // Saves the menus current visibility
void saveIsMenuCollapsed(bool collapsed) { void saveIsMenuCollapsed(bool collapsed) {
_setting.isMenuCollapsed = collapsed; _appearanceSettings.isMenuCollapsed = collapsed;
_saveAppearanceSettings(); _saveAppearanceSettings();
} }
// Saves the current resize offset of the menu // Saves the current resize offset of the menu
void saveMenuOffset(double offset) { void saveMenuOffset(double offset) {
_setting.menuOffset = offset; _appearanceSettings.menuOffset = offset;
_saveAppearanceSettings(); _saveAppearanceSettings();
} }
@ -146,14 +153,14 @@ class AppearanceSettingsCubit extends Cubit<AppearanceSettingsState> {
} }
if (value == null) { if (value == null) {
_setting.settingKeyValue.remove(key); _appearanceSettings.settingKeyValue.remove(key);
} }
if (_setting.settingKeyValue[key] != value) { if (_appearanceSettings.settingKeyValue[key] != value) {
if (value == null) { if (value == null) {
_setting.settingKeyValue.remove(key); _appearanceSettings.settingKeyValue.remove(key);
} else { } else {
_setting.settingKeyValue[key] = value; _appearanceSettings.settingKeyValue[key] = value;
} }
} }
_saveAppearanceSettings(); _saveAppearanceSettings();
@ -164,14 +171,14 @@ class AppearanceSettingsCubit extends Cubit<AppearanceSettingsState> {
Log.warn("The key should not be empty"); Log.warn("The key should not be empty");
return null; return null;
} }
return _setting.settingKeyValue[key]; return _appearanceSettings.settingKeyValue[key];
} }
/// Called when the application launches. /// Called when the application launches.
/// Uses the device locale when the application is opened for the first time. /// Uses the device locale when the application is opened for the first time.
void readLocaleWhenAppLaunch(BuildContext context) { void readLocaleWhenAppLaunch(BuildContext context) {
if (_setting.resetToDefault) { if (_appearanceSettings.resetToDefault) {
_setting.resetToDefault = false; _appearanceSettings.resetToDefault = false;
_saveAppearanceSettings(); _saveAppearanceSettings();
setLocale(context, context.deviceLocale); setLocale(context, context.deviceLocale);
return; return;
@ -204,7 +211,9 @@ class AppearanceSettingsCubit extends Cubit<AppearanceSettingsState> {
} }
Future<void> _saveAppearanceSettings() async { Future<void> _saveAppearanceSettings() async {
UserSettingsBackendService().setAppearanceSetting(_setting).then((result) { UserSettingsBackendService()
.setAppearanceSetting(_appearanceSettings)
.then((result) {
result.fold( result.fold(
(l) => null, (l) => null,
(error) => Log.error(error), (error) => Log.error(error),

View File

@ -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<NotificationSettingsState> {
final Completer<void> _initCompleter = Completer();
late final NotificationSettingsPB _notificationSettings;
NotificationSettingsCubit() : super(NotificationSettingsState.initial()) {
UserSettingsBackendService()
.getNotificationSettings()
.then((notificationSettings) {
_notificationSettings = notificationSettings;
_initCompleter.complete();
});
}
Future<void> toggleNotificationsEnabled() async {
await _initCompleter.future;
_notificationSettings.notificationsEnabled = !state.isNotificationsEnabled;
_saveNotificationSettings();
emit(
state.copyWith(
isNotificationsEnabled: _notificationSettings.notificationsEnabled,
),
);
}
Future<void> _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);
}

View File

@ -13,6 +13,7 @@ enum SettingsPage {
language, language,
files, files,
user, user,
notifications,
syncSetting, syncSetting,
shortcuts, shortcuts,
} }

View File

@ -3,7 +3,7 @@ import 'package:appflowy/startup/plugin/plugin.dart';
import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/auth_service.dart'; import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/user/application/reminder/reminder_bloc.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_bloc.dart';
import 'package:appflowy/workspace/application/home/home_service.dart'; import 'package:appflowy/workspace/application/home/home_service.dart';
import 'package:appflowy/workspace/application/home/home_setting_bloc.dart'; import 'package:appflowy/workspace/application/home/home_setting_bloc.dart';

View File

@ -1,5 +1,5 @@
import 'dart:io'; 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/home/home_setting_bloc.dart';
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart'; import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';

View File

@ -1,5 +1,6 @@
import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/generated/locale_keys.g.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/sync_setting_view.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance_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'; import 'package:appflowy/workspace/presentation/settings/widgets/settings_customize_shortcuts_view.dart';
@ -101,6 +102,8 @@ class SettingsDialog extends StatelessWidget {
didLogout: didLogout, didLogout: didLogout,
didOpenUser: didOpenUser, didOpenUser: didOpenUser,
); );
case SettingsPage.notifications:
return const SettingsNotificationsView();
case SettingsPage.syncSetting: case SettingsPage.syncSetting:
return SyncSettingView(userId: user.id.toString()); return SyncSettingView(userId: user.id.toString());
case SettingsPage.shortcuts: case SettingsPage.shortcuts:

View File

@ -2,7 +2,7 @@ import 'dart:io';
import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.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:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';

View File

@ -1,6 +1,6 @@
import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.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/home/toast.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance/theme_setting_entry_template.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'; import 'package:appflowy/workspace/presentation/settings/widgets/theme_upload/theme_upload_view.dart';

View File

@ -1,6 +1,6 @@
import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.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:appflowy_backend/protobuf/flowy-user/date_time.pbenum.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';

View File

@ -1,7 +1,7 @@
import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.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/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:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';

View File

@ -2,7 +2,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.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/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
import 'package:appflowy/util/google_font_family_extension.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/workspace/application/appearance_defaults.dart';
import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';

View File

@ -1,6 +1,6 @@
import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.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:appflowy_backend/protobuf/flowy-user/date_time.pbenum.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';

View File

@ -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/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/date_format_setting.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance/time_format_setting.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance/time_format_setting.dart';

View File

@ -1,6 +1,6 @@
import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.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:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';

View File

@ -61,6 +61,13 @@ class SettingsMenu extends StatelessWidget {
icon: Icons.account_box_outlined, icon: Icons.account_box_outlined,
changeSelectedPage: changeSelectedPage, changeSelectedPage: changeSelectedPage,
), ),
SettingsMenuElement(
page: SettingsPage.notifications,
selectedPage: currentPage,
label: LocaleKeys.settings_menu_notifications.tr(),
icon: Icons.notifications_outlined,
changeSelectedPage: changeSelectedPage,
),
if (showSyncSetting) if (showSyncSetting)
const SizedBox( const SizedBox(
height: 10, height: 10,

View File

@ -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<NotificationSettingsCubit, NotificationSettingsState>(
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<NotificationSettingsCubit>()
.toggleNotificationsEnabled();
},
)
],
),
],
),
);
},
);
}
}

View File

@ -1,5 +1,5 @@
import 'package:appflowy/user/application/user_settings_service.dart'; 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:bloc_test/bloc_test.dart';
import 'package:appflowy_backend/protobuf/flowy-user/user_setting.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-user/user_setting.pb.dart';
import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra/theme.dart';
@ -18,6 +18,7 @@ void main() {
group('$AppearanceSettingsCubit', () { group('$AppearanceSettingsCubit', () {
late AppearanceSettingsPB appearanceSetting; late AppearanceSettingsPB appearanceSetting;
late DateTimeSettingsPB dateTimeSettings; late DateTimeSettingsPB dateTimeSettings;
setUp(() async { setUp(() async {
appearanceSetting = appearanceSetting =
await UserSettingsBackendService().getAppearanceSetting(); await UserSettingsBackendService().getAppearanceSetting();

View File

@ -1,5 +1,5 @@
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.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/workspace/presentation/settings/widgets/settings_appearance/font_family_setting.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:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';

View File

@ -241,6 +241,7 @@
"language": "Language", "language": "Language",
"user": "User", "user": "User",
"files": "Files", "files": "Files",
"notifications": "Notifications",
"open": "Open Settings", "open": "Open Settings",
"logout": "Logout", "logout": "Logout",
"logoutPrompt": "Are you sure to 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", "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" "openHistoricalUser": "Click to open the anonymous account"
}, },
"notifications": {
"enableNotifications": {
"label": "Enable notifications",
"hint": "Turn off to stop local notifications from appearing."
}
},
"appearance": { "appearance": {
"resetSetting": "Reset this setting", "resetSetting": "Reset this setting",
"fontFamily": { "fontFamily": {

View File

@ -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,
}
}
}

View File

@ -195,7 +195,7 @@ pub async fn get_date_time_settings(
Ok(setting) => setting, Ok(setting) => setting,
Err(e) => { Err(e) => {
tracing::error!( tracing::error!(
"Deserialize AppearanceSettings failed: {:?}, fallback to default", "Deserialize DateTimeSettings failed: {:?}, fallback to default",
e e
); );
DateTimeSettingsPB::default() 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<Weak<StorePreferences>>,
data: AFPluginData<NotificationSettingsPB>,
) -> 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<Weak<StorePreferences>>,
) -> DataResult<NotificationSettingsPB, FlowyError> {
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)] #[tracing::instrument(level = "debug", skip_all, err)]
pub async fn get_user_setting( pub async fn get_user_setting(
manager: AFPluginState<Weak<UserManager>>, manager: AFPluginState<Weak<UserManager>>,

View File

@ -65,6 +65,14 @@ pub fn init(user_session: Weak<UserManager>) -> AFPlugin {
.event(UserEvent::ResetWorkspace, reset_workspace_handler) .event(UserEvent::ResetWorkspace, reset_workspace_handler)
.event(UserEvent::SetDateTimeSettings, set_date_time_settings) .event(UserEvent::SetDateTimeSettings, set_date_time_settings)
.event(UserEvent::GetDateTimeSettings, get_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 { pub struct SignUpContext {
@ -317,4 +325,10 @@ pub enum UserEvent {
/// Retrieve the Date/Time formats /// Retrieve the Date/Time formats
#[event(output = "DateTimeSettingsPB")] #[event(output = "DateTimeSettingsPB")]
GetDateTimeSettings = 34, GetDateTimeSettings = 34,
#[event(input = "NotificationSettingsPB")]
SetNotificationSettings = 35,
#[event(output = "NotificationSettingsPB")]
GetNotificationSettings = 36,
} }