feat: Automatic day/night theme

pull/203/head
Inex Code 2023-02-23 17:41:45 +03:00 committed by Gitea
parent 54513998ce
commit befdc0286e
6 changed files with 172 additions and 238 deletions

View File

@ -63,6 +63,9 @@ class BNames {
/// A boolean field of [appSettingsBox] box. /// A boolean field of [appSettingsBox] box.
static String isDarkModeOn = 'isDarkModeOn'; static String isDarkModeOn = 'isDarkModeOn';
/// A boolean field of [appSettingsBox] box.
static String isAutoDarkModeOn = 'isAutoDarkModeOn';
/// A boolean field of [appSettingsBox] box. /// A boolean field of [appSettingsBox] box.
static String isOnboardingShowing = 'isOnboardingShowing'; static String isOnboardingShowing = 'isOnboardingShowing';

View File

@ -15,10 +15,12 @@ part 'app_settings_state.dart';
class AppSettingsCubit extends Cubit<AppSettingsState> { class AppSettingsCubit extends Cubit<AppSettingsState> {
AppSettingsCubit({ AppSettingsCubit({
required final bool isDarkModeOn, required final bool isDarkModeOn,
required final bool isAutoDarkModeOn,
required final bool isOnboardingShowing, required final bool isOnboardingShowing,
}) : super( }) : super(
AppSettingsState( AppSettingsState(
isDarkModeOn: isDarkModeOn, isDarkModeOn: isDarkModeOn,
isAutoDarkModeOn: isAutoDarkModeOn,
isOnboardingShowing: isOnboardingShowing, isOnboardingShowing: isOnboardingShowing,
), ),
); );
@ -27,10 +29,12 @@ class AppSettingsCubit extends Cubit<AppSettingsState> {
void load() async { void load() async {
final bool? isDarkModeOn = box.get(BNames.isDarkModeOn); final bool? isDarkModeOn = box.get(BNames.isDarkModeOn);
final bool? isAutoDarkModeOn = box.get(BNames.isAutoDarkModeOn);
final bool? isOnboardingShowing = box.get(BNames.isOnboardingShowing); final bool? isOnboardingShowing = box.get(BNames.isOnboardingShowing);
emit( emit(
state.copyWith( state.copyWith(
isDarkModeOn: isDarkModeOn, isDarkModeOn: isDarkModeOn,
isAutoDarkModeOn: isAutoDarkModeOn,
isOnboardingShowing: isOnboardingShowing, isOnboardingShowing: isOnboardingShowing,
), ),
); );
@ -49,9 +53,14 @@ class AppSettingsCubit extends Cubit<AppSettingsState> {
emit(state.copyWith(isDarkModeOn: isDarkModeOn)); emit(state.copyWith(isDarkModeOn: isDarkModeOn));
} }
void turnOffOnboarding() { void updateAutoDarkMode({required final bool isAutoDarkModeOn}) {
box.put(BNames.isOnboardingShowing, false); 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));
} }
} }

View File

@ -3,21 +3,25 @@ part of 'app_settings_cubit.dart';
class AppSettingsState extends Equatable { class AppSettingsState extends Equatable {
const AppSettingsState({ const AppSettingsState({
required this.isDarkModeOn, required this.isDarkModeOn,
required this.isAutoDarkModeOn,
required this.isOnboardingShowing, required this.isOnboardingShowing,
this.corePalette, this.corePalette,
}); });
final bool isDarkModeOn; final bool isDarkModeOn;
final bool isAutoDarkModeOn;
final bool isOnboardingShowing; final bool isOnboardingShowing;
final color_utils.CorePalette? corePalette; final color_utils.CorePalette? corePalette;
AppSettingsState copyWith({ AppSettingsState copyWith({
final bool? isDarkModeOn, final bool? isDarkModeOn,
final bool? isAutoDarkModeOn,
final bool? isOnboardingShowing, final bool? isOnboardingShowing,
final color_utils.CorePalette? corePalette, final color_utils.CorePalette? corePalette,
}) => }) =>
AppSettingsState( AppSettingsState(
isDarkModeOn: isDarkModeOn ?? this.isDarkModeOn, isDarkModeOn: isDarkModeOn ?? this.isDarkModeOn,
isAutoDarkModeOn: isAutoDarkModeOn ?? this.isAutoDarkModeOn,
isOnboardingShowing: isOnboardingShowing ?? this.isOnboardingShowing, isOnboardingShowing: isOnboardingShowing ?? this.isOnboardingShowing,
corePalette: corePalette ?? this.corePalette, corePalette: corePalette ?? this.corePalette,
); );
@ -26,5 +30,6 @@ class AppSettingsState extends Equatable {
corePalette ?? color_utils.CorePalette.of(BrandColors.primary.value); corePalette ?? color_utils.CorePalette.of(BrandColors.primary.value);
@override @override
List<dynamic> get props => [isDarkModeOn, isOnboardingShowing, corePalette]; List<dynamic> get props =>
[isDarkModeOn, isAutoDarkModeOn, isOnboardingShowing, corePalette];
} }

View File

@ -87,11 +87,11 @@ class MyApp extends StatelessWidget {
title: 'SelfPrivacy', title: 'SelfPrivacy',
theme: lightThemeData, theme: lightThemeData,
darkTheme: darkThemeData, darkTheme: darkThemeData,
themeMode: themeMode: appSettings.isAutoDarkModeOn
appSettings.isDarkModeOn ? ThemeMode.dark : ThemeMode.light, ? ThemeMode.system
home: appSettings.isOnboardingShowing : appSettings.isDarkModeOn
? const OnboardingPage(nextPage: InitializingPage()) ? ThemeMode.dark
: const RootPage(), : ThemeMode.light,
builder: (final BuildContext context, final Widget? widget) { builder: (final BuildContext context, final Widget? widget) {
Widget error = const Text('...rendering error...'); Widget error = const Text('...rendering error...');
if (widget is Scaffold || widget is Navigator) { if (widget is Scaffold || widget is Navigator) {

View File

@ -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<AppSettingsPage> createState() => _AppSettingsPageState();
}
class _AppSettingsPageState extends State<AppSettingsPage> {
@override
Widget build(final BuildContext context) {
final bool isDarkModeOn =
context.watch<AppSettingsCubit>().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<AppSettingsCubit>()
.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<ServerInstallationCubit>()
.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<ServerInstallationCubit>().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<ServerInstallationCubit>()
.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)),
),
],
);
}

View File

@ -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<AppSettingsPage> createState() => _AppSettingsPageState();
}
class _AppSettingsPageState extends State<AppSettingsPage> {
@override
Widget build(final BuildContext context) {
final bool isDarkModeOn =
context.watch<AppSettingsCubit>().state.isDarkModeOn;
final bool isSystemDarkModeOn =
context.watch<AppSettingsCubit>().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<AppSettingsCubit>()
.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<AppSettingsCubit>()
.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<ServerInstallationCubit>().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<ServerInstallationCubit>()
.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<ServerInstallationCubit>().clearAppConfig();
Navigator.of(context).pop();
},
),
ActionButton(
text: 'basis.cancel'.tr(),
),
],
),
);
},
);
}