mirror of
https://git.selfprivacy.org/kherel/selfprivacy.org.app.git
synced 2024-11-09 18:33:11 +00:00
feat: introduced app_controller, rehooked dependencies from app_settings_cubit, added language picker to settings_page
This commit is contained in:
parent
0ad15061a3
commit
ea2cc28ac9
183
lib/config/app_controller/app_controller.dart
Normal file
183
lib/config/app_controller/app_controller.dart
Normal file
|
@ -0,0 +1,183 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:material_color_utilities/material_color_utilities.dart'
|
||||
as color_utils;
|
||||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
import 'package:selfprivacy/config/preferences_repository/preferences_repository.dart';
|
||||
|
||||
/// A class that many Widgets can interact with to read current app
|
||||
/// configuration, update it, or listen to its changes.
|
||||
///
|
||||
/// AppController uses repo to change persistent data.
|
||||
class AppController with ChangeNotifier {
|
||||
AppController(this._repo);
|
||||
|
||||
/// repo encapsulates retrieval and storage of preferences
|
||||
final PreferencesRepository _repo;
|
||||
|
||||
/// TODO: to be removed or changed
|
||||
late final ApiConfigModel _apiConfigModel = getIt.get<ApiConfigModel>();
|
||||
|
||||
bool _loaded = false;
|
||||
bool get loaded => _loaded;
|
||||
|
||||
// localization
|
||||
late Locale _locale;
|
||||
Locale get locale => _locale;
|
||||
late List<Locale> _supportedLocales;
|
||||
List<Locale> get supportedLocales => _supportedLocales;
|
||||
|
||||
// theme
|
||||
late ThemeData _lightTheme;
|
||||
ThemeData get lightTheme => _lightTheme;
|
||||
late ThemeData _darkTheme;
|
||||
ThemeData get darkTheme => _darkTheme;
|
||||
late color_utils.CorePalette _corePalette;
|
||||
color_utils.CorePalette get corePalette => _corePalette;
|
||||
|
||||
late bool _systemThemeModeActive;
|
||||
bool get systemThemeModeActive => _systemThemeModeActive;
|
||||
|
||||
late bool _darkThemeModeActive;
|
||||
bool get darkThemeModeActive => _darkThemeModeActive;
|
||||
|
||||
ThemeMode get themeMode => systemThemeModeActive
|
||||
? ThemeMode.system
|
||||
: darkThemeModeActive
|
||||
? ThemeMode.dark
|
||||
: ThemeMode.light;
|
||||
// // Make ThemeMode a private variable so it is not updated directly without
|
||||
// // also persisting the changes with the repo..
|
||||
// late ThemeMode _themeMode;
|
||||
// // Allow Widgets to read the user's preferred ThemeMode.
|
||||
// ThemeMode get themeMode => _themeMode;
|
||||
|
||||
late bool _shouldShowOnboarding;
|
||||
bool get shouldShowOnboarding => _shouldShowOnboarding;
|
||||
|
||||
/// Load the user's settings from the SettingsService. It may load from a
|
||||
/// local database or the internet. The controller only knows it can load the
|
||||
/// settings from the service.
|
||||
Future<void> init({
|
||||
// required final AppPreferencesRepository repo,
|
||||
required final ThemeData lightThemeData,
|
||||
required final ThemeData darkThemeData,
|
||||
required final color_utils.CorePalette colorPalette,
|
||||
}) async {
|
||||
// _repo = repo;
|
||||
|
||||
await Future.wait(<Future>[
|
||||
// load locale
|
||||
() async {
|
||||
_supportedLocales = await _repo.getSupportedLocales();
|
||||
|
||||
_locale = await _repo.getActiveLocale();
|
||||
// preset value to other state holders
|
||||
await _apiConfigModel.setLocaleCode(_locale.languageCode);
|
||||
await _repo.setDelegateLocale(_locale);
|
||||
}(),
|
||||
|
||||
// load theme mode && initialize theme
|
||||
() async {
|
||||
_lightTheme = lightThemeData;
|
||||
_darkTheme = darkThemeData;
|
||||
_corePalette = colorPalette;
|
||||
// _themeMode = await _repo.getThemeMode();
|
||||
_darkThemeModeActive = await _repo.getDarkThemeModeFlag();
|
||||
_systemThemeModeActive = await _repo.getSystemThemeModeFlag();
|
||||
}(),
|
||||
|
||||
// load onboarding flag
|
||||
() async {
|
||||
_shouldShowOnboarding = await _repo.getShouldShowOnboarding();
|
||||
}(),
|
||||
]);
|
||||
|
||||
_loaded = true;
|
||||
// Important! Inform listeners a change has occurred.
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// updateRepoReference
|
||||
|
||||
Future<void> setShouldShowOnboarding(final bool newValue) async {
|
||||
// Do not perform any work if new and old flag values are identical
|
||||
if (newValue == shouldShowOnboarding) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Store the flag in memory
|
||||
_shouldShowOnboarding = newValue;
|
||||
|
||||
notifyListeners();
|
||||
|
||||
// Persist the change
|
||||
await _repo.setShouldShowOnboarding(newValue);
|
||||
}
|
||||
|
||||
Future<void> setSystemThemeModeFlag(final bool newValue) async {
|
||||
// Do not perform any work if new and old ThemeMode are identical
|
||||
if (systemThemeModeActive == newValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Store the new ThemeMode in memory
|
||||
_systemThemeModeActive = newValue;
|
||||
|
||||
// Inform listeners a change has occurred.
|
||||
notifyListeners();
|
||||
|
||||
// Persist the change
|
||||
await _repo.setSystemModeFlag(newValue);
|
||||
}
|
||||
|
||||
Future<void> setDarkThemeModeFlag(final bool newValue) async {
|
||||
// Do not perform any work if new and old ThemeMode are identical
|
||||
if (darkThemeModeActive == newValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Store the new ThemeMode in memory
|
||||
_darkThemeModeActive = newValue;
|
||||
|
||||
// Inform listeners a change has occurred.
|
||||
notifyListeners();
|
||||
|
||||
// Persist the change
|
||||
await _repo.setDarkThemeModeFlag(newValue);
|
||||
}
|
||||
|
||||
// /// Update and persist the ThemeMode based on the user's selection.
|
||||
// Future<void> setThemeMode(final ThemeMode newThemeMode) async {
|
||||
// // Do not perform any work if new and old ThemeMode are identical
|
||||
// if (newThemeMode == themeMode) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// // Store the new ThemeMode in memory
|
||||
// _themeMode = newThemeMode;
|
||||
|
||||
// // Inform listeners a change has occurred.
|
||||
// notifyListeners();
|
||||
|
||||
// // Persist the change
|
||||
// await _repo.setThemeMode(newThemeMode);
|
||||
// }
|
||||
|
||||
Future<void> setLocale(final Locale newLocale) async {
|
||||
// Do not perform any work if new and old Locales are identical
|
||||
if (newLocale == _locale) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Store the new Locale in memory
|
||||
_locale = newLocale;
|
||||
|
||||
/// update locale delegate, which in turn should update deps
|
||||
await _repo.setDelegateLocale(newLocale);
|
||||
|
||||
// Persist the change
|
||||
await _repo.setActiveLocale(newLocale);
|
||||
// Update other locale holders
|
||||
await _apiConfigModel.setLocaleCode(newLocale.languageCode);
|
||||
}
|
||||
}
|
106
lib/config/app_controller/inherited_app_controller.dart
Normal file
106
lib/config/app_controller/inherited_app_controller.dart
Normal file
|
@ -0,0 +1,106 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:material_color_utilities/material_color_utilities.dart'
|
||||
as color_utils;
|
||||
import 'package:selfprivacy/config/app_controller/app_controller.dart';
|
||||
import 'package:selfprivacy/config/brand_colors.dart';
|
||||
import 'package:selfprivacy/config/preferences_repository/inherited_preferences_repository.dart';
|
||||
import 'package:selfprivacy/config/preferences_repository/preferences_repository.dart';
|
||||
import 'package:selfprivacy/theming/factory/app_theme_factory.dart';
|
||||
|
||||
class _AppControllerInjector extends InheritedNotifier<AppController> {
|
||||
const _AppControllerInjector({
|
||||
required super.child,
|
||||
required super.notifier,
|
||||
});
|
||||
}
|
||||
|
||||
class InheritedAppController extends StatefulWidget {
|
||||
const InheritedAppController({
|
||||
required this.child,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
State<InheritedAppController> createState() => _InheritedAppControllerState();
|
||||
|
||||
static AppController of(final BuildContext context) => context
|
||||
.dependOnInheritedWidgetOfExactType<_AppControllerInjector>()!
|
||||
.notifier!;
|
||||
}
|
||||
|
||||
class _InheritedAppControllerState extends State<InheritedAppController> {
|
||||
// actual state provider
|
||||
late AppController controller;
|
||||
// hold local reference to active repo
|
||||
late PreferencesRepository _repo;
|
||||
|
||||
bool initTriggerred = false;
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
/// update reference on dependency change
|
||||
_repo = InheritedPreferencesRepository.of(context)!;
|
||||
|
||||
if (!initTriggerred) {
|
||||
/// hook controller repo to local reference
|
||||
controller = AppController(_repo);
|
||||
initialize();
|
||||
initTriggerred = true;
|
||||
}
|
||||
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
Future<void> initialize() async {
|
||||
late final ThemeData lightThemeData;
|
||||
late final ThemeData darkThemeData;
|
||||
late final color_utils.CorePalette colorPalette;
|
||||
|
||||
await Future.wait(
|
||||
<Future<void>>[
|
||||
() async {
|
||||
lightThemeData = await AppThemeFactory.create(
|
||||
isDark: false,
|
||||
fallbackColor: BrandColors.primary,
|
||||
);
|
||||
}(),
|
||||
() async {
|
||||
darkThemeData = await AppThemeFactory.create(
|
||||
isDark: true,
|
||||
fallbackColor: BrandColors.primary,
|
||||
);
|
||||
}(),
|
||||
() async {
|
||||
colorPalette = (await AppThemeFactory.getCorePalette()) ??
|
||||
color_utils.CorePalette.of(BrandColors.primary.value);
|
||||
}(),
|
||||
],
|
||||
);
|
||||
|
||||
await controller.init(
|
||||
colorPalette: colorPalette,
|
||||
lightThemeData: lightThemeData,
|
||||
darkThemeData: darkThemeData,
|
||||
);
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((final _) {
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => _AppControllerInjector(
|
||||
notifier: controller,
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
|
@ -8,7 +8,6 @@ import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart';
|
|||
import 'package:selfprivacy/logic/bloc/services/services_bloc.dart';
|
||||
import 'package:selfprivacy/logic/bloc/users/users_bloc.dart';
|
||||
import 'package:selfprivacy/logic/bloc/volumes/volumes_bloc.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart';
|
||||
|
@ -56,58 +55,46 @@ class BlocAndProviderConfigState extends State<BlocAndProviderConfig> {
|
|||
}
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
const isDark = false;
|
||||
const isAutoDark = true;
|
||||
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
BlocProvider(
|
||||
create: (final _) => AppSettingsCubit(
|
||||
isDarkModeOn: isDark,
|
||||
isAutoDarkModeOn: isAutoDark,
|
||||
isOnboardingShowing: true,
|
||||
)..load(),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => supportSystemCubit,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => serverInstallationCubit,
|
||||
lazy: false,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => usersBloc,
|
||||
lazy: false,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => servicesBloc,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => backupsBloc,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => dnsRecordsCubit,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => recoveryKeyBloc,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => devicesBloc,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => serverJobsBloc,
|
||||
),
|
||||
BlocProvider(create: (final _) => connectionStatusBloc),
|
||||
BlocProvider(
|
||||
create: (final _) => serverDetailsCubit,
|
||||
),
|
||||
BlocProvider(create: (final _) => volumesBloc),
|
||||
BlocProvider(
|
||||
create: (final _) => JobsCubit(),
|
||||
),
|
||||
],
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
Widget build(final BuildContext context) => MultiProvider(
|
||||
providers: [
|
||||
BlocProvider(
|
||||
create: (final _) => supportSystemCubit,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => serverInstallationCubit,
|
||||
lazy: false,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => usersBloc,
|
||||
lazy: false,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => servicesBloc,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => backupsBloc,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => dnsRecordsCubit,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => recoveryKeyBloc,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => devicesBloc,
|
||||
),
|
||||
BlocProvider(
|
||||
create: (final _) => serverJobsBloc,
|
||||
),
|
||||
BlocProvider(create: (final _) => connectionStatusBloc),
|
||||
BlocProvider(
|
||||
create: (final _) => serverDetailsCubit,
|
||||
),
|
||||
BlocProvider(create: (final _) => volumesBloc),
|
||||
BlocProvider(
|
||||
create: (final _) => JobsCubit(),
|
||||
),
|
||||
],
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -60,17 +60,20 @@ class HiveConfig {
|
|||
|
||||
/// Mappings for the different boxes and their keys
|
||||
class BNames {
|
||||
/// App settings box. Contains app settings like [isDarkModeOn], [isOnboardingShowing]
|
||||
/// App settings box. Contains app settings like [darkThemeModeOn], [shouldShowOnboarding]
|
||||
static String appSettingsBox = 'appSettings';
|
||||
|
||||
/// A boolean field of [appSettingsBox] box.
|
||||
static String isDarkModeOn = 'isDarkModeOn';
|
||||
static String darkThemeModeOn = 'isDarkModeOn';
|
||||
|
||||
/// A boolean field of [appSettingsBox] box.
|
||||
static String isAutoDarkModeOn = 'isAutoDarkModeOn';
|
||||
static String systemThemeModeOn = 'isAutoDarkModeOn';
|
||||
|
||||
/// A boolean field of [appSettingsBox] box.
|
||||
static String isOnboardingShowing = 'isOnboardingShowing';
|
||||
static String shouldShowOnboarding = 'isOnboardingShowing';
|
||||
|
||||
/// A string field
|
||||
static String appLocale = 'appLocale';
|
||||
|
||||
/// Encryption key to decrypt [serverInstallationBox] and [usersBox] box.
|
||||
static String serverInstallationEncryptionKey = 'key';
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/// abstraction for manipulation of stored app preferences
|
||||
abstract class PreferencesDataSource {
|
||||
/// should onboarding be shown
|
||||
Future<bool> getOnboardingFlag();
|
||||
|
||||
/// should onboarding be shown
|
||||
Future<void> setOnboardingFlag(final bool newValue);
|
||||
|
||||
// TODO: should probably deprecate the following, instead add the
|
||||
// getThemeMode and setThemeMode methods, which store one value instead of
|
||||
// flags.
|
||||
|
||||
/// should system theme mode be enabled
|
||||
Future<bool?> getSystemThemeModeFlag();
|
||||
|
||||
/// should system theme mode be enabled
|
||||
Future<void> setSystemThemeModeFlag(final bool newValue);
|
||||
|
||||
/// should dark theme be enabled
|
||||
Future<bool?> getDarkThemeModeFlag();
|
||||
|
||||
/// should dark theme be enabled
|
||||
Future<void> setDarkThemeModeFlag(final bool newValue);
|
||||
|
||||
/// locale, as set by user
|
||||
///
|
||||
///
|
||||
/// when null, app takes system locale
|
||||
Future<String?> getLocale();
|
||||
|
||||
/// locale, as set by user
|
||||
///
|
||||
///
|
||||
/// when null, app takes system locale
|
||||
Future<void> setLocale(final String newLocale);
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
import 'package:hive/hive.dart';
|
||||
import 'package:selfprivacy/config/hive_config.dart';
|
||||
import 'package:selfprivacy/config/preferences_repository/datasources/preferences_datasource.dart';
|
||||
|
||||
/// app preferences data source hive implementation
|
||||
class PreferencesHiveDataSource implements PreferencesDataSource {
|
||||
final Box _appSettingsBox = Hive.box(BNames.appSettingsBox);
|
||||
|
||||
@override
|
||||
Future<bool> getOnboardingFlag() async =>
|
||||
_appSettingsBox.get(BNames.shouldShowOnboarding, defaultValue: true);
|
||||
|
||||
@override
|
||||
Future<void> setOnboardingFlag(final bool newValue) async =>
|
||||
_appSettingsBox.put(BNames.shouldShowOnboarding, newValue);
|
||||
|
||||
@override
|
||||
Future<bool?> getSystemThemeModeFlag() async =>
|
||||
_appSettingsBox.get(BNames.systemThemeModeOn);
|
||||
|
||||
@override
|
||||
Future<void> setSystemThemeModeFlag(final bool newValue) async =>
|
||||
_appSettingsBox.put(BNames.systemThemeModeOn, newValue);
|
||||
|
||||
@override
|
||||
Future<bool?> getDarkThemeModeFlag() async =>
|
||||
_appSettingsBox.get(BNames.darkThemeModeOn);
|
||||
|
||||
@override
|
||||
Future<void> setDarkThemeModeFlag(final bool newValue) async =>
|
||||
_appSettingsBox.put(BNames.darkThemeModeOn, newValue);
|
||||
|
||||
@override
|
||||
Future<String?> getLocale() async => _appSettingsBox.get(BNames.appLocale);
|
||||
|
||||
@override
|
||||
Future<void> setLocale(final String newLocale) async =>
|
||||
_appSettingsBox.put(BNames.appLocale, newLocale);
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/config/preferences_repository/datasources/preferences_datasource.dart';
|
||||
import 'package:selfprivacy/config/preferences_repository/preferences_repository.dart';
|
||||
|
||||
class _PreferencesRepositoryInjector extends InheritedWidget {
|
||||
const _PreferencesRepositoryInjector({
|
||||
required this.settingsRepository,
|
||||
required super.child,
|
||||
});
|
||||
|
||||
final PreferencesRepository settingsRepository;
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(
|
||||
covariant final _PreferencesRepositoryInjector oldWidget,
|
||||
) =>
|
||||
oldWidget.settingsRepository != settingsRepository;
|
||||
}
|
||||
|
||||
/// Creates and injects app preferences repository inside widget tree.
|
||||
class InheritedPreferencesRepository extends StatefulWidget {
|
||||
const InheritedPreferencesRepository({
|
||||
required this.child,
|
||||
required this.dataSource,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final PreferencesDataSource dataSource;
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
State<InheritedPreferencesRepository> createState() =>
|
||||
_InheritedPreferencesRepositoryState();
|
||||
|
||||
static PreferencesRepository? of(final BuildContext context) => context
|
||||
.dependOnInheritedWidgetOfExactType<_PreferencesRepositoryInjector>()
|
||||
?.settingsRepository;
|
||||
}
|
||||
|
||||
class _InheritedPreferencesRepositoryState
|
||||
extends State<InheritedPreferencesRepository> {
|
||||
late PreferencesRepository repo;
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
|
||||
/// recreate repo each time dependencies change
|
||||
repo = PreferencesRepository(
|
||||
dataSource: widget.dataSource,
|
||||
setDelegateLocale: EasyLocalization.of(context)!.setLocale,
|
||||
getDelegateLocale: () => EasyLocalization.of(context)!.locale,
|
||||
getSupportedLocales: () => EasyLocalization.of(context)!.supportedLocales,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => _PreferencesRepositoryInjector(
|
||||
settingsRepository: repo,
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/config/preferences_repository/datasources/preferences_datasource.dart';
|
||||
|
||||
class PreferencesRepository {
|
||||
const PreferencesRepository({
|
||||
required this.dataSource,
|
||||
required this.getSupportedLocales,
|
||||
required this.getDelegateLocale,
|
||||
required this.setDelegateLocale,
|
||||
});
|
||||
|
||||
final PreferencesDataSource dataSource;
|
||||
|
||||
/// easy localizations don't expose type of localization provider,
|
||||
/// so it needs to be this crutchy (I could've created one more class-wrapper,
|
||||
/// containing needed functions, but perceive it as boilerplate, because we
|
||||
/// don't need additional encapsulation level here)
|
||||
final FutureOr<void> Function(Locale) setDelegateLocale;
|
||||
final FutureOr<List<Locale>> Function() getSupportedLocales;
|
||||
final FutureOr<Locale> Function() getDelegateLocale;
|
||||
|
||||
Future<bool> getSystemThemeModeFlag() async =>
|
||||
(await dataSource.getSystemThemeModeFlag()) ?? true;
|
||||
|
||||
Future<void> setSystemThemeModeFlag(final bool newValue) async =>
|
||||
dataSource.setSystemThemeModeFlag(newValue);
|
||||
|
||||
Future<bool> getDarkThemeModeFlag() async =>
|
||||
(await dataSource.getDarkThemeModeFlag()) ?? false;
|
||||
|
||||
Future<void> setDarkThemeModeFlag(final bool newValue) async =>
|
||||
dataSource.setDarkThemeModeFlag(newValue);
|
||||
|
||||
Future<void> setSystemModeFlag(final bool newValue) async =>
|
||||
dataSource.setSystemThemeModeFlag(newValue);
|
||||
|
||||
// Future<ThemeMode> getThemeMode() async {
|
||||
// final themeMode = await dataSource.getThemeMode()?? ThemeMode.system;
|
||||
// }
|
||||
//
|
||||
// Future<void> setThemeMode(final ThemeMode newThemeMode) =>
|
||||
// dataSource.setThemeMode(newThemeMode);
|
||||
|
||||
Future<List<Locale>> supportedLocales() async => getSupportedLocales();
|
||||
|
||||
Future<Locale> getActiveLocale() async {
|
||||
Locale? chosenLocale;
|
||||
|
||||
final String? storedLocaleCode = await dataSource.getLocale();
|
||||
if (storedLocaleCode != null) {
|
||||
chosenLocale = Locale(storedLocaleCode);
|
||||
}
|
||||
|
||||
// when it's null fallback on delegate locale
|
||||
chosenLocale ??= await getDelegateLocale();
|
||||
|
||||
return chosenLocale;
|
||||
}
|
||||
|
||||
Future<void> setActiveLocale(final Locale newLocale) async {
|
||||
await dataSource.setLocale(newLocale.toString());
|
||||
}
|
||||
|
||||
/// true when we need to show onboarding
|
||||
Future<bool> getShouldShowOnboarding() async =>
|
||||
dataSource.getOnboardingFlag();
|
||||
|
||||
/// true when we need to show onboarding
|
||||
Future<void> setShouldShowOnboarding(final bool newValue) =>
|
||||
dataSource.setOnboardingFlag(newValue);
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:material_color_utilities/material_color_utilities.dart'
|
||||
as color_utils;
|
||||
import 'package:selfprivacy/config/brand_colors.dart';
|
||||
import 'package:selfprivacy/config/hive_config.dart';
|
||||
import 'package:selfprivacy/theming/factory/app_theme_factory.dart';
|
||||
|
||||
export 'package:provider/provider.dart';
|
||||
|
||||
part 'app_settings_state.dart';
|
||||
|
||||
class AppSettingsCubit extends Cubit<AppSettingsState> {
|
||||
AppSettingsCubit({
|
||||
required final bool isDarkModeOn,
|
||||
required final bool isAutoDarkModeOn,
|
||||
required final bool isOnboardingShowing,
|
||||
}) : super(
|
||||
AppSettingsState(
|
||||
isDarkModeOn: isDarkModeOn,
|
||||
isAutoDarkModeOn: isAutoDarkModeOn,
|
||||
isOnboardingShowing: isOnboardingShowing,
|
||||
),
|
||||
);
|
||||
|
||||
Box box = Hive.box(BNames.appSettingsBox);
|
||||
|
||||
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,
|
||||
),
|
||||
);
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
final color_utils.CorePalette? colorPalette =
|
||||
await AppThemeFactory.getCorePalette();
|
||||
emit(
|
||||
state.copyWith(
|
||||
corePalette: colorPalette,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void updateDarkMode({required final bool isDarkModeOn}) {
|
||||
box.put(BNames.isDarkModeOn, isDarkModeOn);
|
||||
emit(state.copyWith(isDarkModeOn: isDarkModeOn));
|
||||
}
|
||||
|
||||
void updateAutoDarkMode({required final bool isAutoDarkModeOn}) {
|
||||
box.put(BNames.isAutoDarkModeOn, isAutoDarkModeOn);
|
||||
emit(state.copyWith(isAutoDarkModeOn: isAutoDarkModeOn));
|
||||
}
|
||||
|
||||
void turnOffOnboarding({final bool isOnboardingShowing = false}) {
|
||||
box.put(BNames.isOnboardingShowing, isOnboardingShowing);
|
||||
|
||||
emit(state.copyWith(isOnboardingShowing: isOnboardingShowing));
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
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,
|
||||
);
|
||||
|
||||
color_utils.CorePalette get corePaletteOrDefault =>
|
||||
corePalette ?? color_utils.CorePalette.of(BrandColors.primary.value);
|
||||
|
||||
@override
|
||||
List<dynamic> get props =>
|
||||
[isDarkModeOn, isAutoDarkModeOn, isOnboardingShowing, corePalette];
|
||||
}
|
165
lib/main.dart
165
lib/main.dart
|
@ -1,21 +1,19 @@
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:selfprivacy/config/app_controller/inherited_app_controller.dart';
|
||||
import 'package:selfprivacy/config/bloc_config.dart';
|
||||
import 'package:selfprivacy/config/bloc_observer.dart';
|
||||
import 'package:selfprivacy/config/brand_colors.dart';
|
||||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
import 'package:selfprivacy/config/hive_config.dart';
|
||||
import 'package:selfprivacy/config/localization.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
||||
import 'package:selfprivacy/theming/factory/app_theme_factory.dart';
|
||||
import 'package:selfprivacy/config/preferences_repository/datasources/preferences_hive_datasource.dart';
|
||||
import 'package:selfprivacy/config/preferences_repository/inherited_preferences_repository.dart';
|
||||
import 'package:selfprivacy/ui/router/router.dart';
|
||||
// import 'package:wakelock/wakelock.dart';
|
||||
import 'package:timezone/data/latest.dart' as tz;
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await HiveConfig.init();
|
||||
// await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
||||
|
||||
// try {
|
||||
|
@ -26,86 +24,111 @@ void main() async {
|
|||
// print(e);
|
||||
// }
|
||||
|
||||
await Future.wait(
|
||||
<Future<void>>[
|
||||
HiveConfig.init(),
|
||||
EasyLocalization.ensureInitialized(),
|
||||
],
|
||||
);
|
||||
await getItSetup();
|
||||
await EasyLocalization.ensureInitialized();
|
||||
tz.initializeTimeZones();
|
||||
|
||||
final ThemeData lightThemeData = await AppThemeFactory.create(
|
||||
isDark: false,
|
||||
fallbackColor: BrandColors.primary,
|
||||
);
|
||||
final ThemeData darkThemeData = await AppThemeFactory.create(
|
||||
isDark: true,
|
||||
fallbackColor: BrandColors.primary,
|
||||
);
|
||||
tz.initializeTimeZones();
|
||||
|
||||
Bloc.observer = SimpleBlocObserver();
|
||||
|
||||
runApp(
|
||||
SelfprivacyApp(
|
||||
lightThemeData: lightThemeData,
|
||||
darkThemeData: darkThemeData,
|
||||
Localization(
|
||||
child: InheritedPreferencesRepository(
|
||||
dataSource: PreferencesHiveDataSource(),
|
||||
child: const InheritedAppController(
|
||||
child: AppBuilder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class SelfprivacyApp extends StatelessWidget {
|
||||
SelfprivacyApp({
|
||||
required this.lightThemeData,
|
||||
required this.darkThemeData,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final ThemeData lightThemeData;
|
||||
final ThemeData darkThemeData;
|
||||
|
||||
final _appRouter = RootRouter(getIt.get<NavigationService>().navigatorKey);
|
||||
class AppBuilder extends StatelessWidget {
|
||||
const AppBuilder({super.key});
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => Localization(
|
||||
child: BlocAndProviderConfig(
|
||||
child: BlocBuilder<AppSettingsCubit, AppSettingsState>(
|
||||
builder: (
|
||||
final BuildContext context,
|
||||
final AppSettingsState appSettings,
|
||||
) {
|
||||
getIt.get<ApiConfigModel>().setLocaleCode(
|
||||
context.locale.languageCode,
|
||||
);
|
||||
return MaterialApp.router(
|
||||
routeInformationParser: _appRouter.defaultRouteParser(),
|
||||
routerDelegate: _appRouter.delegate(),
|
||||
scaffoldMessengerKey:
|
||||
getIt.get<NavigationService>().scaffoldMessengerKey,
|
||||
localizationsDelegates: context.localizationDelegates,
|
||||
supportedLocales: context.supportedLocales,
|
||||
locale: context.locale,
|
||||
debugShowCheckedModeBanner: false,
|
||||
title: 'SelfPrivacy',
|
||||
theme: lightThemeData,
|
||||
darkTheme: darkThemeData,
|
||||
themeMode: appSettings.isAutoDarkModeOn
|
||||
? ThemeMode.system
|
||||
: appSettings.isDarkModeOn
|
||||
? ThemeMode.dark
|
||||
: ThemeMode.light,
|
||||
scrollBehavior: const MaterialScrollBehavior().copyWith(
|
||||
scrollbars: false,
|
||||
),
|
||||
builder: (final BuildContext context, final Widget? widget) {
|
||||
Widget error =
|
||||
const Center(child: Text('...rendering error...'));
|
||||
if (widget is Scaffold || widget is Navigator) {
|
||||
error = Scaffold(body: error);
|
||||
}
|
||||
ErrorWidget.builder =
|
||||
(final FlutterErrorDetails errorDetails) => error;
|
||||
Widget build(final BuildContext context) {
|
||||
final appController = InheritedAppController.of(context);
|
||||
|
||||
return widget ?? error;
|
||||
},
|
||||
);
|
||||
},
|
||||
if (appController.loaded) {
|
||||
return const SelfprivacyApp();
|
||||
}
|
||||
|
||||
return const SplashScreen();
|
||||
}
|
||||
}
|
||||
|
||||
/// Widget to be shown
|
||||
/// until essential app initialization is completed
|
||||
class SplashScreen extends StatelessWidget {
|
||||
const SplashScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => const ColoredBox(
|
||||
color: Colors.white,
|
||||
child: Center(
|
||||
child: CircularProgressIndicator.adaptive(
|
||||
valueColor: AlwaysStoppedAnimation(BrandColors.primary),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class SelfprivacyApp extends StatefulWidget {
|
||||
const SelfprivacyApp({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<SelfprivacyApp> createState() => _SelfprivacyAppState();
|
||||
}
|
||||
|
||||
class _SelfprivacyAppState extends State<SelfprivacyApp> {
|
||||
final appKey = UniqueKey();
|
||||
final _appRouter = RootRouter(getIt.get<NavigationService>().navigatorKey);
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
final appController = InheritedAppController.of(context);
|
||||
|
||||
return BlocAndProviderConfig(
|
||||
child: MaterialApp.router(
|
||||
key: appKey,
|
||||
title: 'SelfPrivacy',
|
||||
// routing
|
||||
routeInformationParser: _appRouter.defaultRouteParser(),
|
||||
routerDelegate: _appRouter.delegate(),
|
||||
scaffoldMessengerKey:
|
||||
getIt.get<NavigationService>().scaffoldMessengerKey,
|
||||
// localization settings
|
||||
locale: context.locale,
|
||||
supportedLocales: context.supportedLocales,
|
||||
localizationsDelegates: context.localizationDelegates,
|
||||
// theme settings
|
||||
themeMode: appController.themeMode,
|
||||
theme: appController.lightTheme,
|
||||
darkTheme: appController.darkTheme,
|
||||
// other preferences
|
||||
debugShowCheckedModeBanner: false,
|
||||
scrollBehavior:
|
||||
const MaterialScrollBehavior().copyWith(scrollbars: false),
|
||||
builder: _builder,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _builder(final BuildContext context, final Widget? widget) {
|
||||
Widget error = const Center(child: Text('...rendering error...'));
|
||||
if (widget is Scaffold || widget is Navigator) {
|
||||
error = Scaffold(body: error);
|
||||
}
|
||||
ErrorWidget.builder = (final FlutterErrorDetails errorDetails) => error;
|
||||
|
||||
return widget ?? error;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:selfprivacy/config/app_controller/inherited_app_controller.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||
import 'package:selfprivacy/ui/components/buttons/dialog_action_button.dart';
|
||||
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||
import 'package:selfprivacy/ui/router/router.dart';
|
||||
|
||||
part 'language_picker.dart';
|
||||
part 'reset_app_button.dart';
|
||||
part 'theme_picker.dart';
|
||||
|
||||
@RoutePage()
|
||||
class AppSettingsPage extends StatefulWidget {
|
||||
|
@ -16,82 +22,36 @@ class AppSettingsPage extends StatefulWidget {
|
|||
|
||||
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.adaptive(
|
||||
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),
|
||||
Widget build(final BuildContext context) => BrandHeroScreen(
|
||||
hasBackButton: true,
|
||||
hasFlashButton: false,
|
||||
bodyPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 16,
|
||||
),
|
||||
SwitchListTile.adaptive(
|
||||
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,
|
||||
),
|
||||
heroTitle: 'application_settings.title'.tr(),
|
||||
children: [
|
||||
_ThemePicker(
|
||||
key: ValueKey('theme_picker'.tr()),
|
||||
),
|
||||
),
|
||||
const _ResetAppTile(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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 _) => AlertDialog(
|
||||
title: Text('modals.are_you_sure'.tr()),
|
||||
content: Text('modals.purge_all_keys'.tr()),
|
||||
actions: [
|
||||
DialogActionButton(
|
||||
text: 'modals.purge_all_keys_confirm'.tr(),
|
||||
isRed: true,
|
||||
onPressed: () {
|
||||
context.read<ServerInstallationCubit>().clearAppConfig();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
DialogActionButton(
|
||||
text: 'basis.cancel'.tr(),
|
||||
),
|
||||
],
|
||||
const Divider(height: 5, thickness: 0),
|
||||
_LanguagePicker(
|
||||
key: ValueKey('language_picker'.tr()),
|
||||
),
|
||||
const Divider(height: 5, thickness: 0),
|
||||
const Gap(4),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Text(
|
||||
'application_settings.dangerous_settings'.tr(),
|
||||
style: Theme.of(context).textTheme.titleLarge!.copyWith(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
_ResetAppTile(
|
||||
key: ValueKey('reset_app'.tr()),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:selfprivacy/config/app_controller/inherited_app_controller.dart';
|
||||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/tls_options.dart';
|
||||
import 'package:selfprivacy/logic/bloc/services/services_bloc.dart';
|
||||
import 'package:selfprivacy/logic/bloc/volumes/volumes_bloc.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
||||
import 'package:selfprivacy/ui/components/list_tiles/section_title.dart';
|
||||
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||
import 'package:selfprivacy/ui/router/router.dart';
|
||||
|
@ -60,17 +61,14 @@ class _DeveloperSettingsPageState extends State<DeveloperSettingsPage> {
|
|||
title: Text('developer_settings.reset_onboarding'.tr()),
|
||||
subtitle:
|
||||
Text('developer_settings.reset_onboarding_description'.tr()),
|
||||
enabled:
|
||||
!context.watch<AppSettingsCubit>().state.isOnboardingShowing,
|
||||
onTap: () => context
|
||||
.read<AppSettingsCubit>()
|
||||
.turnOffOnboarding(isOnboardingShowing: true),
|
||||
enabled: !InheritedAppController.of(context).shouldShowOnboarding,
|
||||
onTap: () => InheritedAppController.of(context)
|
||||
.setShouldShowOnboarding(true),
|
||||
),
|
||||
ListTile(
|
||||
title: Text('storage.start_migration_button'.tr()),
|
||||
subtitle: Text('storage.data_migration_notice'.tr()),
|
||||
enabled:
|
||||
!context.watch<AppSettingsCubit>().state.isOnboardingShowing,
|
||||
enabled: InheritedAppController.of(context).shouldShowOnboarding,
|
||||
onTap: () => context.pushRoute(
|
||||
ServicesMigrationRoute(
|
||||
diskStatus: context.read<VolumesBloc>().state.diskStatus,
|
||||
|
|
53
lib/ui/pages/more/app_settings/language_picker.dart
Normal file
53
lib/ui/pages/more/app_settings/language_picker.dart
Normal file
|
@ -0,0 +1,53 @@
|
|||
part of 'app_settings.dart';
|
||||
|
||||
class _LanguagePicker extends StatelessWidget {
|
||||
const _LanguagePicker({super.key});
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => ListTile(
|
||||
title: Text(
|
||||
'application_settings.language'.tr(),
|
||||
),
|
||||
subtitle: Text('application_settings.click_to_change_locale'.tr()),
|
||||
trailing: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Text(
|
||||
context.locale.toString(),
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
),
|
||||
onTap: () async {
|
||||
final appController = InheritedAppController.of(context);
|
||||
final Locale? newLocale = await showDialog<Locale?>(
|
||||
context: context,
|
||||
builder: (final context) => const _LanguagePickerDialog(),
|
||||
routeSettings: _LanguagePickerDialog.routeSettings,
|
||||
);
|
||||
|
||||
if (newLocale != null) {
|
||||
await appController.setLocale(newLocale);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
class _LanguagePickerDialog extends StatelessWidget {
|
||||
const _LanguagePickerDialog();
|
||||
static const routeSettings = RouteSettings(name: 'LanguagePickerDialog');
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => SimpleDialog(
|
||||
title: Text('application_settings.language'.tr()),
|
||||
children: [
|
||||
for (final locale
|
||||
in InheritedAppController.of(context).supportedLocales)
|
||||
ListTile(
|
||||
// TODO: add locale to language name matcher
|
||||
title: Text(locale.toString()),
|
||||
onTap: () {
|
||||
Navigator.of(context).pop(locale);
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
42
lib/ui/pages/more/app_settings/reset_app_button.dart
Normal file
42
lib/ui/pages/more/app_settings/reset_app_button.dart
Normal file
|
@ -0,0 +1,42 @@
|
|||
part of 'app_settings.dart';
|
||||
|
||||
class _ResetAppTile extends StatelessWidget {
|
||||
const _ResetAppTile({super.key});
|
||||
|
||||
@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 context) => const _ResetAppDialog(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class _ResetAppDialog extends StatelessWidget {
|
||||
const _ResetAppDialog();
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) => AlertDialog(
|
||||
title: Text('modals.are_you_sure'.tr()),
|
||||
content: Text('modals.purge_all_keys'.tr()),
|
||||
actions: [
|
||||
DialogActionButton(
|
||||
text: 'modals.purge_all_keys_confirm'.tr(),
|
||||
isRed: true,
|
||||
onPressed: () {
|
||||
context.read<ServerInstallationCubit>().clearAppConfig();
|
||||
|
||||
context.router.maybePop([
|
||||
const RootRoute(),
|
||||
]);
|
||||
context.resetLocale();
|
||||
},
|
||||
),
|
||||
DialogActionButton(
|
||||
text: 'basis.cancel'.tr(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
44
lib/ui/pages/more/app_settings/theme_picker.dart
Normal file
44
lib/ui/pages/more/app_settings/theme_picker.dart
Normal file
|
@ -0,0 +1,44 @@
|
|||
part of 'app_settings.dart';
|
||||
|
||||
class _ThemePicker extends StatelessWidget {
|
||||
const _ThemePicker({super.key});
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
final appController = InheritedAppController.of(context);
|
||||
// final themeMode = appController.themeMode;
|
||||
// final bool isSystemThemeModeEnabled = themeMode == ThemeMode.system;
|
||||
// final bool isDarkModeOn = themeMode == ThemeMode.dark;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
SwitchListTile.adaptive(
|
||||
title: Text('application_settings.system_theme_mode_title'.tr()),
|
||||
subtitle:
|
||||
Text('application_settings.system_theme_mode_description'.tr()),
|
||||
value: appController.systemThemeModeActive,
|
||||
onChanged: appController.setSystemThemeModeFlag,
|
||||
// onChanged: (final newValue) => appController.setThemeMode(
|
||||
// newValue
|
||||
// ? ThemeMode.system
|
||||
// : (isDarkModeOn ? ThemeMode.dark : ThemeMode.light),
|
||||
// ),
|
||||
),
|
||||
SwitchListTile.adaptive(
|
||||
title: Text('application_settings.dark_theme_title'.tr()),
|
||||
subtitle: Text('application_settings.change_application_theme'.tr()),
|
||||
value: appController.darkThemeModeActive,
|
||||
onChanged: appController.systemThemeModeActive
|
||||
? null
|
||||
: appController.setDarkThemeModeFlag,
|
||||
// onChanged: isSystemThemeModeEnabled
|
||||
// ? null
|
||||
// : (final newValue) => appController.setThemeMode(
|
||||
// newValue ? ThemeMode.dark : ThemeMode.light,
|
||||
// ),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
||||
import 'package:selfprivacy/config/app_controller/inherited_app_controller.dart';
|
||||
import 'package:selfprivacy/ui/pages/onboarding/views/views.dart';
|
||||
import 'package:selfprivacy/ui/router/router.dart';
|
||||
|
||||
|
@ -37,7 +37,8 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
|||
),
|
||||
OnboardingSecondView(
|
||||
onProceed: () {
|
||||
context.read<AppSettingsCubit>().turnOffOnboarding();
|
||||
InheritedAppController.of(context)
|
||||
.setShouldShowOnboarding(false);
|
||||
context.router.replaceAll([
|
||||
const RootRoute(),
|
||||
const InitializingRoute(),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart';
|
||||
import 'package:selfprivacy/config/app_controller/inherited_app_controller.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||
import 'package:selfprivacy/ui/layouts/root_scaffold_with_navigation.dart';
|
||||
import 'package:selfprivacy/ui/router/root_destinations.dart';
|
||||
|
@ -19,31 +19,33 @@ class RootPage extends StatefulWidget implements AutoRouteWrapper {
|
|||
}
|
||||
|
||||
class _RootPageState extends State<RootPage> with TickerProviderStateMixin {
|
||||
bool shouldUseSplitView() => false;
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
if (InheritedAppController.of(context).shouldShowOnboarding) {
|
||||
context.router.replace(const OnboardingRoute());
|
||||
}
|
||||
|
||||
final destinations = rootDestinations;
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
final bool isReady = context.watch<ServerInstallationCubit>().state
|
||||
is ServerInstallationFinished;
|
||||
|
||||
if (context.read<AppSettingsCubit>().state.isOnboardingShowing) {
|
||||
context.router.replace(const OnboardingRoute());
|
||||
}
|
||||
|
||||
return AutoRouter(
|
||||
builder: (final context, final child) {
|
||||
final currentDestinationIndex = destinations.indexWhere(
|
||||
final currentDestinationIndex = rootDestinations.indexWhere(
|
||||
(final destination) =>
|
||||
context.router.isRouteActive(destination.route.routeName),
|
||||
);
|
||||
final isOtherRouterActive =
|
||||
context.router.root.current.name != RootRoute.name;
|
||||
|
||||
final routeName = getRouteTitle(context.router.current.name).tr();
|
||||
return RootScaffoldWithNavigation(
|
||||
title: routeName,
|
||||
destinations: destinations,
|
||||
destinations: rootDestinations,
|
||||
showBottomBar:
|
||||
!(currentDestinationIndex == -1 && !isOtherRouterActive),
|
||||
showFab: isReady,
|
||||
|
@ -53,99 +55,3 @@ class _RootPageState extends State<RootPage> with TickerProviderStateMixin {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MainScreenNavigationRail extends StatelessWidget {
|
||||
const MainScreenNavigationRail({
|
||||
required this.destinations,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final List<RouteDestination> destinations;
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
int? activeIndex = destinations.indexWhere(
|
||||
(final destination) =>
|
||||
context.router.isRouteActive(destination.route.routeName),
|
||||
);
|
||||
if (activeIndex == -1) {
|
||||
activeIndex = null;
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: SizedBox(
|
||||
height: MediaQuery.of(context).size.height,
|
||||
width: 72,
|
||||
child: LayoutBuilder(
|
||||
builder: (final context, final constraints) => SingleChildScrollView(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(minHeight: constraints.maxHeight),
|
||||
child: IntrinsicHeight(
|
||||
child: NavigationRail(
|
||||
backgroundColor: Colors.transparent,
|
||||
labelType: NavigationRailLabelType.all,
|
||||
destinations: destinations
|
||||
.map(
|
||||
(final destination) => NavigationRailDestination(
|
||||
icon: Icon(destination.icon),
|
||||
label: Text(destination.label),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
selectedIndex: activeIndex,
|
||||
onDestinationSelected: (final index) {
|
||||
context.router.replaceAll([destinations[index].route]);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MainScreenNavigationDrawer extends StatelessWidget {
|
||||
const MainScreenNavigationDrawer({
|
||||
required this.destinations,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final List<RouteDestination> destinations;
|
||||
|
||||
@override
|
||||
Widget build(final BuildContext context) {
|
||||
int? activeIndex = destinations.indexWhere(
|
||||
(final destination) =>
|
||||
context.router.isRouteActive(destination.route.routeName),
|
||||
);
|
||||
if (activeIndex == -1) {
|
||||
activeIndex = null;
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
height: MediaQuery.of(context).size.height,
|
||||
width: 296,
|
||||
child: LayoutBuilder(
|
||||
builder: (final context, final constraints) => NavigationDrawer(
|
||||
key: const Key('PrimaryNavigationDrawer'),
|
||||
selectedIndex: activeIndex,
|
||||
onDestinationSelected: (final index) {
|
||||
context.router.replaceAll([destinations[index].route]);
|
||||
},
|
||||
children: [
|
||||
const SizedBox(height: 18),
|
||||
...destinations.map(
|
||||
(final destination) => NavigationDrawerDestination(
|
||||
icon: Icon(destination.icon),
|
||||
label: Text(destination.label),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/config/app_controller/inherited_app_controller.dart';
|
||||
import 'package:selfprivacy/illustrations/stray_deer.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/logic/models/price.dart';
|
||||
import 'package:selfprivacy/logic/models/server_provider_location.dart';
|
||||
|
@ -205,10 +205,8 @@ class SelectTypePage extends StatelessWidget {
|
|||
),
|
||||
painter: StrayDeerPainter(
|
||||
colorScheme: Theme.of(context).colorScheme,
|
||||
colorPalette: context
|
||||
.read<AppSettingsCubit>()
|
||||
.state
|
||||
.corePaletteOrDefault,
|
||||
colorPalette:
|
||||
InheritedAppController.of(context).corePalette,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
Loading…
Reference in a new issue