diff --git a/lib/config/hive_config.dart b/lib/config/hive_config.dart index 93bce6ee..b6ba018c 100644 --- a/lib/config/hive_config.dart +++ b/lib/config/hive_config.dart @@ -63,6 +63,9 @@ class BNames { /// A boolean field of [appSettingsBox] box. static String isDarkModeOn = 'isDarkModeOn'; + /// A boolean field of [appSettingsBox] box. + static String isAutoDarkModeOn = 'isAutoDarkModeOn'; + /// A boolean field of [appSettingsBox] box. static String isOnboardingShowing = 'isOnboardingShowing'; diff --git a/lib/logic/cubit/app_settings/app_settings_cubit.dart b/lib/logic/cubit/app_settings/app_settings_cubit.dart index d013d418..af1ab3e0 100644 --- a/lib/logic/cubit/app_settings/app_settings_cubit.dart +++ b/lib/logic/cubit/app_settings/app_settings_cubit.dart @@ -15,10 +15,12 @@ part 'app_settings_state.dart'; class AppSettingsCubit extends Cubit { AppSettingsCubit({ required final bool isDarkModeOn, + required final bool isAutoDarkModeOn, required final bool isOnboardingShowing, }) : super( AppSettingsState( isDarkModeOn: isDarkModeOn, + isAutoDarkModeOn: isAutoDarkModeOn, isOnboardingShowing: isOnboardingShowing, ), ); @@ -27,10 +29,12 @@ class AppSettingsCubit extends Cubit { void load() async { final bool? isDarkModeOn = box.get(BNames.isDarkModeOn); + final bool? isAutoDarkModeOn = box.get(BNames.isAutoDarkModeOn); final bool? isOnboardingShowing = box.get(BNames.isOnboardingShowing); emit( state.copyWith( isDarkModeOn: isDarkModeOn, + isAutoDarkModeOn: isAutoDarkModeOn, isOnboardingShowing: isOnboardingShowing, ), ); @@ -49,9 +53,14 @@ class AppSettingsCubit extends Cubit { emit(state.copyWith(isDarkModeOn: isDarkModeOn)); } - void turnOffOnboarding() { - box.put(BNames.isOnboardingShowing, false); + void updateAutoDarkMode({required final bool isAutoDarkModeOn}) { + box.put(BNames.isAutoDarkModeOn, isAutoDarkModeOn); + emit(state.copyWith(isAutoDarkModeOn: isAutoDarkModeOn)); + } - emit(state.copyWith(isOnboardingShowing: false)); + void turnOffOnboarding({final bool isOnboardingShowing = false}) { + box.put(BNames.isOnboardingShowing, isOnboardingShowing); + + emit(state.copyWith(isOnboardingShowing: isOnboardingShowing)); } } diff --git a/lib/logic/cubit/app_settings/app_settings_state.dart b/lib/logic/cubit/app_settings/app_settings_state.dart index 8b29f6e9..ad364d66 100644 --- a/lib/logic/cubit/app_settings/app_settings_state.dart +++ b/lib/logic/cubit/app_settings/app_settings_state.dart @@ -3,21 +3,25 @@ part of 'app_settings_cubit.dart'; class AppSettingsState extends Equatable { const AppSettingsState({ required this.isDarkModeOn, + required this.isAutoDarkModeOn, required this.isOnboardingShowing, this.corePalette, }); final bool isDarkModeOn; + final bool isAutoDarkModeOn; final bool isOnboardingShowing; final color_utils.CorePalette? corePalette; AppSettingsState copyWith({ final bool? isDarkModeOn, + final bool? isAutoDarkModeOn, final bool? isOnboardingShowing, final color_utils.CorePalette? corePalette, }) => AppSettingsState( isDarkModeOn: isDarkModeOn ?? this.isDarkModeOn, + isAutoDarkModeOn: isAutoDarkModeOn ?? this.isAutoDarkModeOn, isOnboardingShowing: isOnboardingShowing ?? this.isOnboardingShowing, corePalette: corePalette ?? this.corePalette, ); @@ -26,5 +30,6 @@ class AppSettingsState extends Equatable { corePalette ?? color_utils.CorePalette.of(BrandColors.primary.value); @override - List get props => [isDarkModeOn, isOnboardingShowing, corePalette]; + List get props => + [isDarkModeOn, isAutoDarkModeOn, isOnboardingShowing, corePalette]; } diff --git a/lib/main.dart b/lib/main.dart index 3bab9f0d..cde6d45c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -87,11 +87,11 @@ class MyApp extends StatelessWidget { title: 'SelfPrivacy', theme: lightThemeData, darkTheme: darkThemeData, - themeMode: - appSettings.isDarkModeOn ? ThemeMode.dark : ThemeMode.light, - home: appSettings.isOnboardingShowing - ? const OnboardingPage(nextPage: InitializingPage()) - : const RootPage(), + themeMode: appSettings.isAutoDarkModeOn + ? ThemeMode.system + : appSettings.isDarkModeOn + ? ThemeMode.dark + : ThemeMode.light, builder: (final BuildContext context, final Widget? widget) { Widget error = const Text('...rendering error...'); if (widget is Scaffold || widget is Navigator) { diff --git a/lib/ui/pages/more/app_settings/app_setting.dart b/lib/ui/pages/more/app_settings/app_setting.dart deleted file mode 100644 index 4287e985..00000000 --- a/lib/ui/pages/more/app_settings/app_setting.dart +++ /dev/null @@ -1,229 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:selfprivacy/config/brand_colors.dart'; -import 'package:selfprivacy/config/brand_theme.dart'; -import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; -import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; -import 'package:selfprivacy/ui/components/action_button/action_button.dart'; -import 'package:selfprivacy/ui/components/brand_alert/brand_alert.dart'; -import 'package:selfprivacy/ui/components/brand_header/brand_header.dart'; -import 'package:selfprivacy/ui/components/brand_switch/brand_switch.dart'; -import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; -import 'package:selfprivacy/utils/named_font_weight.dart'; -import 'package:easy_localization/easy_localization.dart'; - -class AppSettingsPage extends StatefulWidget { - const AppSettingsPage({super.key}); - - @override - State createState() => _AppSettingsPageState(); -} - -class _AppSettingsPageState extends State { - @override - Widget build(final BuildContext context) { - final bool isDarkModeOn = - context.watch().state.isDarkModeOn; - - return SafeArea( - child: Builder( - builder: (final context) => Scaffold( - appBar: PreferredSize( - preferredSize: const Size.fromHeight(52), - child: BrandHeader( - title: 'application_settings.title'.tr(), - hasBackButton: true, - ), - ), - body: ListView( - padding: paddingH15V0, - children: [ - const Divider(height: 1), - Container( - padding: const EdgeInsets.symmetric(vertical: 16), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: _TextColumn( - title: 'application_settings.dark_theme_title'.tr(), - value: - 'application_settings.dark_theme_description'.tr(), - hasWarning: false, - ), - ), - const SizedBox(width: 5), - BrandSwitch( - value: Theme.of(context).brightness == Brightness.dark, - onChanged: (final value) => context - .read() - .updateDarkMode(isDarkModeOn: !isDarkModeOn), - ), - ], - ), - ), - const Divider(height: 0), - Container( - padding: const EdgeInsets.symmetric(vertical: 16), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: _TextColumn( - title: 'application_settings.reset_config_title'.tr(), - value: 'application_settings.reset_config_description' - .tr(), - hasWarning: false, - ), - ), - const SizedBox(width: 5), - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: BrandColors.red1, - ), - child: Text( - 'basis.reset'.tr(), - style: const TextStyle( - color: BrandColors.white, - fontWeight: NamedFontWeight.demiBold, - ), - ), - onPressed: () { - showDialog( - context: context, - builder: (final _) => BrandAlert( - title: 'modals.are_you_sure'.tr(), - contentText: 'modals.purge_all_keys'.tr(), - actions: [ - ActionButton( - text: 'modals.purge_all_keys_confirm'.tr(), - isRed: true, - onPressed: () { - context - .read() - .clearAppConfig(); - Navigator.of(context).pop(); - }, - ), - ActionButton( - text: 'basis.cancel'.tr(), - ), - ], - ), - ); - }, - ), - ], - ), - ), - const Divider(height: 0), - _deleteServer(context) - ], - ), - ), - ), - ); - } - - Widget _deleteServer(final BuildContext context) { - final bool isDisabled = - context.watch().state.serverDetails == null; - return Container( - padding: const EdgeInsets.symmetric(vertical: 16), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: _TextColumn( - title: 'application_settings.delete_server_title'.tr(), - value: 'application_settings.delete_server_description'.tr(), - hasWarning: false, - ), - ), - const SizedBox(width: 5), - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: BrandColors.red1, - ), - onPressed: isDisabled - ? null - : () { - showDialog( - context: context, - builder: (final _) => BrandAlert( - title: 'modals.are_you_sure'.tr(), - contentText: 'modals.delete_server_volume'.tr(), - actions: [ - ActionButton( - text: 'modals.yes'.tr(), - isRed: true, - onPressed: () async { - showDialog( - context: context, - builder: (final context) => Container( - alignment: Alignment.center, - child: const CircularProgressIndicator(), - ), - ); - await context - .read() - .serverDelete(); - if (!mounted) { - return; - } - Navigator.of(context).pop(); - }, - ), - ActionButton( - text: 'basis.cancel'.tr(), - ), - ], - ), - ); - }, - child: Text( - 'basis.delete'.tr(), - style: const TextStyle( - color: BrandColors.white, - fontWeight: NamedFontWeight.demiBold, - ), - ), - ), - ], - ), - ); - } -} - -class _TextColumn extends StatelessWidget { - const _TextColumn({ - required this.title, - required this.value, - this.hasWarning = false, - }); - - final String title; - final String value; - final bool hasWarning; - @override - Widget build(final BuildContext context) => Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - BrandText.body1( - title, - style: TextStyle(color: hasWarning ? BrandColors.warning : null), - ), - const SizedBox(height: 5), - BrandText.body1( - value, - style: const TextStyle( - fontSize: 13, - height: 1.53, - color: BrandColors.gray1, - ).merge(TextStyle(color: hasWarning ? BrandColors.warning : null)), - ), - ], - ); -} diff --git a/lib/ui/pages/more/app_settings/app_settings.dart b/lib/ui/pages/more/app_settings/app_settings.dart new file mode 100644 index 00000000..08be5393 --- /dev/null +++ b/lib/ui/pages/more/app_settings/app_settings.dart @@ -0,0 +1,146 @@ +import 'package:flutter/material.dart'; +import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; +import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; +import 'package:selfprivacy/ui/components/action_button/action_button.dart'; +import 'package:selfprivacy/ui/components/brand_alert/brand_alert.dart'; +import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; +import 'package:easy_localization/easy_localization.dart'; + +class AppSettingsPage extends StatefulWidget { + const AppSettingsPage({super.key}); + + @override + State createState() => _AppSettingsPageState(); +} + +class _AppSettingsPageState extends State { + @override + Widget build(final BuildContext context) { + final bool isDarkModeOn = + context.watch().state.isDarkModeOn; + + final bool isSystemDarkModeOn = + context.watch().state.isAutoDarkModeOn; + + return BrandHeroScreen( + hasBackButton: true, + hasFlashButton: false, + bodyPadding: const EdgeInsets.symmetric(vertical: 16), + heroTitle: 'application_settings.title'.tr(), + children: [ + SwitchListTile( + title: Text('application_settings.system_dark_theme_title'.tr()), + subtitle: + Text('application_settings.system_dark_theme_description'.tr()), + value: isSystemDarkModeOn, + onChanged: (final value) => context + .read() + .updateAutoDarkMode(isAutoDarkModeOn: !isSystemDarkModeOn), + ), + SwitchListTile( + title: Text('application_settings.dark_theme_title'.tr()), + subtitle: Text('application_settings.dark_theme_description'.tr()), + value: Theme.of(context).brightness == Brightness.dark, + onChanged: isSystemDarkModeOn + ? null + : (final value) => context + .read() + .updateDarkMode(isDarkModeOn: !isDarkModeOn), + ), + const Divider(height: 0), + Padding( + padding: const EdgeInsets.all(16), + child: Text( + 'application_settings.dangerous_settings'.tr(), + style: Theme.of(context).textTheme.labelLarge!.copyWith( + color: Theme.of(context).colorScheme.error, + ), + ), + ), + const _ResetAppTile(), + // const Divider(height: 0), + _deleteServer(context) + ], + ); + } + + Widget _deleteServer(final BuildContext context) { + final bool isDisabled = + context.watch().state.serverDetails == null; + return ListTile( + title: Text('application_settings.delete_server_title'.tr()), + subtitle: Text('application_settings.delete_server_description'.tr()), + textColor: isDisabled + ? Theme.of(context).colorScheme.onBackground.withOpacity(0.5) + : Theme.of(context).colorScheme.onBackground, + onTap: isDisabled + ? null + : () { + showDialog( + context: context, + builder: (final _) => BrandAlert( + title: 'modals.are_you_sure'.tr(), + contentText: 'modals.delete_server_volume'.tr(), + actions: [ + ActionButton( + text: 'modals.yes'.tr(), + isRed: true, + onPressed: () async { + showDialog( + context: context, + builder: (final context) => Container( + alignment: Alignment.center, + child: const CircularProgressIndicator(), + ), + ); + await context + .read() + .serverDelete(); + if (!mounted) { + return; + } + Navigator.of(context).pop(); + }, + ), + ActionButton( + text: 'basis.cancel'.tr(), + ), + ], + ), + ); + }, + ); + } +} + +class _ResetAppTile extends StatelessWidget { + const _ResetAppTile(); + + @override + Widget build(final BuildContext context) => ListTile( + title: Text('application_settings.reset_config_title'.tr()), + subtitle: Text('application_settings.reset_config_description'.tr()), + onTap: () { + showDialog( + context: context, + builder: (final _) => BrandAlert( + title: 'modals.are_you_sure'.tr(), + contentText: 'modals.purge_all_keys'.tr(), + actions: [ + ActionButton( + text: 'modals.purge_all_keys_confirm'.tr(), + isRed: true, + onPressed: () { + context.read().clearAppConfig(); + Navigator.of(context).pop(); + }, + ), + ActionButton( + text: 'basis.cancel'.tr(), + ), + ], + ), + ); + }, + ); +}