From d962f987860b84014c84e207c7b6edca79750fa7 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Fri, 26 Jul 2024 16:51:03 -0400 Subject: [PATCH] settings / preferences upate --- assets/i18n/en.json | 21 +- assets/sounds/badeep.wav | Bin 0 -> 27712 bytes assets/sounds/beepbadeep.wav | Bin 0 -> 61680 bytes assets/sounds/bonk.wav | Bin 0 -> 19856 bytes assets/sounds/boop.wav | Bin 0 -> 17988 bytes lib/app.dart | 2 +- lib/layout/home/home_screen.dart | 45 ++- lib/main.dart | 2 +- lib/notifications/models/models.dart | 2 + .../models/notifications_preference.dart | 69 ++++ .../notifications_preference.freezed.dart | 358 ++++++++++++++++++ .../models/notifications_preference.g.dart | 50 +++ lib/notifications/notifications.dart | 4 +- .../views/notifications_preferences.dart | 285 ++++++++++++++ lib/notifications/views/views.dart | 2 + lib/settings/models/preferences.dart | 26 +- lib/settings/models/preferences.freezed.dart | 183 +++++---- lib/settings/models/preferences.g.dart | 28 +- lib/settings/settings_page.dart | 3 + lib/theme/models/theme_preference.dart | 13 +- .../models/theme_preference.freezed.dart | 15 +- lib/theme/models/theme_preference.g.dart | 11 +- lib/theme/views/brightness_preferences.dart | 4 +- lib/theme/views/color_preferences.dart | 4 +- lib/theme/views/widget_helpers.dart | 8 + pubspec.yaml | 5 + 26 files changed, 1015 insertions(+), 125 deletions(-) create mode 100644 assets/sounds/badeep.wav create mode 100644 assets/sounds/beepbadeep.wav create mode 100644 assets/sounds/bonk.wav create mode 100644 assets/sounds/boop.wav create mode 100644 lib/notifications/models/models.dart create mode 100644 lib/notifications/models/notifications_preference.dart create mode 100644 lib/notifications/models/notifications_preference.freezed.dart create mode 100644 lib/notifications/models/notifications_preference.g.dart create mode 100644 lib/notifications/views/notifications_preferences.dart create mode 100644 lib/notifications/views/views.dart diff --git a/assets/i18n/en.json b/assets/i18n/en.json index 7185fab..0fad430 100644 --- a/assets/i18n/en.json +++ b/assets/i18n/en.json @@ -219,7 +219,26 @@ "settings_page": { "titlebar": "Settings", "color_theme": "Color Theme", - "brightness_mode": "Brightness Mode" + "brightness_mode": "Brightness Mode", + "display_beta_warning": "Display beta warning on startup", + "none": "None", + "in_app": "In-app", + "push": "Push", + "in_app_or_push": "In-app or Push", + "enable_badge": "Enable icon 'badge' bubble", + "enable_notifications": "Enable notifications", + "message_notification_content": "Message notification content", + "invitation_accepted": "On invitation accept/reject", + "message_received": "On message received", + "message_sent": "On message sent", + "name_and_content": "Name and content", + "name_only": "Name only", + "nothing": "Nothing", + "bonk": "Bonk", + "boop": "Boop", + "badeep": "Badeep", + "beep_badeep": "Beep-Badeep", + "custom": "Custom" }, "developer": { "title": "Developer Logs", diff --git a/assets/sounds/badeep.wav b/assets/sounds/badeep.wav new file mode 100644 index 0000000000000000000000000000000000000000..475c2100cf3ab731bb890a9f83d583cbf33d3a02 GIT binary patch literal 27712 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

g&=ve&U*g@HwZ`Caq6e)cHz7-K)q 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 Ytu@+R+{@*3otInL+T-(HUu}DqFHwX?ZvX%Q literal 0 HcmV?d00001 diff --git a/assets/sounds/beepbadeep.wav b/assets/sounds/beepbadeep.wav new file mode 100644 index 0000000000000000000000000000000000000000..8577b125fa96257fef5878a36f7a8af0cd634682 GIT binary patch literal 61680 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 literal 0 HcmV?d00001 diff --git a/assets/sounds/bonk.wav b/assets/sounds/bonk.wav new file mode 100644 index 0000000000000000000000000000000000000000..3e9236c3d5f8b677c9b23983d5984b96ab1e864d GIT binary patch literal 19856 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

)!ot+&Eje)#i zdsqS&!sn5VNPpA;{R-;8-9enX6Z8=uP%?h->Q~BG%6N)BMTb5|m!o~qGUN{8hcJ-2 z@EG_U$U%z1S$+m7A_o#vh@bJ>cspi`Pr`o2+O1w#R~>jRpktc#oOOluKDg?zQn3VV zGj<0P;PLniyb51K>?eAWiQuUa2>J}Yh7Q7O0FQhTHSz)3jE+F{=o>TzAb1Bjt96nx zfwGq}lro9pOrcZ0qsP$MC=;zm(vd;PE%*d11G*Xrb%W-S8RSLc5z&TIhy;8VegiAR zA^^6#@bW95X`S^Musm;oMQ;Zh77ntqqgWfDdn^7s9sz8lih#(+zX&`9I^ov)u8Xt z{pd*43;hIad@#aBHUgjc7J34_EE5_*P9lFLt`qGzl}N&u;n|o98-?w}NNXJS)k?>@ zXn6y0uCn@Jy#eNb$4aqopuhAQ)SKoI8wpo3kgNdjpP?O4KR5zb!4>c?;B1~VszMH+ z)6fp|9_mHuKzmXkN(==(-pmXppxDVoqVnuZQQ?a^}N9x?&xj(mXc0F4mAz@tGWWEmMw#*+t$ zi$oVrGw_4B8S}u;VBfGQ*a56Nprfl^IsnVWR$!N~u9{UB&FArKJOrGZ$|0(L;F=9_ zpu506j)wcf3b+7XgN#Btk+(<+>IQUt7MhLzjOL?fz$uagKj;|^Fs(zrBAbz^h!Mp4 zGr(Dum(T-93^@QlyNN6#+KJu(NB8i2a6)G)-er$>U{A2I*dDA8pk*+&4x5ji!>(hM z7zGc;m*dB99iV?NagGQ8vCJnjmz)dDhS;zLx&-foLlFUT6WN6bQ3;ZUMxrNy?S6=E z2A?f}p4lLp4gz+l7C8s}y%&Na$AHhbLNaJFu(2=5*Q6WJmF)l#UAS0*Rv(iBWMzw2utBS zcp->&Du68xLs4`I+86LC9X$`O4x%#v$D&bZpk*(S)5uVu`Jdt2pljg`pMVZQBzSe% z$L~Zn(G%os&p{t*D4v1KF(LjtRs^aYx3GDDH>&`5y7H$6jEQ&0r{EXy_qZ3)15}IN z6NAaIAOmdz_HhLyf~oLT_z)b3aF8>|D#U<%L*h|3KurwV6(eS#z0u)7jv;N9CVL-NP3fDkHI&_w7X zh$Z}C2Vk`qz+d4kn2yNdSi}LGvWNr-?g7vq1-RmZ@DL4L3O|Rtd|(0`1UCV_-vVOC zF4=z|Ka=yxr66N-Azu;sK%0LCc3=V_AVPue2?;rfnG}FV7;w;+=tqnvGKj;(l^=Go zi$Ckh9b^GnO1eYtAj)0{IR6-80elYyIlv}(9(V%kAe;lvGZlfFNk04-eh0p}3cl<5 zEg9G@TNsA(pm)$tXyXr=>A~)@$S34NawQo;b|-bfy1WIk#Tg*O?Zk0F-(Dh<*hOq0 zegW^R;Mu*r$Qo5sT3V0V}glaL0C%Z08(7eJ?RIWz$31L9d8)K0bnBt0Xyko!RPKLgmN zo+OuaCA9>FY$DVk3zHCK;8PE-HNeMr$v1?I18ANG=IkZ!fw@g&8_5UH#r6TaTnf#D z&VeUYbD(EX2UH26uomE*257K_Jz+b5G7gB!2uKIDL#5DHaPsaN;Lj%D&qe`H?E_gz z3&xatXPb972u(Ct;&V7HJP?LdiD51Y&3{pvVZ`6w-$5%7VI+LjmJkn#plI7_hF`Jby+@yE-nHw#?#TAnti$LN8Hf7}lImEbimGjnN=u&~vuSBI{S3SF{% zZdgQoeEc8FfDs`p)@Ed;t_jIl;r*9I{<_Y<^cAz0&tIB3e@TW%*Se{*maNQ7@bc=q z(VwqB_O*Cva`KJ!xQX@I~nS`Dr}{bniZJ zP-5Jmh{X7aL6H#=agp6)`o%;>#YQJa#>d4a_6qx_sejwtAIlD0GJEA>aLYfIP3u}V z=HFZP@239KvLlwy2RKSy^mk*rsQ!Pl{J&V@pKdc`{))^c%h&z@$DcC$fs!%l%l`4( zE>3oddC~kI(q5Xnd_{T}=M%jC%KAUy@#jiibACv$N9yb@ob+Cix+*=bSJ*#%_cx;c zocG71`Ts&>vHvID{$=gIoBCg`zj|JJMsJ`rQ6b?`A(7!@!^3+-M)ZgTf5!F*5C1Ef zKSur6i>z2OCv$b`^7MXl0SbEmMN3C6PhUJg9cbyuV3k^M!G z|EPt3wMYM(*8XMa-8wfHYyYT(+8)W!2>C#R>)2hRDg zeC{%HfVP3jJyKVu&0jJmb@9?g>7%G T$1c|23LWVgpt`XdlrjDnt08bA literal 0 HcmV?d00001 diff --git a/assets/sounds/boop.wav b/assets/sounds/boop.wav new file mode 100644 index 0000000000000000000000000000000000000000..df284bc5d942faeab211c1fbd9b05a697aeae8c4 GIT binary patch literal 17988 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 literal 0 HcmV?d00001 diff --git a/lib/app.dart b/lib/app.dart index 5c47fa3..98e349a 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -43,7 +43,7 @@ class VeilidChatApp extends StatelessWidget { void _reloadTheme(BuildContext context) { log.info('Reloading theme'); final theme = - PreferencesRepository.instance.value.themePreferences.themeData(); + PreferencesRepository.instance.value.themePreference.themeData(); ThemeSwitcher.of(context).changeTheme(theme: theme); // Hack to reload translations diff --git a/lib/layout/home/home_screen.dart b/lib/layout/home/home_screen.dart index 8e3bf48..a941220 100644 --- a/lib/layout/home/home_screen.dart +++ b/lib/layout/home/home_screen.dart @@ -12,6 +12,7 @@ import 'package:url_launcher/url_launcher_string.dart'; import 'package:veilid_support/veilid_support.dart'; import '../../account_manager/account_manager.dart'; +import '../../settings/settings.dart'; import '../../theme/theme.dart'; import 'drawer_menu/drawer_menu.dart'; import 'home_account_invalid.dart'; @@ -41,7 +42,17 @@ class HomeScreenState extends State .indexWhere((x) => x.superIdentity.recordKey == activeLocalAccount); final canClose = activeIndex != -1; - unawaited(_doBetaDialog(context)); + final displayBetaWarning = context + .read() + .state + .asData + ?.value + .notificationsPreference + .displayBetaWarning ?? + true; + if (displayBetaWarning) { + await _doBetaDialog(context); + } if (!canClose) { await _zoomDrawerController.open!(); @@ -51,10 +62,13 @@ class HomeScreenState extends State } Future _doBetaDialog(BuildContext context) async { + var displayBetaWarning = true; + await QuickAlert.show( - context: context, - title: translate('splash.beta_title'), - widget: RichText( + context: context, + title: translate('splash.beta_title'), + widget: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ + RichText( textAlign: TextAlign.center, text: TextSpan( children: [ @@ -77,7 +91,28 @@ class HomeScreenState extends State ], ), ), - type: QuickAlertType.warning); + Row(mainAxisSize: MainAxisSize.min, children: [ + StatefulBuilder( + builder: (context, setState) => Checkbox.adaptive( + value: displayBetaWarning, + onChanged: (value) { + setState(() { + displayBetaWarning = value ?? true; + }); + }, + )), + Text(translate('settings_page.display_beta_warning'), + style: const TextStyle(color: Colors.black)), + ]), + ]), + type: QuickAlertType.warning, + ); + + final preferencesInstance = PreferencesRepository.instance; + await preferencesInstance.set(preferencesInstance.value.copyWith( + notificationsPreference: preferencesInstance + .value.notificationsPreference + .copyWith(displayBetaWarning: displayBetaWarning))); } Widget _buildAccountPage( diff --git a/lib/main.dart b/lib/main.dart index 4edaa5b..7193e06 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -36,7 +36,7 @@ void main() async { WidgetsFlutterBinding.ensureInitialized(); await PreferencesRepository.instance.init(); final initialThemeData = - PreferencesRepository.instance.value.themePreferences.themeData(); + PreferencesRepository.instance.value.themePreference.themeData(); // Manage window on desktop platforms await initializeWindowControl(); diff --git a/lib/notifications/models/models.dart b/lib/notifications/models/models.dart new file mode 100644 index 0000000..52f3c2b --- /dev/null +++ b/lib/notifications/models/models.dart @@ -0,0 +1,2 @@ +export 'notifications_preference.dart'; +export 'notifications_state.dart'; diff --git a/lib/notifications/models/notifications_preference.dart b/lib/notifications/models/notifications_preference.dart new file mode 100644 index 0000000..35385c6 --- /dev/null +++ b/lib/notifications/models/notifications_preference.dart @@ -0,0 +1,69 @@ +import 'package:change_case/change_case.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'notifications_preference.freezed.dart'; +part 'notifications_preference.g.dart'; + +@freezed +class NotificationsPreference with _$NotificationsPreference { + const factory NotificationsPreference({ + @Default(true) bool displayBetaWarning, + @Default(true) bool enableBadge, + @Default(true) bool enableNotifications, + @Default(MessageNotificationContent.nameAndContent) + MessageNotificationContent messageNotificationContent, + @Default(NotificationMode.inAppOrPush) + NotificationMode onInvitationAcceptedMode, + @Default(SoundEffect.beepBaDeep) SoundEffect onInvitationAcceptedSound, + @Default(NotificationMode.inAppOrPush) + NotificationMode onMessageReceivedMode, + @Default(SoundEffect.boop) SoundEffect onMessageReceivedSound, + @Default(SoundEffect.bonk) SoundEffect onMessageSentSound, + }) = _NotificationsPreference; + + factory NotificationsPreference.fromJson(dynamic json) => + _$NotificationsPreferenceFromJson(json as Map); + + static const NotificationsPreference defaults = NotificationsPreference(); +} + +enum NotificationMode { + none, + inApp, + push, + inAppOrPush; + + factory NotificationMode.fromJson(dynamic j) => + NotificationMode.values.byName((j as String).toCamelCase()); + String toJson() => name.toPascalCase(); + + static const NotificationMode defaults = NotificationMode.none; +} + +enum MessageNotificationContent { + nothing, + nameOnly, + nameAndContent; + + factory MessageNotificationContent.fromJson(dynamic j) => + MessageNotificationContent.values.byName((j as String).toCamelCase()); + String toJson() => name.toPascalCase(); + + static const MessageNotificationContent defaults = + MessageNotificationContent.nothing; +} + +enum SoundEffect { + none, + bonk, + boop, + baDeep, + beepBaDeep, + custom; + + factory SoundEffect.fromJson(dynamic j) => + SoundEffect.values.byName((j as String).toCamelCase()); + String toJson() => name.toPascalCase(); + + static const SoundEffect defaults = SoundEffect.none; +} diff --git a/lib/notifications/models/notifications_preference.freezed.dart b/lib/notifications/models/notifications_preference.freezed.dart new file mode 100644 index 0000000..b2cbc67 --- /dev/null +++ b/lib/notifications/models/notifications_preference.freezed.dart @@ -0,0 +1,358 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'notifications_preference.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +NotificationsPreference _$NotificationsPreferenceFromJson( + Map json) { + return _NotificationsPreference.fromJson(json); +} + +/// @nodoc +mixin _$NotificationsPreference { + bool get displayBetaWarning => throw _privateConstructorUsedError; + bool get enableBadge => throw _privateConstructorUsedError; + bool get enableNotifications => throw _privateConstructorUsedError; + MessageNotificationContent get messageNotificationContent => + throw _privateConstructorUsedError; + NotificationMode get onInvitationAcceptedMode => + throw _privateConstructorUsedError; + SoundEffect get onInvitationAcceptedSound => + throw _privateConstructorUsedError; + NotificationMode get onMessageReceivedMode => + throw _privateConstructorUsedError; + SoundEffect get onMessageReceivedSound => throw _privateConstructorUsedError; + SoundEffect get onMessageSentSound => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $NotificationsPreferenceCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $NotificationsPreferenceCopyWith<$Res> { + factory $NotificationsPreferenceCopyWith(NotificationsPreference value, + $Res Function(NotificationsPreference) then) = + _$NotificationsPreferenceCopyWithImpl<$Res, NotificationsPreference>; + @useResult + $Res call( + {bool displayBetaWarning, + bool enableBadge, + bool enableNotifications, + MessageNotificationContent messageNotificationContent, + NotificationMode onInvitationAcceptedMode, + SoundEffect onInvitationAcceptedSound, + NotificationMode onMessageReceivedMode, + SoundEffect onMessageReceivedSound, + SoundEffect onMessageSentSound}); +} + +/// @nodoc +class _$NotificationsPreferenceCopyWithImpl<$Res, + $Val extends NotificationsPreference> + implements $NotificationsPreferenceCopyWith<$Res> { + _$NotificationsPreferenceCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? displayBetaWarning = null, + Object? enableBadge = null, + Object? enableNotifications = null, + Object? messageNotificationContent = null, + Object? onInvitationAcceptedMode = null, + Object? onInvitationAcceptedSound = null, + Object? onMessageReceivedMode = null, + Object? onMessageReceivedSound = null, + Object? onMessageSentSound = null, + }) { + return _then(_value.copyWith( + displayBetaWarning: null == displayBetaWarning + ? _value.displayBetaWarning + : displayBetaWarning // ignore: cast_nullable_to_non_nullable + as bool, + enableBadge: null == enableBadge + ? _value.enableBadge + : enableBadge // ignore: cast_nullable_to_non_nullable + as bool, + enableNotifications: null == enableNotifications + ? _value.enableNotifications + : enableNotifications // ignore: cast_nullable_to_non_nullable + as bool, + messageNotificationContent: null == messageNotificationContent + ? _value.messageNotificationContent + : messageNotificationContent // ignore: cast_nullable_to_non_nullable + as MessageNotificationContent, + onInvitationAcceptedMode: null == onInvitationAcceptedMode + ? _value.onInvitationAcceptedMode + : onInvitationAcceptedMode // ignore: cast_nullable_to_non_nullable + as NotificationMode, + onInvitationAcceptedSound: null == onInvitationAcceptedSound + ? _value.onInvitationAcceptedSound + : onInvitationAcceptedSound // ignore: cast_nullable_to_non_nullable + as SoundEffect, + onMessageReceivedMode: null == onMessageReceivedMode + ? _value.onMessageReceivedMode + : onMessageReceivedMode // ignore: cast_nullable_to_non_nullable + as NotificationMode, + onMessageReceivedSound: null == onMessageReceivedSound + ? _value.onMessageReceivedSound + : onMessageReceivedSound // ignore: cast_nullable_to_non_nullable + as SoundEffect, + onMessageSentSound: null == onMessageSentSound + ? _value.onMessageSentSound + : onMessageSentSound // ignore: cast_nullable_to_non_nullable + as SoundEffect, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$NotificationsPreferenceImplCopyWith<$Res> + implements $NotificationsPreferenceCopyWith<$Res> { + factory _$$NotificationsPreferenceImplCopyWith( + _$NotificationsPreferenceImpl value, + $Res Function(_$NotificationsPreferenceImpl) then) = + __$$NotificationsPreferenceImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {bool displayBetaWarning, + bool enableBadge, + bool enableNotifications, + MessageNotificationContent messageNotificationContent, + NotificationMode onInvitationAcceptedMode, + SoundEffect onInvitationAcceptedSound, + NotificationMode onMessageReceivedMode, + SoundEffect onMessageReceivedSound, + SoundEffect onMessageSentSound}); +} + +/// @nodoc +class __$$NotificationsPreferenceImplCopyWithImpl<$Res> + extends _$NotificationsPreferenceCopyWithImpl<$Res, + _$NotificationsPreferenceImpl> + implements _$$NotificationsPreferenceImplCopyWith<$Res> { + __$$NotificationsPreferenceImplCopyWithImpl( + _$NotificationsPreferenceImpl _value, + $Res Function(_$NotificationsPreferenceImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? displayBetaWarning = null, + Object? enableBadge = null, + Object? enableNotifications = null, + Object? messageNotificationContent = null, + Object? onInvitationAcceptedMode = null, + Object? onInvitationAcceptedSound = null, + Object? onMessageReceivedMode = null, + Object? onMessageReceivedSound = null, + Object? onMessageSentSound = null, + }) { + return _then(_$NotificationsPreferenceImpl( + displayBetaWarning: null == displayBetaWarning + ? _value.displayBetaWarning + : displayBetaWarning // ignore: cast_nullable_to_non_nullable + as bool, + enableBadge: null == enableBadge + ? _value.enableBadge + : enableBadge // ignore: cast_nullable_to_non_nullable + as bool, + enableNotifications: null == enableNotifications + ? _value.enableNotifications + : enableNotifications // ignore: cast_nullable_to_non_nullable + as bool, + messageNotificationContent: null == messageNotificationContent + ? _value.messageNotificationContent + : messageNotificationContent // ignore: cast_nullable_to_non_nullable + as MessageNotificationContent, + onInvitationAcceptedMode: null == onInvitationAcceptedMode + ? _value.onInvitationAcceptedMode + : onInvitationAcceptedMode // ignore: cast_nullable_to_non_nullable + as NotificationMode, + onInvitationAcceptedSound: null == onInvitationAcceptedSound + ? _value.onInvitationAcceptedSound + : onInvitationAcceptedSound // ignore: cast_nullable_to_non_nullable + as SoundEffect, + onMessageReceivedMode: null == onMessageReceivedMode + ? _value.onMessageReceivedMode + : onMessageReceivedMode // ignore: cast_nullable_to_non_nullable + as NotificationMode, + onMessageReceivedSound: null == onMessageReceivedSound + ? _value.onMessageReceivedSound + : onMessageReceivedSound // ignore: cast_nullable_to_non_nullable + as SoundEffect, + onMessageSentSound: null == onMessageSentSound + ? _value.onMessageSentSound + : onMessageSentSound // ignore: cast_nullable_to_non_nullable + as SoundEffect, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$NotificationsPreferenceImpl implements _NotificationsPreference { + const _$NotificationsPreferenceImpl( + {this.displayBetaWarning = true, + this.enableBadge = true, + this.enableNotifications = true, + this.messageNotificationContent = + MessageNotificationContent.nameAndContent, + this.onInvitationAcceptedMode = NotificationMode.inAppOrPush, + this.onInvitationAcceptedSound = SoundEffect.beepBaDeep, + this.onMessageReceivedMode = NotificationMode.inAppOrPush, + this.onMessageReceivedSound = SoundEffect.boop, + this.onMessageSentSound = SoundEffect.bonk}); + + factory _$NotificationsPreferenceImpl.fromJson(Map json) => + _$$NotificationsPreferenceImplFromJson(json); + + @override + @JsonKey() + final bool displayBetaWarning; + @override + @JsonKey() + final bool enableBadge; + @override + @JsonKey() + final bool enableNotifications; + @override + @JsonKey() + final MessageNotificationContent messageNotificationContent; + @override + @JsonKey() + final NotificationMode onInvitationAcceptedMode; + @override + @JsonKey() + final SoundEffect onInvitationAcceptedSound; + @override + @JsonKey() + final NotificationMode onMessageReceivedMode; + @override + @JsonKey() + final SoundEffect onMessageReceivedSound; + @override + @JsonKey() + final SoundEffect onMessageSentSound; + + @override + String toString() { + return 'NotificationsPreference(displayBetaWarning: $displayBetaWarning, enableBadge: $enableBadge, enableNotifications: $enableNotifications, messageNotificationContent: $messageNotificationContent, onInvitationAcceptedMode: $onInvitationAcceptedMode, onInvitationAcceptedSound: $onInvitationAcceptedSound, onMessageReceivedMode: $onMessageReceivedMode, onMessageReceivedSound: $onMessageReceivedSound, onMessageSentSound: $onMessageSentSound)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$NotificationsPreferenceImpl && + (identical(other.displayBetaWarning, displayBetaWarning) || + other.displayBetaWarning == displayBetaWarning) && + (identical(other.enableBadge, enableBadge) || + other.enableBadge == enableBadge) && + (identical(other.enableNotifications, enableNotifications) || + other.enableNotifications == enableNotifications) && + (identical(other.messageNotificationContent, + messageNotificationContent) || + other.messageNotificationContent == + messageNotificationContent) && + (identical( + other.onInvitationAcceptedMode, onInvitationAcceptedMode) || + other.onInvitationAcceptedMode == onInvitationAcceptedMode) && + (identical(other.onInvitationAcceptedSound, + onInvitationAcceptedSound) || + other.onInvitationAcceptedSound == onInvitationAcceptedSound) && + (identical(other.onMessageReceivedMode, onMessageReceivedMode) || + other.onMessageReceivedMode == onMessageReceivedMode) && + (identical(other.onMessageReceivedSound, onMessageReceivedSound) || + other.onMessageReceivedSound == onMessageReceivedSound) && + (identical(other.onMessageSentSound, onMessageSentSound) || + other.onMessageSentSound == onMessageSentSound)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, + displayBetaWarning, + enableBadge, + enableNotifications, + messageNotificationContent, + onInvitationAcceptedMode, + onInvitationAcceptedSound, + onMessageReceivedMode, + onMessageReceivedSound, + onMessageSentSound); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$NotificationsPreferenceImplCopyWith<_$NotificationsPreferenceImpl> + get copyWith => __$$NotificationsPreferenceImplCopyWithImpl< + _$NotificationsPreferenceImpl>(this, _$identity); + + @override + Map toJson() { + return _$$NotificationsPreferenceImplToJson( + this, + ); + } +} + +abstract class _NotificationsPreference implements NotificationsPreference { + const factory _NotificationsPreference( + {final bool displayBetaWarning, + final bool enableBadge, + final bool enableNotifications, + final MessageNotificationContent messageNotificationContent, + final NotificationMode onInvitationAcceptedMode, + final SoundEffect onInvitationAcceptedSound, + final NotificationMode onMessageReceivedMode, + final SoundEffect onMessageReceivedSound, + final SoundEffect onMessageSentSound}) = _$NotificationsPreferenceImpl; + + factory _NotificationsPreference.fromJson(Map json) = + _$NotificationsPreferenceImpl.fromJson; + + @override + bool get displayBetaWarning; + @override + bool get enableBadge; + @override + bool get enableNotifications; + @override + MessageNotificationContent get messageNotificationContent; + @override + NotificationMode get onInvitationAcceptedMode; + @override + SoundEffect get onInvitationAcceptedSound; + @override + NotificationMode get onMessageReceivedMode; + @override + SoundEffect get onMessageReceivedSound; + @override + SoundEffect get onMessageSentSound; + @override + @JsonKey(ignore: true) + _$$NotificationsPreferenceImplCopyWith<_$NotificationsPreferenceImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/notifications/models/notifications_preference.g.dart b/lib/notifications/models/notifications_preference.g.dart new file mode 100644 index 0000000..d22b4b5 --- /dev/null +++ b/lib/notifications/models/notifications_preference.g.dart @@ -0,0 +1,50 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'notifications_preference.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$NotificationsPreferenceImpl _$$NotificationsPreferenceImplFromJson( + Map json) => + _$NotificationsPreferenceImpl( + displayBetaWarning: json['display_beta_warning'] as bool? ?? true, + enableBadge: json['enable_badge'] as bool? ?? true, + enableNotifications: json['enable_notifications'] as bool? ?? true, + messageNotificationContent: json['message_notification_content'] == null + ? MessageNotificationContent.nameAndContent + : MessageNotificationContent.fromJson( + json['message_notification_content']), + onInvitationAcceptedMode: json['on_invitation_accepted_mode'] == null + ? NotificationMode.inAppOrPush + : NotificationMode.fromJson(json['on_invitation_accepted_mode']), + onInvitationAcceptedSound: json['on_invitation_accepted_sound'] == null + ? SoundEffect.beepBaDeep + : SoundEffect.fromJson(json['on_invitation_accepted_sound']), + onMessageReceivedMode: json['on_message_received_mode'] == null + ? NotificationMode.inAppOrPush + : NotificationMode.fromJson(json['on_message_received_mode']), + onMessageReceivedSound: json['on_message_received_sound'] == null + ? SoundEffect.boop + : SoundEffect.fromJson(json['on_message_received_sound']), + onMessageSentSound: json['on_message_sent_sound'] == null + ? SoundEffect.bonk + : SoundEffect.fromJson(json['on_message_sent_sound']), + ); + +Map _$$NotificationsPreferenceImplToJson( + _$NotificationsPreferenceImpl instance) => + { + 'display_beta_warning': instance.displayBetaWarning, + 'enable_badge': instance.enableBadge, + 'enable_notifications': instance.enableNotifications, + 'message_notification_content': + instance.messageNotificationContent.toJson(), + 'on_invitation_accepted_mode': instance.onInvitationAcceptedMode.toJson(), + 'on_invitation_accepted_sound': + instance.onInvitationAcceptedSound.toJson(), + 'on_message_received_mode': instance.onMessageReceivedMode.toJson(), + 'on_message_received_sound': instance.onMessageReceivedSound.toJson(), + 'on_message_sent_sound': instance.onMessageSentSound.toJson(), + }; diff --git a/lib/notifications/notifications.dart b/lib/notifications/notifications.dart index 5841483..0426651 100644 --- a/lib/notifications/notifications.dart +++ b/lib/notifications/notifications.dart @@ -1,3 +1,3 @@ export 'cubits/notifications_cubit.dart'; -export 'models/notifications_state.dart'; -export 'views/notifications_widget.dart'; +export 'models/models.dart'; +export 'views/views.dart'; diff --git a/lib/notifications/views/notifications_preferences.dart b/lib/notifications/views/notifications_preferences.dart new file mode 100644 index 0000000..12f4667 --- /dev/null +++ b/lib/notifications/views/notifications_preferences.dart @@ -0,0 +1,285 @@ +import 'package:awesome_extensions/awesome_extensions.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:flutter_translate/flutter_translate.dart'; + +import '../../settings/settings.dart'; +import '../../theme/theme.dart'; +import '../notifications.dart'; + +const String formFieldDisplayBetaWarning = 'displayBetaWarning'; +const String formFieldEnableBadge = 'enableBadge'; +const String formFieldEnableNotifications = 'enableNotifications'; +const String formFieldMessageNotificationContent = 'messageNotificationContent'; +const String formFieldInvitationAcceptMode = 'invitationAcceptMode'; +const String formFieldInvitationAcceptSound = 'invitationAcceptSound'; +const String formFieldMessageReceivedMode = 'messageReceivedMode'; +const String formFieldMessageReceivedSound = 'messageReceivedSound'; +const String formFieldMessageSentSound = 'messageSentSound'; + +Widget buildSettingsPageNotificationPreferences( + {required BuildContext context, required void Function() onChanged}) { + final theme = Theme.of(context); + final scale = theme.extension()!; + final scaleConfig = theme.extension()!; + final textTheme = theme.textTheme; + + final preferencesRepository = PreferencesRepository.instance; + final notificationsPreference = + preferencesRepository.value.notificationsPreference; + + Future updatePreferences( + NotificationsPreference newNotificationsPreference) async { + final newPrefs = preferencesRepository.value + .copyWith(notificationsPreference: newNotificationsPreference); + await preferencesRepository.set(newPrefs); + onChanged(); + } + + List> notificationModeItems() { + final out = >[]; + final items = [ + (NotificationMode.none, true, translate('settings_page.none')), + (NotificationMode.inApp, true, translate('settings_page.in_app')), + (NotificationMode.push, false, translate('settings_page.push')), + ( + NotificationMode.inAppOrPush, + true, + translate('settings_page.in_app_or_push') + ), + ]; + for (final x in items) { + out.add(DropdownMenuItem( + value: x.$1, + enabled: x.$2, + child: Text(x.$3, style: textTheme.labelSmall))); + } + return out; + } + + List> soundEffectItems() { + final out = >[]; + final items = [ + (SoundEffect.none, true, translate('settings_page.none')), + (SoundEffect.bonk, true, translate('settings_page.bonk')), + (SoundEffect.boop, true, translate('settings_page.boop')), + (SoundEffect.baDeep, true, translate('settings_page.badeep')), + (SoundEffect.beepBaDeep, true, translate('settings_page.beep_badeep')), + (SoundEffect.custom, false, translate('settings_page.custom')), + ]; + for (final x in items) { + out.add(DropdownMenuItem( + value: x.$1, + enabled: x.$2, + child: Text(x.$3, style: textTheme.labelSmall))); + } + return out; + } + + List> + messageNotificationContentItems() { + final out = >[]; + final items = [ + ( + MessageNotificationContent.nameAndContent, + true, + translate('settings_page.name_and_content') + ), + ( + MessageNotificationContent.nameOnly, + true, + translate('settings_page.name_only') + ), + ( + MessageNotificationContent.nothing, + true, + translate('settings_page.nothing') + ), + ]; + for (final x in items) { + out.add(DropdownMenuItem( + value: x.$1, + enabled: x.$2, + child: Text(x.$3, style: textTheme.labelSmall))); + } + return out; + } + + return DecoratedBox( + decoration: ShapeDecoration( + shape: RoundedRectangleBorder( + side: BorderSide(width: 2, color: scale.primaryScale.border), + borderRadius: + BorderRadius.circular(8 * scaleConfig.borderRadiusScale))), + child: Column(mainAxisSize: MainAxisSize.min, children: [ + // Display Beta Warning + FormBuilderCheckbox( + name: formFieldDisplayBetaWarning, + side: BorderSide(color: scale.primaryScale.border, width: 2), + title: Text(translate('settings_page.display_beta_warning'), + style: textTheme.labelMedium), + initialValue: notificationsPreference.displayBetaWarning, + onChanged: (value) async { + if (value == null) { + return; + } + final newNotificationsPreference = + notificationsPreference.copyWith(displayBetaWarning: value); + + await updatePreferences(newNotificationsPreference); + }), + // Enable Badge + FormBuilderCheckbox( + name: formFieldEnableBadge, + side: BorderSide(color: scale.primaryScale.border, width: 2), + title: Text(translate('settings_page.enable_badge'), + style: textTheme.labelMedium), + initialValue: notificationsPreference.enableBadge, + onChanged: (value) async { + if (value == null) { + return; + } + final newNotificationsPreference = + notificationsPreference.copyWith(enableBadge: value); + await updatePreferences(newNotificationsPreference); + }), + // Enable Notifications + FormBuilderCheckbox( + name: formFieldEnableNotifications, + side: BorderSide(color: scale.primaryScale.border, width: 2), + title: Text(translate('settings_page.enable_notifications'), + style: textTheme.labelMedium), + initialValue: notificationsPreference.enableNotifications, + onChanged: (value) async { + if (value == null) { + return; + } + final newNotificationsPreference = + notificationsPreference.copyWith(enableNotifications: value); + await updatePreferences(newNotificationsPreference); + }), + + FormBuilderDropdown( + name: formFieldMessageNotificationContent, + isDense: false, + decoration: InputDecoration( + labelText: translate('settings_page.message_notification_content')), + enabled: notificationsPreference.enableNotifications, + initialValue: notificationsPreference.messageNotificationContent, + onChanged: (value) async { + if (value == null) { + return; + } + final newNotificationsPreference = notificationsPreference.copyWith( + messageNotificationContent: value); + await updatePreferences(newNotificationsPreference); + }, + items: messageNotificationContentItems(), + ).paddingAll(8), + + // Notifications + Table( + defaultVerticalAlignment: TableCellVerticalAlignment.middle, + children: [ + TableRow(children: [ + // Invitation accepted + Text( + textAlign: TextAlign.right, + translate('settings_page.invitation_accepted')) + .paddingAll(8), + FormBuilderDropdown( + name: formFieldInvitationAcceptMode, + isDense: false, + enabled: notificationsPreference.enableNotifications, + initialValue: notificationsPreference.onInvitationAcceptedMode, + onChanged: (value) async { + if (value == null) { + return; + } + final newNotificationsPreference = notificationsPreference + .copyWith(onInvitationAcceptedMode: value); + await updatePreferences(newNotificationsPreference); + }, + items: notificationModeItems(), + ).paddingAll(4), + FormBuilderDropdown( + name: formFieldInvitationAcceptSound, + isDense: false, + enabled: notificationsPreference.enableNotifications, + initialValue: notificationsPreference.onInvitationAcceptedSound, + onChanged: (value) async { + if (value == null) { + return; + } + final newNotificationsPreference = notificationsPreference + .copyWith(onInvitationAcceptedSound: value); + await updatePreferences(newNotificationsPreference); + }, + items: soundEffectItems(), + ).paddingAll(4) + ]), + // Message received + TableRow(children: [ + Text( + textAlign: TextAlign.right, + translate('settings_page.message_received')) + .paddingAll(8), + FormBuilderDropdown( + name: formFieldMessageReceivedMode, + isDense: false, + enabled: notificationsPreference.enableNotifications, + initialValue: notificationsPreference.onMessageReceivedMode, + onChanged: (value) async { + if (value == null) { + return; + } + final newNotificationsPreference = notificationsPreference + .copyWith(onMessageReceivedMode: value); + await updatePreferences(newNotificationsPreference); + }, + items: notificationModeItems(), + ).paddingAll(4), + FormBuilderDropdown( + name: formFieldMessageReceivedSound, + isDense: false, + enabled: notificationsPreference.enableNotifications, + initialValue: notificationsPreference.onMessageReceivedSound, + onChanged: (value) async { + if (value == null) { + return; + } + final newNotificationsPreference = notificationsPreference + .copyWith(onMessageReceivedSound: value); + await updatePreferences(newNotificationsPreference); + }, + items: soundEffectItems(), + ).paddingAll(4) + ]), + + // Message sent + TableRow(children: [ + Text( + textAlign: TextAlign.right, + translate('settings_page.message_sent')) + .paddingAll(8), + const SizedBox.shrink(), + FormBuilderDropdown( + name: formFieldMessageSentSound, + isDense: false, + enabled: notificationsPreference.enableNotifications, + initialValue: notificationsPreference.onMessageSentSound, + onChanged: (value) async { + if (value == null) { + return; + } + final newNotificationsPreference = notificationsPreference + .copyWith(onMessageSentSound: value); + await updatePreferences(newNotificationsPreference); + }, + items: soundEffectItems(), + ).paddingAll(4) + ]), + ]).paddingAll(8) + ]).paddingAll(8), + ); +} diff --git a/lib/notifications/views/views.dart b/lib/notifications/views/views.dart new file mode 100644 index 0000000..48a03b0 --- /dev/null +++ b/lib/notifications/views/views.dart @@ -0,0 +1,2 @@ +export 'notifications_preferences.dart'; +export 'notifications_widget.dart'; diff --git a/lib/settings/models/preferences.dart b/lib/settings/models/preferences.dart index 8dfcb73..e646c61 100644 --- a/lib/settings/models/preferences.dart +++ b/lib/settings/models/preferences.dart @@ -1,6 +1,7 @@ import 'package:change_case/change_case.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; +import '../../notifications/notifications.dart'; import '../../theme/theme.dart'; part 'preferences.freezed.dart'; @@ -11,19 +12,15 @@ part 'preferences.g.dart'; @freezed class LockPreference with _$LockPreference { const factory LockPreference({ - required int inactivityLockSecs, - required bool lockWhenSwitching, - required bool lockWithSystemLock, + @Default(0) int inactivityLockSecs, + @Default(false) bool lockWhenSwitching, + @Default(false) bool lockWithSystemLock, }) = _LockPreference; factory LockPreference.fromJson(dynamic json) => _$LockPreferenceFromJson(json as Map); - static const LockPreference defaults = LockPreference( - inactivityLockSecs: 0, - lockWhenSwitching: false, - lockWithSystemLock: false, - ); + static const LockPreference defaults = LockPreference(); } // Theme supports multiple translations @@ -42,16 +39,15 @@ enum LanguagePreference { @freezed class Preferences with _$Preferences { const factory Preferences({ - required ThemePreferences themePreferences, - required LanguagePreference language, - required LockPreference locking, + @Default(ThemePreferences.defaults) ThemePreferences themePreference, + @Default(LanguagePreference.defaults) LanguagePreference languagePreference, + @Default(LockPreference.defaults) LockPreference lockPreference, + @Default(NotificationsPreference.defaults) + NotificationsPreference notificationsPreference, }) = _Preferences; factory Preferences.fromJson(dynamic json) => _$PreferencesFromJson(json as Map); - static const Preferences defaults = Preferences( - themePreferences: ThemePreferences.defaults, - language: LanguagePreference.defaults, - locking: LockPreference.defaults); + static const Preferences defaults = Preferences(); } diff --git a/lib/settings/models/preferences.freezed.dart b/lib/settings/models/preferences.freezed.dart index e9667c8..1735d45 100644 --- a/lib/settings/models/preferences.freezed.dart +++ b/lib/settings/models/preferences.freezed.dart @@ -126,18 +126,21 @@ class __$$LockPreferenceImplCopyWithImpl<$Res> @JsonSerializable() class _$LockPreferenceImpl implements _LockPreference { const _$LockPreferenceImpl( - {required this.inactivityLockSecs, - required this.lockWhenSwitching, - required this.lockWithSystemLock}); + {this.inactivityLockSecs = 0, + this.lockWhenSwitching = false, + this.lockWithSystemLock = false}); factory _$LockPreferenceImpl.fromJson(Map json) => _$$LockPreferenceImplFromJson(json); @override + @JsonKey() final int inactivityLockSecs; @override + @JsonKey() final bool lockWhenSwitching; @override + @JsonKey() final bool lockWithSystemLock; @override @@ -180,9 +183,9 @@ class _$LockPreferenceImpl implements _LockPreference { abstract class _LockPreference implements LockPreference { const factory _LockPreference( - {required final int inactivityLockSecs, - required final bool lockWhenSwitching, - required final bool lockWithSystemLock}) = _$LockPreferenceImpl; + {final int inactivityLockSecs, + final bool lockWhenSwitching, + final bool lockWithSystemLock}) = _$LockPreferenceImpl; factory _LockPreference.fromJson(Map json) = _$LockPreferenceImpl.fromJson; @@ -205,9 +208,12 @@ Preferences _$PreferencesFromJson(Map json) { /// @nodoc mixin _$Preferences { - ThemePreferences get themePreferences => throw _privateConstructorUsedError; - LanguagePreference get language => throw _privateConstructorUsedError; - LockPreference get locking => throw _privateConstructorUsedError; + ThemePreferences get themePreference => throw _privateConstructorUsedError; + LanguagePreference get languagePreference => + throw _privateConstructorUsedError; + LockPreference get lockPreference => throw _privateConstructorUsedError; + NotificationsPreference get notificationsPreference => + throw _privateConstructorUsedError; Map toJson() => throw _privateConstructorUsedError; @JsonKey(ignore: true) @@ -222,12 +228,14 @@ abstract class $PreferencesCopyWith<$Res> { _$PreferencesCopyWithImpl<$Res, Preferences>; @useResult $Res call( - {ThemePreferences themePreferences, - LanguagePreference language, - LockPreference locking}); + {ThemePreferences themePreference, + LanguagePreference languagePreference, + LockPreference lockPreference, + NotificationsPreference notificationsPreference}); - $ThemePreferencesCopyWith<$Res> get themePreferences; - $LockPreferenceCopyWith<$Res> get locking; + $ThemePreferencesCopyWith<$Res> get themePreference; + $LockPreferenceCopyWith<$Res> get lockPreference; + $NotificationsPreferenceCopyWith<$Res> get notificationsPreference; } /// @nodoc @@ -243,39 +251,53 @@ class _$PreferencesCopyWithImpl<$Res, $Val extends Preferences> @pragma('vm:prefer-inline') @override $Res call({ - Object? themePreferences = null, - Object? language = null, - Object? locking = null, + Object? themePreference = null, + Object? languagePreference = null, + Object? lockPreference = null, + Object? notificationsPreference = null, }) { return _then(_value.copyWith( - themePreferences: null == themePreferences - ? _value.themePreferences - : themePreferences // ignore: cast_nullable_to_non_nullable + themePreference: null == themePreference + ? _value.themePreference + : themePreference // ignore: cast_nullable_to_non_nullable as ThemePreferences, - language: null == language - ? _value.language - : language // ignore: cast_nullable_to_non_nullable + languagePreference: null == languagePreference + ? _value.languagePreference + : languagePreference // ignore: cast_nullable_to_non_nullable as LanguagePreference, - locking: null == locking - ? _value.locking - : locking // ignore: cast_nullable_to_non_nullable + lockPreference: null == lockPreference + ? _value.lockPreference + : lockPreference // ignore: cast_nullable_to_non_nullable as LockPreference, + notificationsPreference: null == notificationsPreference + ? _value.notificationsPreference + : notificationsPreference // ignore: cast_nullable_to_non_nullable + as NotificationsPreference, ) as $Val); } @override @pragma('vm:prefer-inline') - $ThemePreferencesCopyWith<$Res> get themePreferences { - return $ThemePreferencesCopyWith<$Res>(_value.themePreferences, (value) { - return _then(_value.copyWith(themePreferences: value) as $Val); + $ThemePreferencesCopyWith<$Res> get themePreference { + return $ThemePreferencesCopyWith<$Res>(_value.themePreference, (value) { + return _then(_value.copyWith(themePreference: value) as $Val); }); } @override @pragma('vm:prefer-inline') - $LockPreferenceCopyWith<$Res> get locking { - return $LockPreferenceCopyWith<$Res>(_value.locking, (value) { - return _then(_value.copyWith(locking: value) as $Val); + $LockPreferenceCopyWith<$Res> get lockPreference { + return $LockPreferenceCopyWith<$Res>(_value.lockPreference, (value) { + return _then(_value.copyWith(lockPreference: value) as $Val); + }); + } + + @override + @pragma('vm:prefer-inline') + $NotificationsPreferenceCopyWith<$Res> get notificationsPreference { + return $NotificationsPreferenceCopyWith<$Res>( + _value.notificationsPreference, (value) { + return _then(_value.copyWith(notificationsPreference: value) as $Val); }); } } @@ -289,14 +311,17 @@ abstract class _$$PreferencesImplCopyWith<$Res> @override @useResult $Res call( - {ThemePreferences themePreferences, - LanguagePreference language, - LockPreference locking}); + {ThemePreferences themePreference, + LanguagePreference languagePreference, + LockPreference lockPreference, + NotificationsPreference notificationsPreference}); @override - $ThemePreferencesCopyWith<$Res> get themePreferences; + $ThemePreferencesCopyWith<$Res> get themePreference; @override - $LockPreferenceCopyWith<$Res> get locking; + $LockPreferenceCopyWith<$Res> get lockPreference; + @override + $NotificationsPreferenceCopyWith<$Res> get notificationsPreference; } /// @nodoc @@ -310,23 +335,28 @@ class __$$PreferencesImplCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override $Res call({ - Object? themePreferences = null, - Object? language = null, - Object? locking = null, + Object? themePreference = null, + Object? languagePreference = null, + Object? lockPreference = null, + Object? notificationsPreference = null, }) { return _then(_$PreferencesImpl( - themePreferences: null == themePreferences - ? _value.themePreferences - : themePreferences // ignore: cast_nullable_to_non_nullable + themePreference: null == themePreference + ? _value.themePreference + : themePreference // ignore: cast_nullable_to_non_nullable as ThemePreferences, - language: null == language - ? _value.language - : language // ignore: cast_nullable_to_non_nullable + languagePreference: null == languagePreference + ? _value.languagePreference + : languagePreference // ignore: cast_nullable_to_non_nullable as LanguagePreference, - locking: null == locking - ? _value.locking - : locking // ignore: cast_nullable_to_non_nullable + lockPreference: null == lockPreference + ? _value.lockPreference + : lockPreference // ignore: cast_nullable_to_non_nullable as LockPreference, + notificationsPreference: null == notificationsPreference + ? _value.notificationsPreference + : notificationsPreference // ignore: cast_nullable_to_non_nullable + as NotificationsPreference, )); } } @@ -335,23 +365,30 @@ class __$$PreferencesImplCopyWithImpl<$Res> @JsonSerializable() class _$PreferencesImpl implements _Preferences { const _$PreferencesImpl( - {required this.themePreferences, - required this.language, - required this.locking}); + {this.themePreference = ThemePreferences.defaults, + this.languagePreference = LanguagePreference.defaults, + this.lockPreference = LockPreference.defaults, + this.notificationsPreference = NotificationsPreference.defaults}); factory _$PreferencesImpl.fromJson(Map json) => _$$PreferencesImplFromJson(json); @override - final ThemePreferences themePreferences; + @JsonKey() + final ThemePreferences themePreference; @override - final LanguagePreference language; + @JsonKey() + final LanguagePreference languagePreference; @override - final LockPreference locking; + @JsonKey() + final LockPreference lockPreference; + @override + @JsonKey() + final NotificationsPreference notificationsPreference; @override String toString() { - return 'Preferences(themePreferences: $themePreferences, language: $language, locking: $locking)'; + return 'Preferences(themePreference: $themePreference, languagePreference: $languagePreference, lockPreference: $lockPreference, notificationsPreference: $notificationsPreference)'; } @override @@ -359,17 +396,21 @@ class _$PreferencesImpl implements _Preferences { return identical(this, other) || (other.runtimeType == runtimeType && other is _$PreferencesImpl && - (identical(other.themePreferences, themePreferences) || - other.themePreferences == themePreferences) && - (identical(other.language, language) || - other.language == language) && - (identical(other.locking, locking) || other.locking == locking)); + (identical(other.themePreference, themePreference) || + other.themePreference == themePreference) && + (identical(other.languagePreference, languagePreference) || + other.languagePreference == languagePreference) && + (identical(other.lockPreference, lockPreference) || + other.lockPreference == lockPreference) && + (identical( + other.notificationsPreference, notificationsPreference) || + other.notificationsPreference == notificationsPreference)); } @JsonKey(ignore: true) @override - int get hashCode => - Object.hash(runtimeType, themePreferences, language, locking); + int get hashCode => Object.hash(runtimeType, themePreference, + languagePreference, lockPreference, notificationsPreference); @JsonKey(ignore: true) @override @@ -387,19 +428,23 @@ class _$PreferencesImpl implements _Preferences { abstract class _Preferences implements Preferences { const factory _Preferences( - {required final ThemePreferences themePreferences, - required final LanguagePreference language, - required final LockPreference locking}) = _$PreferencesImpl; + {final ThemePreferences themePreference, + final LanguagePreference languagePreference, + final LockPreference lockPreference, + final NotificationsPreference notificationsPreference}) = + _$PreferencesImpl; factory _Preferences.fromJson(Map json) = _$PreferencesImpl.fromJson; @override - ThemePreferences get themePreferences; + ThemePreferences get themePreference; @override - LanguagePreference get language; + LanguagePreference get languagePreference; @override - LockPreference get locking; + LockPreference get lockPreference; + @override + NotificationsPreference get notificationsPreference; @override @JsonKey(ignore: true) _$$PreferencesImplCopyWith<_$PreferencesImpl> get copyWith => diff --git a/lib/settings/models/preferences.g.dart b/lib/settings/models/preferences.g.dart index 23cd5bb..5813f67 100644 --- a/lib/settings/models/preferences.g.dart +++ b/lib/settings/models/preferences.g.dart @@ -8,9 +8,9 @@ part of 'preferences.dart'; _$LockPreferenceImpl _$$LockPreferenceImplFromJson(Map json) => _$LockPreferenceImpl( - inactivityLockSecs: (json['inactivity_lock_secs'] as num).toInt(), - lockWhenSwitching: json['lock_when_switching'] as bool, - lockWithSystemLock: json['lock_with_system_lock'] as bool, + inactivityLockSecs: (json['inactivity_lock_secs'] as num?)?.toInt() ?? 0, + lockWhenSwitching: json['lock_when_switching'] as bool? ?? false, + lockWithSystemLock: json['lock_with_system_lock'] as bool? ?? false, ); Map _$$LockPreferenceImplToJson( @@ -23,14 +23,24 @@ Map _$$LockPreferenceImplToJson( _$PreferencesImpl _$$PreferencesImplFromJson(Map json) => _$PreferencesImpl( - themePreferences: ThemePreferences.fromJson(json['theme_preferences']), - language: LanguagePreference.fromJson(json['language']), - locking: LockPreference.fromJson(json['locking']), + themePreference: json['theme_preference'] == null + ? ThemePreferences.defaults + : ThemePreferences.fromJson(json['theme_preference']), + languagePreference: json['language_preference'] == null + ? LanguagePreference.defaults + : LanguagePreference.fromJson(json['language_preference']), + lockPreference: json['lock_preference'] == null + ? LockPreference.defaults + : LockPreference.fromJson(json['lock_preference']), + notificationsPreference: json['notifications_preference'] == null + ? NotificationsPreference.defaults + : NotificationsPreference.fromJson(json['notifications_preference']), ); Map _$$PreferencesImplToJson(_$PreferencesImpl instance) => { - 'theme_preferences': instance.themePreferences.toJson(), - 'language': instance.language.toJson(), - 'locking': instance.locking.toJson(), + 'theme_preference': instance.themePreference.toJson(), + 'language_preference': instance.languagePreference.toJson(), + 'lock_preference': instance.lockPreference.toJson(), + 'notifications_preference': instance.notificationsPreference.toJson(), }; diff --git a/lib/settings/settings_page.dart b/lib/settings/settings_page.dart index 5b92643..ac21fc4 100644 --- a/lib/settings/settings_page.dart +++ b/lib/settings/settings_page.dart @@ -6,6 +6,7 @@ import 'package:flutter_translate/flutter_translate.dart'; import 'package:go_router/go_router.dart'; import '../layout/default_app_bar.dart'; +import '../notifications/notifications.dart'; import '../theme/theme.dart'; import '../veilid_processor/veilid_processor.dart'; import 'settings.dart'; @@ -49,6 +50,8 @@ class SettingsPageState extends State { context: context, onChanged: () => setState(() {})), buildSettingsPageBrightnessPreferences( context: context, onChanged: () => setState(() {})), + buildSettingsPageNotificationPreferences( + context: context, onChanged: () => setState(() {})), ].map((x) => x.paddingLTRB(0, 0, 0, 8)).toList(), ), ).paddingSymmetric(horizontal: 24, vertical: 16), diff --git a/lib/theme/models/theme_preference.dart b/lib/theme/models/theme_preference.dart index 0accd8f..cfa05c9 100644 --- a/lib/theme/models/theme_preference.dart +++ b/lib/theme/models/theme_preference.dart @@ -49,19 +49,16 @@ enum ColorPreference { @freezed class ThemePreferences with _$ThemePreferences { const factory ThemePreferences({ - required BrightnessPreference brightnessPreference, - required ColorPreference colorPreference, - required double displayScale, + @Default(BrightnessPreference.system) + BrightnessPreference brightnessPreference, + @Default(ColorPreference.vapor) ColorPreference colorPreference, + @Default(1) double displayScale, }) = _ThemePreferences; factory ThemePreferences.fromJson(dynamic json) => _$ThemePreferencesFromJson(json as Map); - static const ThemePreferences defaults = ThemePreferences( - colorPreference: ColorPreference.vapor, - brightnessPreference: BrightnessPreference.system, - displayScale: 1, - ); + static const ThemePreferences defaults = ThemePreferences(); } extension ThemePreferencesExt on ThemePreferences { diff --git a/lib/theme/models/theme_preference.freezed.dart b/lib/theme/models/theme_preference.freezed.dart index 9f10955..97e3f81 100644 --- a/lib/theme/models/theme_preference.freezed.dart +++ b/lib/theme/models/theme_preference.freezed.dart @@ -127,18 +127,21 @@ class __$$ThemePreferencesImplCopyWithImpl<$Res> @JsonSerializable() class _$ThemePreferencesImpl implements _ThemePreferences { const _$ThemePreferencesImpl( - {required this.brightnessPreference, - required this.colorPreference, - required this.displayScale}); + {this.brightnessPreference = BrightnessPreference.system, + this.colorPreference = ColorPreference.vapor, + this.displayScale = 1}); factory _$ThemePreferencesImpl.fromJson(Map json) => _$$ThemePreferencesImplFromJson(json); @override + @JsonKey() final BrightnessPreference brightnessPreference; @override + @JsonKey() final ColorPreference colorPreference; @override + @JsonKey() final double displayScale; @override @@ -181,9 +184,9 @@ class _$ThemePreferencesImpl implements _ThemePreferences { abstract class _ThemePreferences implements ThemePreferences { const factory _ThemePreferences( - {required final BrightnessPreference brightnessPreference, - required final ColorPreference colorPreference, - required final double displayScale}) = _$ThemePreferencesImpl; + {final BrightnessPreference brightnessPreference, + final ColorPreference colorPreference, + final double displayScale}) = _$ThemePreferencesImpl; factory _ThemePreferences.fromJson(Map json) = _$ThemePreferencesImpl.fromJson; diff --git a/lib/theme/models/theme_preference.g.dart b/lib/theme/models/theme_preference.g.dart index 6f33c43..4cb2d71 100644 --- a/lib/theme/models/theme_preference.g.dart +++ b/lib/theme/models/theme_preference.g.dart @@ -9,10 +9,13 @@ part of 'theme_preference.dart'; _$ThemePreferencesImpl _$$ThemePreferencesImplFromJson( Map json) => _$ThemePreferencesImpl( - brightnessPreference: - BrightnessPreference.fromJson(json['brightness_preference']), - colorPreference: ColorPreference.fromJson(json['color_preference']), - displayScale: (json['display_scale'] as num).toDouble(), + brightnessPreference: json['brightness_preference'] == null + ? BrightnessPreference.system + : BrightnessPreference.fromJson(json['brightness_preference']), + colorPreference: json['color_preference'] == null + ? ColorPreference.vapor + : ColorPreference.fromJson(json['color_preference']), + displayScale: (json['display_scale'] as num?)?.toDouble() ?? 1, ); Map _$$ThemePreferencesImplToJson( diff --git a/lib/theme/views/brightness_preferences.dart b/lib/theme/views/brightness_preferences.dart index 2f3f410..0c39976 100644 --- a/lib/theme/views/brightness_preferences.dart +++ b/lib/theme/views/brightness_preferences.dart @@ -24,7 +24,7 @@ List> _getBrightnessDropdownItems() { Widget buildSettingsPageBrightnessPreferences( {required BuildContext context, required void Function() onChanged}) { final preferencesRepository = PreferencesRepository.instance; - final themePreferences = preferencesRepository.value.themePreferences; + final themePreferences = preferencesRepository.value.themePreference; return ThemeSwitcher.withTheme( builder: (_, switcher, theme) => FormBuilderDropdown( name: formFieldBrightness, @@ -36,7 +36,7 @@ Widget buildSettingsPageBrightnessPreferences( final newThemePrefs = themePreferences.copyWith( brightnessPreference: value as BrightnessPreference); final newPrefs = preferencesRepository.value - .copyWith(themePreferences: newThemePrefs); + .copyWith(themePreference: newThemePrefs); await preferencesRepository.set(newPrefs); switcher.changeTheme(theme: newThemePrefs.themeData()); diff --git a/lib/theme/views/color_preferences.dart b/lib/theme/views/color_preferences.dart index 228c2fb..ce03c0a 100644 --- a/lib/theme/views/color_preferences.dart +++ b/lib/theme/views/color_preferences.dart @@ -34,7 +34,7 @@ List> _getThemeDropdownItems() { Widget buildSettingsPageColorPreferences( {required BuildContext context, required void Function() onChanged}) { final preferencesRepository = PreferencesRepository.instance; - final themePreferences = preferencesRepository.value.themePreferences; + final themePreferences = preferencesRepository.value.themePreference; return ThemeSwitcher.withTheme( builder: (_, switcher, theme) => FormBuilderDropdown( name: formFieldTheme, @@ -46,7 +46,7 @@ Widget buildSettingsPageColorPreferences( final newThemePrefs = themePreferences.copyWith( colorPreference: value as ColorPreference); final newPrefs = preferencesRepository.value - .copyWith(themePreferences: newThemePrefs); + .copyWith(themePreference: newThemePrefs); await preferencesRepository.set(newPrefs); switcher.changeTheme(theme: newThemePrefs.themeData()); diff --git a/lib/theme/views/widget_helpers.dart b/lib/theme/views/widget_helpers.dart index 91ae11a..4beb48b 100644 --- a/lib/theme/views/widget_helpers.dart +++ b/lib/theme/views/widget_helpers.dart @@ -19,6 +19,14 @@ extension BorderExt on Widget { child: this); } +extension SizeToFixExt on Widget { + FittedBox fit({BoxFit? fit, Key? key}) => FittedBox( + key: key, + fit: fit ?? BoxFit.scaleDown, + child: this, + ); +} + extension ModalProgressExt on Widget { BlurryModalProgressHUD withModalHUD(BuildContext context, bool isLoading) { final theme = Theme.of(context); diff --git a/pubspec.yaml b/pubspec.yaml index edfa7b0..fea8acb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -160,6 +160,11 @@ flutter: - assets/images/ellet.png # Printing - assets/js/pdf/3.2.146/pdf.min.js + # Sounds + - assets/sounds/bonk.wav + - assets/sounds/boop.wav + - assets/sounds/badeep.wav + - assets/sounds/beepbadeep.wav # Fonts fonts: - family: Source Code Pro