diff --git a/lib/app.dart b/lib/app.dart index 5b529c7e..020fe05e 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -89,15 +89,17 @@ class _AppState extends State with WidgetsBindingObserver { Locale('de', ''), ]; + final settings = context.watch(); + if (widget.storageError) { return MaterialApp( localizationsDelegates: localizationsDelegates, debugShowCheckedModeBanner: false, supportedLocales: supportedLocales, title: 'twonly', - theme: lightTheme, - darkTheme: darkTheme, - themeMode: context.read().themeMode, + theme: getLightTheme(settings.primaryColor), + darkTheme: getDarkTheme(settings.primaryColor), + themeMode: settings.themeMode, home: const CriticalErrorView(), ); } @@ -108,9 +110,9 @@ class _AppState extends State with WidgetsBindingObserver { debugShowCheckedModeBanner: false, supportedLocales: supportedLocales, title: 'twonly', - theme: lightTheme, - darkTheme: darkTheme, - themeMode: context.read().themeMode, + theme: getLightTheme(settings.primaryColor), + darkTheme: getDarkTheme(settings.primaryColor), + themeMode: settings.themeMode, home: const RecoveryView(), ); } @@ -121,9 +123,9 @@ class _AppState extends State with WidgetsBindingObserver { debugShowCheckedModeBanner: false, supportedLocales: supportedLocales, title: 'twonly', - theme: lightTheme, - darkTheme: darkTheme, - themeMode: context.read().themeMode, + theme: getLightTheme(settings.primaryColor), + darkTheme: getDarkTheme(settings.primaryColor), + themeMode: settings.themeMode, ); }, ); diff --git a/lib/src/localization/generated/app_localizations.dart b/lib/src/localization/generated/app_localizations.dart index ada1cac6..38415396 100644 --- a/lib/src/localization/generated/app_localizations.dart +++ b/lib/src/localization/generated/app_localizations.dart @@ -4405,6 +4405,30 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Brightness'** String get brightness; + + /// No description provided for @settingsAppearancePrimaryColor. + /// + /// In en, this message translates to: + /// **'Primary Color'** + String get settingsAppearancePrimaryColor; + + /// No description provided for @themeSystemDefault. + /// + /// In en, this message translates to: + /// **'System default'** + String get themeSystemDefault; + + /// No description provided for @themeLight. + /// + /// In en, this message translates to: + /// **'Light'** + String get themeLight; + + /// No description provided for @themeDark. + /// + /// In en, this message translates to: + /// **'Dark'** + String get themeDark; } class _AppLocalizationsDelegate diff --git a/lib/src/localization/generated/app_localizations_de.dart b/lib/src/localization/generated/app_localizations_de.dart index 4cc7472e..d66b20ef 100644 --- a/lib/src/localization/generated/app_localizations_de.dart +++ b/lib/src/localization/generated/app_localizations_de.dart @@ -2553,4 +2553,16 @@ class AppLocalizationsDe extends AppLocalizations { @override String get brightness => 'Helligkeit'; + + @override + String get settingsAppearancePrimaryColor => 'Hauptfarbe'; + + @override + String get themeSystemDefault => 'Systemstandard'; + + @override + String get themeLight => 'Hell'; + + @override + String get themeDark => 'Dunkel'; } diff --git a/lib/src/localization/generated/app_localizations_en.dart b/lib/src/localization/generated/app_localizations_en.dart index 516a7d10..35d76662 100644 --- a/lib/src/localization/generated/app_localizations_en.dart +++ b/lib/src/localization/generated/app_localizations_en.dart @@ -2526,4 +2526,16 @@ class AppLocalizationsEn extends AppLocalizations { @override String get brightness => 'Brightness'; + + @override + String get settingsAppearancePrimaryColor => 'Primary Color'; + + @override + String get themeSystemDefault => 'System default'; + + @override + String get themeLight => 'Light'; + + @override + String get themeDark => 'Dark'; } diff --git a/lib/src/localization/translations b/lib/src/localization/translations index 2f37f6cd..5feb6f49 160000 --- a/lib/src/localization/translations +++ b/lib/src/localization/translations @@ -1 +1 @@ -Subproject commit 2f37f6cd8ebd07dbc113bab0d5816bd37fb4fca6 +Subproject commit 5feb6f49653741ec37c49e57b3b6c7c178ed4427 diff --git a/lib/src/model/json/userdata.model.dart b/lib/src/model/json/userdata.model.dart index e451447a..23aad0ba 100644 --- a/lib/src/model/json/userdata.model.dart +++ b/lib/src/model/json/userdata.model.dart @@ -57,6 +57,12 @@ class UserData { @JsonKey(defaultValue: ThemeMode.system) ThemeMode themeMode = ThemeMode.system; + int? primaryColorValue; + + Color get primaryColor => primaryColorValue != null + ? Color(primaryColorValue!) + : const Color(0xFF57CC99); + int? defaultShowTime; @JsonKey(defaultValue: false) diff --git a/lib/src/model/json/userdata.model.g.dart b/lib/src/model/json/userdata.model.g.dart index ccb08cc9..f2848958 100644 --- a/lib/src/model/json/userdata.model.g.dart +++ b/lib/src/model/json/userdata.model.g.dart @@ -33,6 +33,7 @@ UserData _$UserDataFromJson(Map json) => ..themeMode = $enumDecodeNullable(_$ThemeModeEnumMap, json['themeMode']) ?? ThemeMode.system + ..primaryColorValue = (json['primaryColorValue'] as num?)?.toInt() ..defaultShowTime = (json['defaultShowTime'] as num?)?.toInt() ..requestedAudioPermission = json['requestedAudioPermission'] as bool? ?? false @@ -137,6 +138,7 @@ Map _$UserDataToJson(UserData instance) => { 'lastPlanBallance': instance.lastPlanBallance, 'additionalUserInvites': instance.additionalUserInvites, 'themeMode': _$ThemeModeEnumMap[instance.themeMode]!, + 'primaryColorValue': instance.primaryColorValue, 'defaultShowTime': instance.defaultShowTime, 'requestedAudioPermission': instance.requestedAudioPermission, 'enableDatabaseLogging': instance.enableDatabaseLogging, diff --git a/lib/src/providers/settings.provider.dart b/lib/src/providers/settings.provider.dart index f2057b3a..546052db 100644 --- a/lib/src/providers/settings.provider.dart +++ b/lib/src/providers/settings.provider.dart @@ -2,18 +2,23 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:twonly/locator.dart'; import 'package:twonly/src/services/user.service.dart'; +import 'package:twonly/src/visual/themes/light.dart'; class SettingsChangeProvider with ChangeNotifier, DiagnosticableTreeMixin { late ThemeMode _themeMode; + late Color _primaryColor; ThemeMode get themeMode => _themeMode; + Color get primaryColor => _primaryColor; void loadSettings() { if (userService.isUserCreated) { _themeMode = userService.currentUser.themeMode; + _primaryColor = userService.currentUser.primaryColor; notifyListeners(); } else { _themeMode = ThemeMode.system; + _primaryColor = defaultPrimaryColor; } } @@ -28,4 +33,16 @@ class SettingsChangeProvider with ChangeNotifier, DiagnosticableTreeMixin { await UserService.update((u) => u.themeMode = newThemeMode); } + + Future updatePrimaryColor(Color newColor) async { + if (newColor.toARGB32() == _primaryColor.toARGB32()) return; + + _primaryColor = newColor; + + notifyListeners(); + + await UserService.update( + (u) => u.primaryColorValue = newColor.toARGB32(), + ); + } } diff --git a/lib/src/visual/components/contact_request_badge.comp.dart b/lib/src/visual/components/contact_request_badge.comp.dart index 4a08c5b1..b2d6a13f 100644 --- a/lib/src/visual/components/contact_request_badge.comp.dart +++ b/lib/src/visual/components/contact_request_badge.comp.dart @@ -5,7 +5,6 @@ import 'package:twonly/locator.dart'; import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/visual/components/notification_badge.comp.dart'; -import 'package:twonly/src/visual/themes/light.dart'; class ContactRequestBadgeComp extends StatelessWidget { const ContactRequestBadgeComp({super.key}); @@ -26,8 +25,8 @@ class ContactRequestBadgeComp extends StatelessWidget { child: Container( width: 40, height: 40, - decoration: const BoxDecoration( - color: primaryColor, + decoration: BoxDecoration( + color: context.color.primary, shape: BoxShape.circle, ), ), diff --git a/lib/src/visual/components/selectable_thumbnail.comp.dart b/lib/src/visual/components/selectable_thumbnail.comp.dart index 58c3c212..743c0ee1 100644 --- a/lib/src/visual/components/selectable_thumbnail.comp.dart +++ b/lib/src/visual/components/selectable_thumbnail.comp.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:twonly/src/visual/themes/light.dart'; +import 'package:twonly/src/utils/misc.dart'; class SelectableThumbnailComp extends StatelessWidget { const SelectableThumbnailComp({ @@ -19,7 +19,7 @@ class SelectableThumbnailComp extends StatelessWidget { duration: const Duration(milliseconds: 200), curve: Curves.easeInOut, decoration: BoxDecoration( - color: isSelected ? primaryColor : Colors.transparent, + color: isSelected ? context.color.primary : Colors.transparent, boxShadow: const [ BoxShadow( color: Colors.black12, @@ -49,7 +49,7 @@ class SelectableThumbnailComp extends StatelessWidget { child: Container( padding: const EdgeInsets.all(2), decoration: BoxDecoration( - color: isSelected ? primaryColor : Colors.black38, + color: isSelected ? context.color.primary : Colors.black38, shape: BoxShape.circle, border: Border.all( color: Theme.of(context).brightness == Brightness.dark diff --git a/lib/src/visual/components/verification_badge_info.comp.dart b/lib/src/visual/components/verification_badge_info.comp.dart index eb78ba4a..5dfa23b5 100644 --- a/lib/src/visual/components/verification_badge_info.comp.dart +++ b/lib/src/visual/components/verification_badge_info.comp.dart @@ -9,7 +9,6 @@ import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/visual/components/profile_qr_code.comp.dart'; import 'package:twonly/src/visual/elements/my_button.element.dart'; import 'package:twonly/src/visual/elements/svg_icon.element.dart'; -import 'package:twonly/src/visual/themes/light.dart'; const colorVerificationBadgeYellow = Color.fromARGB(255, 0, 182, 238); @@ -90,7 +89,7 @@ class VerificationBadgeInfo extends StatelessWidget { context, icon: const SvgIcon(assetPath: SvgIcons.verifiedGreen, size: 40), description: context.lang.verificationBadgeGreenDesc, - boldTextColor: primaryColor, + boldTextColor: context.color.primary, onTap: () => context.push( Routes.cameraQRScanner, extra: { diff --git a/lib/src/visual/components/verification_success_animation.comp.dart b/lib/src/visual/components/verification_success_animation.comp.dart index beebc1c5..a7c0c421 100644 --- a/lib/src/visual/components/verification_success_animation.comp.dart +++ b/lib/src/visual/components/verification_success_animation.comp.dart @@ -86,7 +86,7 @@ class VerificationSuccessAnimationState ''; static const _grey = Color(0xFF8E9AAF); - static const Color _green = primaryColor; + static const Color _green = defaultPrimaryColor; @override void initState() { diff --git a/lib/src/visual/elements/my_button.element.dart b/lib/src/visual/elements/my_button.element.dart index c3038416..87adfe89 100644 --- a/lib/src/visual/elements/my_button.element.dart +++ b/lib/src/visual/elements/my_button.element.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/visual/elements/reactive_tap_feedback.element.dart'; -import 'package:twonly/src/visual/themes/light.dart'; enum MyButtonVariant { primary, @@ -49,7 +48,7 @@ class _MyButtonState extends State { switch (widget.variant) { case MyButtonVariant.primary: buttonStyle = FilledButton.styleFrom( - backgroundColor: primaryColor, + backgroundColor: context.color.primary, foregroundColor: Colors.black87, disabledBackgroundColor: disabledBgColor, disabledForegroundColor: disabledFgColor, @@ -96,7 +95,7 @@ class _MyButtonState extends State { ); case MyButtonVariant.primaryMiddle: buttonStyle = FilledButton.styleFrom( - backgroundColor: primaryColor, + backgroundColor: context.color.primary, foregroundColor: Colors.black87, disabledBackgroundColor: disabledBgColor, disabledForegroundColor: disabledFgColor, @@ -115,7 +114,7 @@ class _MyButtonState extends State { ); case MyButtonVariant.primaryDense: buttonStyle = FilledButton.styleFrom( - backgroundColor: primaryColor, + backgroundColor: context.color.primary, foregroundColor: Colors.black87, disabledBackgroundColor: disabledBgColor, disabledForegroundColor: disabledFgColor, diff --git a/lib/src/visual/elements/my_icon_button.element.dart b/lib/src/visual/elements/my_icon_button.element.dart index f982240b..9717722d 100644 --- a/lib/src/visual/elements/my_icon_button.element.dart +++ b/lib/src/visual/elements/my_icon_button.element.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/visual/elements/reactive_tap_feedback.element.dart'; -import 'package:twonly/src/visual/themes/light.dart'; enum MyIconButtonVariant { primary, @@ -43,7 +42,7 @@ class _MyIconButtonState extends State { late final Color fgColor; if (widget.variant == MyIconButtonVariant.primary) { - bgColor = primaryColor; + bgColor = context.color.primary; fgColor = Colors.black87; } else { bgColor = isDark ? Colors.grey[800]! : Colors.grey[200]!; diff --git a/lib/src/visual/themes/dark.dart b/lib/src/visual/themes/dark.dart index 775b2c09..62c5f3a4 100644 --- a/lib/src/visual/themes/dark.dart +++ b/lib/src/visual/themes/dark.dart @@ -1,11 +1,13 @@ import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:twonly/src/visual/themes/light.dart'; -final ThemeData darkTheme = () { +ThemeData getDarkTheme([Color primary = defaultPrimaryColor]) { final base = ThemeData.dark().copyWith( colorScheme: ColorScheme.fromSeed( brightness: Brightness.dark, - seedColor: const Color(0xFF57CC99), + seedColor: primary, + primary: primary, surface: const Color.fromARGB(255, 20, 18, 23), surfaceContainer: const Color.fromARGB(255, 45, 41, 54), surfaceContainerLow: const Color.fromARGB(255, 38, 34, 45), @@ -21,4 +23,6 @@ final ThemeData darkTheme = () { fontFamilyFallback: Platform.isAndroid ? const ['NotoColorEmoji'] : null, ), ); -}(); +} + +final ThemeData darkTheme = getDarkTheme(); diff --git a/lib/src/visual/themes/light.dart b/lib/src/visual/themes/light.dart index 7d59b5b5..8625e8f0 100644 --- a/lib/src/visual/themes/light.dart +++ b/lib/src/visual/themes/light.dart @@ -2,12 +2,13 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:twonly/src/utils/misc.dart'; -const primaryColor = Color(0xFF57CC99); +const defaultPrimaryColor = Color(0xFF57CC99); -final ThemeData lightTheme = () { +ThemeData getLightTheme([Color primary = defaultPrimaryColor]) { final base = ThemeData( colorScheme: ColorScheme.fromSeed( - seedColor: primaryColor, + seedColor: primary, + primary: primary, ), inputDecorationTheme: const InputDecorationTheme( border: OutlineInputBorder(), @@ -19,10 +20,12 @@ final ThemeData lightTheme = () { fontFamilyFallback: Platform.isAndroid ? const ['NotoColorEmoji'] : null, ), ); -}(); +} + +final ThemeData lightTheme = getLightTheme(); final ButtonStyle primaryColorButtonStyle = FilledButton.styleFrom( - backgroundColor: primaryColor, + backgroundColor: defaultPrimaryColor, foregroundColor: Colors.black87, padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16), shape: RoundedRectangleBorder( diff --git a/lib/src/visual/views/chats/chat_list.view.dart b/lib/src/visual/views/chats/chat_list.view.dart index cc8078fa..803c7907 100644 --- a/lib/src/visual/views/chats/chat_list.view.dart +++ b/lib/src/visual/views/chats/chat_list.view.dart @@ -15,7 +15,6 @@ import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/visual/components/avatar_icon.comp.dart'; import 'package:twonly/src/visual/components/connection_status.comp.dart'; import 'package:twonly/src/visual/components/notification_badge.comp.dart'; -import 'package:twonly/src/visual/themes/light.dart'; import 'package:twonly/src/visual/views/chats/chat_list_components/empty_chat_list.comp.dart'; import 'package:twonly/src/visual/views/chats/chat_list_components/group_list_item.comp.dart'; import 'package:twonly/src/visual/views/chats/chat_list_components/news_btn.comp.dart'; @@ -196,8 +195,8 @@ class _ChatListViewState extends State with AutomaticKeepAliveClie child: Container( width: 40, height: 40, - decoration: const BoxDecoration( - color: primaryColor, + decoration: BoxDecoration( + color: context.color.primary, shape: BoxShape.circle, ), ), @@ -329,7 +328,7 @@ class _ChatListViewState extends State with AutomaticKeepAliveClie FloatingActionButton( heroTag: 'new_chat_fab', elevation: 2, - backgroundColor: primaryColor, + backgroundColor: context.color.primary, foregroundColor: Colors.black87, onPressed: () => context.push(Routes.chatsStartNewChat), child: const FaIcon( diff --git a/lib/src/visual/views/chats/chat_list_components/news_btn.comp.dart b/lib/src/visual/views/chats/chat_list_components/news_btn.comp.dart index 05ba9752..1fda75e3 100644 --- a/lib/src/visual/views/chats/chat_list_components/news_btn.comp.dart +++ b/lib/src/visual/views/chats/chat_list_components/news_btn.comp.dart @@ -5,7 +5,6 @@ import 'package:twonly/locator.dart'; import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/visual/components/notification_badge.comp.dart'; -import 'package:twonly/src/visual/themes/light.dart'; class NewsIconButtonComp extends StatelessWidget { const NewsIconButtonComp({super.key}); @@ -23,7 +22,7 @@ class NewsIconButtonComp extends StatelessWidget { builder: (context, count, child) { return NotificationBadgeComp( count: count.toString(), - backgroundColor: primaryColor, + backgroundColor: context.color.primary, textColor: Colors.black87, child: IconButton( onPressed: () => context.push(Routes.settingsHelpNews), diff --git a/lib/src/visual/views/onboarding/setup/profile.setup.dart b/lib/src/visual/views/onboarding/setup/profile.setup.dart index d5802760..f781874c 100644 --- a/lib/src/visual/views/onboarding/setup/profile.setup.dart +++ b/lib/src/visual/views/onboarding/setup/profile.setup.dart @@ -8,7 +8,6 @@ import 'package:twonly/src/visual/components/avatar_icon.comp.dart' show AvatarIcon; import 'package:twonly/src/visual/elements/my_button.element.dart'; import 'package:twonly/src/visual/elements/my_input.element.dart'; -import 'package:twonly/src/visual/themes/light.dart'; import 'package:twonly/src/visual/views/onboarding/setup/components/next_button.comp.dart'; class ProfileSetupPage extends StatefulWidget { @@ -67,7 +66,7 @@ class _ProfileSetupPageState extends State { foregroundDecoration: BoxDecoration( shape: BoxShape.circle, border: Border.all( - color: primaryColor, + color: context.color.primary, width: 4, ), ), diff --git a/lib/src/visual/views/settings/appearance.view.dart b/lib/src/visual/views/settings/appearance.view.dart index 4caa5a0a..7bb271c7 100644 --- a/lib/src/visual/views/settings/appearance.view.dart +++ b/lib/src/visual/views/settings/appearance.view.dart @@ -6,6 +6,7 @@ import 'package:twonly/locator.dart'; import 'package:twonly/src/providers/settings.provider.dart'; import 'package:twonly/src/services/user.service.dart'; import 'package:twonly/src/utils/misc.dart'; +import 'package:twonly/src/visual/components/custom_color_picker_dialog.comp.dart'; import 'package:twonly/src/visual/elements/radio_button.element.dart'; class AppearanceView extends StatefulWidget { @@ -36,7 +37,7 @@ class _AppearanceViewState extends State { RadioButton( value: ThemeMode.system, groupValue: selectedValue, - label: 'System default', + label: context.lang.themeSystemDefault, onChanged: (value) { selectedValue = value; Navigator.of(context).pop(); @@ -45,7 +46,7 @@ class _AppearanceViewState extends State { RadioButton( value: ThemeMode.light, groupValue: selectedValue, - label: 'Light', + label: context.lang.themeLight, onChanged: (value) { selectedValue = value; Navigator.of(context).pop(); @@ -54,7 +55,7 @@ class _AppearanceViewState extends State { RadioButton( value: ThemeMode.dark, groupValue: selectedValue, - label: 'Dark', + label: context.lang.themeDark, onChanged: (value) { selectedValue = value; Navigator.of(context).pop(); @@ -72,6 +73,124 @@ class _AppearanceViewState extends State { } } + Future _showSelectPrimaryColor(BuildContext context) async { + const presetColors = [ + Color(0xFF57CC99), // Original Twonly Green + Color(0xFF3A76F0), // Signal Blue + Color(0xFF9D4EDD), // Purple + Color(0xFFFF5964), // Coral Red + Color(0xFFFF9F1C), // Amber Orange + ]; + + await showModalBottomSheet( + context: context, + showDragHandle: true, + builder: (context) { + final activeColor = + context.watch().primaryColor; + return Padding( + padding: const EdgeInsets.fromLTRB(24, 8, 24, 32), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + context.lang.settingsAppearancePrimaryColor, + style: Theme.of(context).textTheme.titleLarge?.copyWith( + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: presetColors.map((color) { + final isSelected = + activeColor.toARGB32() == color.toARGB32(); + return GestureDetector( + onTap: () async { + await context + .read() + .updatePrimaryColor(color); + }, + child: Container( + width: 48, + height: 48, + decoration: BoxDecoration( + color: color, + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: color.withValues(alpha: 0.4), + blurRadius: 8, + offset: const Offset(0, 3), + ), + ], + border: Border.all( + color: + isSelected ? Colors.white : Colors.transparent, + width: 3, + ), + ), + child: isSelected + ? const Icon( + Icons.check, + color: Colors.white, + size: 24, + ) + : null, + ), + ); + }).toList(), + ), + const SizedBox(height: 24), + ListTile( + contentPadding: EdgeInsets.zero, + leading: Container( + width: 40, + height: 40, + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: const SweepGradient( + colors: [ + Colors.red, + Colors.yellow, + Colors.green, + Colors.cyan, + Colors.blue, + Color(0xFFFF00FF), + Colors.red, + ], + ), + border: Border.all(color: Colors.white24, width: 2), + ), + ), + title: Text(context.lang.customColor), + trailing: const Icon(Icons.chevron_right), + onTap: () async { + final pickedValue = await showDialog( + context: context, + builder: (context) => CustomColorPickerDialog( + initialColor: activeColor, + ), + ); + if (pickedValue != null && context.mounted) { + final pickedColor = Color(pickedValue); + await context + .read() + .updatePrimaryColor(pickedColor); + if (context.mounted) { + Navigator.of(context).pop(); + } + } + }, + ), + ], + ), + ); + }, + ); + } + Future toggleShowNewsIcon() async { await UserService.update((u) { u.showNewsShortcut = !u.showNewsShortcut; @@ -90,9 +209,22 @@ class _AppearanceViewState extends State { }); } + String _themeModeLabel(BuildContext context, ThemeMode mode) { + switch (mode) { + case ThemeMode.system: + return context.lang.themeSystemDefault; + case ThemeMode.light: + return context.lang.themeLight; + case ThemeMode.dark: + return context.lang.themeDark; + } + } + @override Widget build(BuildContext context) { final selectedTheme = context.watch().themeMode; + final primaryColor = context.watch().primaryColor; + return Scaffold( appBar: AppBar( title: Text(context.lang.settingsAppearance), @@ -105,13 +237,28 @@ class _AppearanceViewState extends State { ListTile( title: Text(context.lang.settingsAppearanceTheme), subtitle: Text( - selectedTheme.name, + _themeModeLabel(context, selectedTheme), style: const TextStyle(color: Colors.grey), ), onTap: () async { await _showSelectThemeMode(context); }, ), + ListTile( + title: Text(context.lang.settingsAppearancePrimaryColor), + trailing: Container( + width: 24, + height: 24, + decoration: BoxDecoration( + color: primaryColor, + shape: BoxShape.circle, + border: Border.all(color: Colors.white, width: 1.5), + ), + ), + onTap: () async { + await _showSelectPrimaryColor(context); + }, + ), ListTile( title: Text(context.lang.hideNewsIcon), onTap: toggleShowNewsIcon, diff --git a/lib/src/visual/views/settings/backup/passwordless_recovery/components/second_factor_picker.comp.dart b/lib/src/visual/views/settings/backup/passwordless_recovery/components/second_factor_picker.comp.dart index 39b0582c..5836c59b 100644 --- a/lib/src/visual/views/settings/backup/passwordless_recovery/components/second_factor_picker.comp.dart +++ b/lib/src/visual/views/settings/backup/passwordless_recovery/components/second_factor_picker.comp.dart @@ -3,7 +3,6 @@ import 'package:flutter/services.dart'; import 'package:twonly/src/services/passwordless_recovery.service.dart'; import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/visual/elements/my_input.element.dart'; -import 'package:twonly/src/visual/themes/light.dart'; class _FactorOption { const _FactorOption({ @@ -113,7 +112,7 @@ class SecondFactorPicker extends StatelessWidget { child: Container( padding: const EdgeInsets.symmetric(vertical: 8), decoration: BoxDecoration( - color: isSelected ? primaryColor : Colors.transparent, + color: isSelected ? context.color.primary : Colors.transparent, borderRadius: borderRadius, ), child: Column( diff --git a/lib/src/visual/views/settings/backup/passwordless_recovery/components/threshold_picker.comp.dart b/lib/src/visual/views/settings/backup/passwordless_recovery/components/threshold_picker.comp.dart index 15993964..cf449f57 100644 --- a/lib/src/visual/views/settings/backup/passwordless_recovery/components/threshold_picker.comp.dart +++ b/lib/src/visual/views/settings/backup/passwordless_recovery/components/threshold_picker.comp.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:twonly/src/utils/misc.dart'; -import 'package:twonly/src/visual/themes/light.dart'; class ThresholdPicker extends StatelessWidget { const ThresholdPicker({ @@ -103,7 +102,7 @@ class ThresholdPicker extends StatelessWidget { child: Container( padding: const EdgeInsets.symmetric(vertical: 14), decoration: BoxDecoration( - color: isSelected ? primaryColor : Colors.transparent, + color: isSelected ? context.color.primary : Colors.transparent, borderRadius: borderRadius, ), child: Center( diff --git a/pubspec.yaml b/pubspec.yaml index 0e46b764..19785c7c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: "twonly, a privacy-friendly way to connect with friends through sec publish_to: 'none' -version: 0.4.3+164 +version: 0.4.3+165 environment: sdk: ^3.11.0