diff --git a/.vscode/launch.json b/.vscode/launch.json index 17f7b44..205e7b6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -21,7 +21,8 @@ "request": "launch", "type": "dart", "flutterMode": "debug", - "args": ["--flavor", "base"], + // TODO: https://github.com/flutter/flutter/issues/146890#issuecomment-2212409188 + "args": ["--flavor", "base" /*"--enable-software-rendering"*/], "program": "lib/main.dart" }, { diff --git a/lib/core/settings/settings.dart b/lib/core/settings/settings.dart index c99772d..d23eb70 100644 --- a/lib/core/settings/settings.dart +++ b/lib/core/settings/settings.dart @@ -330,56 +330,59 @@ abstract class _AppSettingsKey { abstract class SharedPreferencesModule { @Singleton(env: [Env.prod, Env.dev]) @preResolve - Future get oldPref async => + Future get prefOld async => SharedPreferences.getInstance(); @Singleton(env: [Env.prod, Env.dev]) @preResolve - Future get pref async => SharedPreferencesAsync(); + Future pref(SharedPreferences prefOld) async { + final pref = SharedPreferencesAsync(); + final migrator = SharedPreferencesMigrator( + oldPrefs: prefOld, + newPrefs: pref, + ); + await migrator.migrate(); + + return pref; + } @Singleton(env: [Env.test]) @preResolve - Future get testPref async => TestSharedPreferences(); -} + Future get testPref async => + TestSharedPreferencesAsync(); -class TestSharedPreferences implements SharedPreferences { - final Map _map = {}; - - @override - Future clear() async { - _map.clear(); - return true; + @Singleton(env: [Env.test]) + @preResolve + Future get testOldPref async { + // ignore: invalid_use_of_visible_for_testing_member + SharedPreferences.setMockInitialValues({}); + return SharedPreferences.getInstance(); } +} - @override - Future commit() async => true; - - @override - bool containsKey(String key) => _map.containsKey(key); +class TestSharedPreferencesAsync implements SharedPreferencesAsync { + final Map _map = {}; - @override dynamic get(String key) => _map[key]; @override - bool? getBool(String key) => get(key) as bool?; - - @override - double? getDouble(String key) => get(key) as double?; + Future containsKey(String key) async => _map.containsKey(key); @override - int? getInt(String key) => get(key) as int?; + Future getBool(String key) async => get(key) as bool?; @override - Set getKeys() => _map.keys.toSet(); + Future getDouble(String key) async => get(key) as double?; @override - String? getString(String key) => get(key) as String?; + Future getInt(String key) async => get(key) as int?; @override - List? getStringList(String key) => get(key) as List?; + Future getString(String key) async => get(key) as String?; @override - Future reload() async {} + Future?> getStringList(String key) async => + get(key) as List?; @override Future remove(String key) async => _map.remove(key) != null; @@ -404,4 +407,33 @@ class TestSharedPreferences implements SharedPreferences { @override Future setStringList(String key, List value) async => _set(key, value); + + @override + Future clear({Set? allowList}) async { + if (allowList == null) { + _map.clear(); + } else { + _map.removeWhere((k, v) => !allowList.contains(k)); + } + } + + @override + Future> getAll({Set? allowList}) async { + if (allowList == null) { + return _map; + } else { + return Map.fromEntries( + _map.entries.where((e) => allowList.contains(e.key)), + ); + } + } + + @override + Future> getKeys({Set? allowList}) async { + if (allowList == null) { + return Set.from(_map.keys); + } else { + return Set.from(_map.keys.where((k) => allowList.contains(k))); + } + } } diff --git a/lib/core/settings/shared_pref_migrator.dart b/lib/core/settings/shared_pref_migrator.dart index ae4bc15..adfa178 100644 --- a/lib/core/settings/shared_pref_migrator.dart +++ b/lib/core/settings/shared_pref_migrator.dart @@ -4,6 +4,8 @@ import 'dart:async'; import 'package:shared_preferences/shared_preferences.dart'; +import '../../logger.dart'; + class SharedPreferencesMigrator { final SharedPreferences oldPrefs; final SharedPreferencesAsync newPrefs; @@ -15,8 +17,11 @@ class SharedPreferencesMigrator { /// Reads all the values from [oldPrefs] and saves them to [newPrefs]. Future migrate() async { - if (didMigrate ?? false) return; - for (final key in oldPrefs.getKeys()) { + if (didMigrate ?? false) { + return; + } + final keys = oldPrefs.getKeys(); + for (final key in keys) { final value = oldPrefs.get(key); if (value is String) { await newPrefs.setString(key, value); @@ -30,6 +35,9 @@ class SharedPreferencesMigrator { await newPrefs.setStringList(key, value); } } + if (keys.isNotEmpty) { + log().i('[SharedPreferences] Migration completed'); + } oldPrefs.setBool("shared_preferences_package_did_migrate", true); } } diff --git a/lib/injector.config.dart b/lib/injector.config.dart index 77163f7..4f06c6a 100644 --- a/lib/injector.config.dart +++ b/lib/injector.config.dart @@ -104,7 +104,7 @@ extension GetItInjectableX on _i174.GetIt { }, ); await gh.singletonAsync<_i460.SharedPreferences>( - () => sharedPreferencesModule.pref, + () => sharedPreferencesModule.prefOld, registerFor: { _prod, _dev, @@ -123,11 +123,16 @@ extension GetItInjectableX on _i174.GetIt { () => flutterSecureStorageModule.testStorage, registerFor: {_test}, ); - await gh.singletonAsync<_i460.SharedPreferences>( + await gh.singletonAsync<_i460.SharedPreferencesAsync>( () => sharedPreferencesModule.testPref, registerFor: {_test}, preResolve: true, ); + await gh.singletonAsync<_i460.SharedPreferences>( + () => sharedPreferencesModule.testOldPref, + registerFor: {_test}, + preResolve: true, + ); await gh.singletonAsync<_i93.AppDatabase>( () => appDatabaseModule.inMemoryDb, registerFor: {_test}, @@ -147,6 +152,10 @@ extension GetItInjectableX on _i174.GetIt { () => clientModule.clientProd, registerFor: {_prod}, ); + gh.singleton<_i23.AppSettings>(() => _i23.AppSettingsImpl( + gh<_i460.SharedPreferencesAsync>(), + gh<_i460.SharedPreferences>(), + )); gh.factory<_i796.CrashReportSender>( () => _i796.CrashReportSenderImpl(gh<_i253.PlatformInfo>())); gh.factory<_i776.ServiceAuthStorage>(() => _i776.ServiceAuthStorageImpl( @@ -175,20 +184,39 @@ extension GetItInjectableX on _i174.GetIt { ); gh.singleton<_i144.ShipmentRepository>( () => _i144.ShipmentRepositoryImpl(gh<_i93.AppDatabase>())); + gh.singleton<_i710.NotificationManager>(() => _i710.NotificationManagerImpl( + gh<_i253.PlatformInfo>(), + gh<_i23.AppSettings>(), + )); gh.singleton<_i819.TrackNumberRepository>( () => _i819.TrackNumberRepositoryImpl(gh<_i93.AppDatabase>())); + await gh.singletonAsync<_i460.SharedPreferencesAsync>( + () => sharedPreferencesModule.pref(gh<_i460.SharedPreferences>()), + registerFor: { + _prod, + _dev, + }, + preResolve: true, + ); gh.singleton<_i36.WorkManagerRepository>( () => _i36.WorkManagerRepositoryImpl(gh<_i93.AppDatabase>())); + gh.factory<_i728.SystemTray>(() => _i728.SystemTray( + gh<_i253.PlatformInfo>(), + gh<_i23.AppSettings>(), + )); gh.singleton<_i35.ServiceRepository>(() => _i35.ServiceRepositoryImpl( gh<_i93.AppDatabase>(), gh<_i776.ServiceAuthStorage>(), )); gh.singleton<_i1023.TrackingRepository>( () => _i1023.TrackingRepositoryImpl(gh<_i93.AppDatabase>())); + gh.factory<_i521.TrackingLimiter>(() => _i521.TrackingLimiterImpl( + gh<_i23.AppSettings>(), + gh<_i1023.TrackingRepository>(), + gh<_i541.DateTimeProvider>(), + )); gh.factory<_i761.ParserFactory>( () => _i761.ParserFactoryImpl(gh<_i541.DateTimeProvider>())); - gh.singleton<_i23.AppSettings>( - () => _i23.AppSettingsImpl(gh<_i460.SharedPreferences>())); gh.singleton<_i94.WorkManager>( () => _i94.WorkManagerImpl( gh<_i36.WorkManagerRepository>(), @@ -198,6 +226,11 @@ extension GetItInjectableX on _i174.GetIt { ), registerFor: {_prod}, ); + gh.factory<_i514.TrackingNotifyTask>(() => _i514.TrackingNotifyTask( + gh<_i710.NotificationManager>(), + gh<_i819.TrackNumberRepository>(), + gh<_i23.AppSettings>(), + )); gh.factory<_i554.CrashReportManager>(() => _i554.CrashReportManagerImpl( gh<_i97.CrashReportBuilder>(), gh<_i796.CrashReportSender>(), @@ -207,10 +240,6 @@ extension GetItInjectableX on _i174.GetIt { gh<_i580.Fetcher>(), gh<_i761.ParserFactory>(), )); - gh.singleton<_i710.NotificationManager>(() => _i710.NotificationManagerImpl( - gh<_i253.PlatformInfo>(), - gh<_i23.AppSettings>(), - )); gh.singleton<_i94.WorkManager>( () => _i94.DebugWorkManagerImpl( gh<_i36.WorkManagerRepository>(), @@ -233,20 +262,6 @@ extension GetItInjectableX on _i174.GetIt { )); gh.singleton<_i577.WorkerManager>( () => _i577.WorkerManagerImpl(gh<_i94.WorkManager>())); - gh.factory<_i728.SystemTray>(() => _i728.SystemTray( - gh<_i253.PlatformInfo>(), - gh<_i23.AppSettings>(), - )); - gh.factory<_i521.TrackingLimiter>(() => _i521.TrackingLimiterImpl( - gh<_i23.AppSettings>(), - gh<_i1023.TrackingRepository>(), - gh<_i541.DateTimeProvider>(), - )); - gh.factory<_i514.TrackingNotifyTask>(() => _i514.TrackingNotifyTask( - gh<_i710.NotificationManager>(), - gh<_i819.TrackNumberRepository>(), - gh<_i23.AppSettings>(), - )); gh.singleton<_i268.TrackingScheduler>(() => _i268.TrackingSchedulerImpl( gh<_i577.WorkerManager>(), gh<_i819.TrackNumberRepository>(), diff --git a/lib/main.dart b/lib/main.dart index 1da1069..181b365 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2021 Yaroslav Pronin +// Copyright (C) 2021-2024 Yaroslav Pronin // Copyright (C) 2021 Insurgo Inc. // // This file is part of LibreTrack. @@ -19,11 +19,14 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:libretrack/core/app_database_isolate_binder.dart'; import 'package:libretrack/core/crash_catcher/crash_catcher.dart'; import 'package:libretrack/core/crash_catcher/hook/flutter_crash_hook.dart'; import 'package:libretrack/core/notification_manager.dart'; +import 'package:libretrack/core/settings/settings.dart'; import 'package:libretrack/platform/system_tray.dart'; +import 'package:libretrack/ui/app_cubit.dart'; import 'core/tracking_scheduler.dart'; import 'core/work_manager/work_manager.dart'; @@ -46,9 +49,14 @@ Future _main() async { await getIt().init(); runApp( - App( - enableDevicePreview: false, - navigatorKey: navigatorKey, + BlocProvider( + create: (context) => AppCubit( + getIt(), + ), + child: App( + enableDevicePreview: false, + navigatorKey: navigatorKey, + ), ), ); } diff --git a/lib/ui/app.dart b/lib/ui/app.dart index 27b81aa..1a3d7d2 100644 --- a/lib/ui/app.dart +++ b/lib/ui/app.dart @@ -16,6 +16,8 @@ // You should have received a copy of the GNU General Public License // along with LibreTrack. If not, see . +// ignore_for_file: unused_element + import 'dart:async'; import 'package:collection/collection.dart'; @@ -61,6 +63,10 @@ class _AppState extends State { void initState() { super.initState(); + WidgetsBinding.instance.addPostFrameCallback( + (_) async => await context.read().load(), + ); + _routerDelegate = AppRouterDelegate(navigatorKey: widget.navigatorKey); final platform = getIt(); @@ -209,30 +215,31 @@ class _AppState extends State { TransitionBuilder? builder, ThemeMode? themeMode, }) { - return BlocProvider( - create: (context) => AppCubit( - getIt(), - ), - child: BlocBuilder( - builder: (context, state) { - return MaterialApp.router( - title: 'LibreTrack', - themeMode: themeMode ?? _mapThemeMode(state.theme), - theme: AppTheme.getThemeData(), - darkTheme: AppTheme.getThemeData(dark: true), - localizationsDelegates: AppLocalizations.localizationsDelegates, - supportedLocales: AppLocalizations.supportedLocales, - locale: locale ?? _mapLocale(state.locale), - builder: (context, child) { - return InltLocaleBridge( - child: builder == null ? child : builder(context, child), - ); - }, - routerDelegate: _routerDelegate, - routeInformationParser: _routeInfoParser, - ); - }, - ), + return BlocBuilder( + builder: (context, state) { + switch (state) { + case AppStateInitial(): + return const _Loading(); + case AppStateLoaded(locale: var sLocale, theme: final sTheme) || + AppStateChanged(locale: var sLocale, theme: final sTheme): + return MaterialApp.router( + title: 'LibreTrack', + themeMode: themeMode ?? _mapThemeMode(sTheme), + theme: AppTheme.getThemeData(), + darkTheme: AppTheme.getThemeData(dark: true), + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + locale: locale ?? _mapLocale(sLocale), + builder: (context, child) { + return InltLocaleBridge( + child: builder == null ? child : builder(context, child), + ); + }, + routerDelegate: _routerDelegate, + routeInformationParser: _routeInfoParser, + ); + } + }, ); } @@ -254,3 +261,18 @@ class _AppState extends State { ); } } + +class _Loading extends StatelessWidget { + const _Loading({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + alignment: Alignment.center, + color: AppTheme.paletteLight.primary, + child: CircularProgressIndicator( + color: AppTheme.paletteLight.secondary, + ), + ); + } +} diff --git a/lib/ui/app_cubit.dart b/lib/ui/app_cubit.dart index 8c6ec21..ccfd245 100644 --- a/lib/ui/app_cubit.dart +++ b/lib/ui/app_cubit.dart @@ -23,11 +23,16 @@ import 'package:libretrack/core/settings/settings.dart'; part 'app_cubit.freezed.dart'; @freezed -class AppState with _$AppState { +sealed class AppState with _$AppState { const factory AppState.initial({ + @Default(null) AppThemeType? theme, + @Default(null) AppLocaleType? locale, + }) = AppStateInitial; + + const factory AppState.loaded({ required AppThemeType theme, required AppLocaleType locale, - }) = AppStateInitial; + }) = AppStateLoaded; const factory AppState.changed({ required AppThemeType theme, @@ -36,25 +41,32 @@ class AppState with _$AppState { } class AppCubit extends Cubit { - AppCubit(AppSettings pref) - : super( - AppState.initial( - theme: pref.theme, - locale: pref.locale, - ), - ); + final AppSettings _pref; - void setTheme(AppThemeType theme) { + AppCubit(this._pref) : super(const AppState.initial()); + + Future load() async { emit(AppState.changed( - theme: theme, - locale: state.locale, + theme: await _pref.theme, + locale: await _pref.locale, )); } + void setTheme(AppThemeType theme) { + if (state.locale case final locale?) { + emit(AppState.changed( + theme: theme, + locale: locale, + )); + } + } + void setLocale(AppLocaleType locale) { - emit(AppState.changed( - theme: state.theme, - locale: locale, - )); + if (state.theme case final theme?) { + emit(AppState.changed( + theme: theme, + locale: locale, + )); + } } } diff --git a/lib/ui/app_cubit.freezed.dart b/lib/ui/app_cubit.freezed.dart index a473497..6cfbab4 100644 --- a/lib/ui/app_cubit.freezed.dart +++ b/lib/ui/app_cubit.freezed.dart @@ -16,23 +16,27 @@ final _privateConstructorUsedError = UnsupportedError( /// @nodoc mixin _$AppState { - AppThemeType get theme => throw _privateConstructorUsedError; - AppLocaleType get locale => throw _privateConstructorUsedError; + AppThemeType? get theme => throw _privateConstructorUsedError; + AppLocaleType? get locale => throw _privateConstructorUsedError; @optionalTypeArgs TResult when({ - required TResult Function(AppThemeType theme, AppLocaleType locale) initial, + required TResult Function(AppThemeType? theme, AppLocaleType? locale) + initial, + required TResult Function(AppThemeType theme, AppLocaleType locale) loaded, required TResult Function(AppThemeType theme, AppLocaleType locale) changed, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult? whenOrNull({ - TResult? Function(AppThemeType theme, AppLocaleType locale)? initial, + TResult? Function(AppThemeType? theme, AppLocaleType? locale)? initial, + TResult? Function(AppThemeType theme, AppLocaleType locale)? loaded, TResult? Function(AppThemeType theme, AppLocaleType locale)? changed, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeWhen({ - TResult Function(AppThemeType theme, AppLocaleType locale)? initial, + TResult Function(AppThemeType? theme, AppLocaleType? locale)? initial, + TResult Function(AppThemeType theme, AppLocaleType locale)? loaded, TResult Function(AppThemeType theme, AppLocaleType locale)? changed, required TResult orElse(), }) => @@ -40,18 +44,21 @@ mixin _$AppState { @optionalTypeArgs TResult map({ required TResult Function(AppStateInitial value) initial, + required TResult Function(AppStateLoaded value) loaded, required TResult Function(AppStateChanged value) changed, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult? mapOrNull({ TResult? Function(AppStateInitial value)? initial, + TResult? Function(AppStateLoaded value)? loaded, TResult? Function(AppStateChanged value)? changed, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeMap({ TResult Function(AppStateInitial value)? initial, + TResult Function(AppStateLoaded value)? loaded, TResult Function(AppStateChanged value)? changed, required TResult orElse(), }) => @@ -71,8 +78,8 @@ abstract class $AppStateCopyWith<$Res> { @useResult $Res call({AppThemeType theme, AppLocaleType locale}); - $AppThemeTypeCopyWith<$Res> get theme; - $AppLocaleTypeCopyWith<$Res> get locale; + $AppThemeTypeCopyWith<$Res>? get theme; + $AppLocaleTypeCopyWith<$Res>? get locale; } /// @nodoc @@ -95,11 +102,11 @@ class _$AppStateCopyWithImpl<$Res, $Val extends AppState> }) { return _then(_value.copyWith( theme: null == theme - ? _value.theme + ? _value.theme! : theme // ignore: cast_nullable_to_non_nullable as AppThemeType, locale: null == locale - ? _value.locale + ? _value.locale! : locale // ignore: cast_nullable_to_non_nullable as AppLocaleType, ) as $Val); @@ -109,8 +116,12 @@ class _$AppStateCopyWithImpl<$Res, $Val extends AppState> /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') - $AppThemeTypeCopyWith<$Res> get theme { - return $AppThemeTypeCopyWith<$Res>(_value.theme, (value) { + $AppThemeTypeCopyWith<$Res>? get theme { + if (_value.theme == null) { + return null; + } + + return $AppThemeTypeCopyWith<$Res>(_value.theme!, (value) { return _then(_value.copyWith(theme: value) as $Val); }); } @@ -119,8 +130,12 @@ class _$AppStateCopyWithImpl<$Res, $Val extends AppState> /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') - $AppLocaleTypeCopyWith<$Res> get locale { - return $AppLocaleTypeCopyWith<$Res>(_value.locale, (value) { + $AppLocaleTypeCopyWith<$Res>? get locale { + if (_value.locale == null) { + return null; + } + + return $AppLocaleTypeCopyWith<$Res>(_value.locale!, (value) { return _then(_value.copyWith(locale: value) as $Val); }); } @@ -134,12 +149,12 @@ abstract class _$$AppStateInitialImplCopyWith<$Res> __$$AppStateInitialImplCopyWithImpl<$Res>; @override @useResult - $Res call({AppThemeType theme, AppLocaleType locale}); + $Res call({AppThemeType? theme, AppLocaleType? locale}); @override - $AppThemeTypeCopyWith<$Res> get theme; + $AppThemeTypeCopyWith<$Res>? get theme; @override - $AppLocaleTypeCopyWith<$Res> get locale; + $AppLocaleTypeCopyWith<$Res>? get locale; } /// @nodoc @@ -155,18 +170,18 @@ class __$$AppStateInitialImplCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override $Res call({ - Object? theme = null, - Object? locale = null, + Object? theme = freezed, + Object? locale = freezed, }) { return _then(_$AppStateInitialImpl( - theme: null == theme + theme: freezed == theme ? _value.theme : theme // ignore: cast_nullable_to_non_nullable - as AppThemeType, - locale: null == locale + as AppThemeType?, + locale: freezed == locale ? _value.locale : locale // ignore: cast_nullable_to_non_nullable - as AppLocaleType, + as AppLocaleType?, )); } } @@ -174,12 +189,14 @@ class __$$AppStateInitialImplCopyWithImpl<$Res> /// @nodoc class _$AppStateInitialImpl implements AppStateInitial { - const _$AppStateInitialImpl({required this.theme, required this.locale}); + const _$AppStateInitialImpl({this.theme = null, this.locale = null}); @override - final AppThemeType theme; + @JsonKey() + final AppThemeType? theme; @override - final AppLocaleType locale; + @JsonKey() + final AppLocaleType? locale; @override String toString() { @@ -210,7 +227,9 @@ class _$AppStateInitialImpl implements AppStateInitial { @override @optionalTypeArgs TResult when({ - required TResult Function(AppThemeType theme, AppLocaleType locale) initial, + required TResult Function(AppThemeType? theme, AppLocaleType? locale) + initial, + required TResult Function(AppThemeType theme, AppLocaleType locale) loaded, required TResult Function(AppThemeType theme, AppLocaleType locale) changed, }) { return initial(theme, locale); @@ -219,7 +238,8 @@ class _$AppStateInitialImpl implements AppStateInitial { @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function(AppThemeType theme, AppLocaleType locale)? initial, + TResult? Function(AppThemeType? theme, AppLocaleType? locale)? initial, + TResult? Function(AppThemeType theme, AppLocaleType locale)? loaded, TResult? Function(AppThemeType theme, AppLocaleType locale)? changed, }) { return initial?.call(theme, locale); @@ -228,7 +248,8 @@ class _$AppStateInitialImpl implements AppStateInitial { @override @optionalTypeArgs TResult maybeWhen({ - TResult Function(AppThemeType theme, AppLocaleType locale)? initial, + TResult Function(AppThemeType? theme, AppLocaleType? locale)? initial, + TResult Function(AppThemeType theme, AppLocaleType locale)? loaded, TResult Function(AppThemeType theme, AppLocaleType locale)? changed, required TResult orElse(), }) { @@ -242,6 +263,7 @@ class _$AppStateInitialImpl implements AppStateInitial { @optionalTypeArgs TResult map({ required TResult Function(AppStateInitial value) initial, + required TResult Function(AppStateLoaded value) loaded, required TResult Function(AppStateChanged value) changed, }) { return initial(this); @@ -251,6 +273,7 @@ class _$AppStateInitialImpl implements AppStateInitial { @optionalTypeArgs TResult? mapOrNull({ TResult? Function(AppStateInitial value)? initial, + TResult? Function(AppStateLoaded value)? loaded, TResult? Function(AppStateChanged value)? changed, }) { return initial?.call(this); @@ -260,6 +283,7 @@ class _$AppStateInitialImpl implements AppStateInitial { @optionalTypeArgs TResult maybeMap({ TResult Function(AppStateInitial value)? initial, + TResult Function(AppStateLoaded value)? loaded, TResult Function(AppStateChanged value)? changed, required TResult orElse(), }) { @@ -272,8 +296,197 @@ class _$AppStateInitialImpl implements AppStateInitial { abstract class AppStateInitial implements AppState { const factory AppStateInitial( + {final AppThemeType? theme, + final AppLocaleType? locale}) = _$AppStateInitialImpl; + + @override + AppThemeType? get theme; + @override + AppLocaleType? get locale; + + /// Create a copy of AppState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$AppStateInitialImplCopyWith<_$AppStateInitialImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$AppStateLoadedImplCopyWith<$Res> + implements $AppStateCopyWith<$Res> { + factory _$$AppStateLoadedImplCopyWith(_$AppStateLoadedImpl value, + $Res Function(_$AppStateLoadedImpl) then) = + __$$AppStateLoadedImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({AppThemeType theme, AppLocaleType locale}); + + @override + $AppThemeTypeCopyWith<$Res> get theme; + @override + $AppLocaleTypeCopyWith<$Res> get locale; +} + +/// @nodoc +class __$$AppStateLoadedImplCopyWithImpl<$Res> + extends _$AppStateCopyWithImpl<$Res, _$AppStateLoadedImpl> + implements _$$AppStateLoadedImplCopyWith<$Res> { + __$$AppStateLoadedImplCopyWithImpl( + _$AppStateLoadedImpl _value, $Res Function(_$AppStateLoadedImpl) _then) + : super(_value, _then); + + /// Create a copy of AppState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? theme = null, + Object? locale = null, + }) { + return _then(_$AppStateLoadedImpl( + theme: null == theme + ? _value.theme + : theme // ignore: cast_nullable_to_non_nullable + as AppThemeType, + locale: null == locale + ? _value.locale + : locale // ignore: cast_nullable_to_non_nullable + as AppLocaleType, + )); + } + + /// Create a copy of AppState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $AppThemeTypeCopyWith<$Res> get theme { + return $AppThemeTypeCopyWith<$Res>(_value.theme, (value) { + return _then(_value.copyWith(theme: value)); + }); + } + + /// Create a copy of AppState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $AppLocaleTypeCopyWith<$Res> get locale { + return $AppLocaleTypeCopyWith<$Res>(_value.locale, (value) { + return _then(_value.copyWith(locale: value)); + }); + } +} + +/// @nodoc + +class _$AppStateLoadedImpl implements AppStateLoaded { + const _$AppStateLoadedImpl({required this.theme, required this.locale}); + + @override + final AppThemeType theme; + @override + final AppLocaleType locale; + + @override + String toString() { + return 'AppState.loaded(theme: $theme, locale: $locale)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AppStateLoadedImpl && + (identical(other.theme, theme) || other.theme == theme) && + (identical(other.locale, locale) || other.locale == locale)); + } + + @override + int get hashCode => Object.hash(runtimeType, theme, locale); + + /// Create a copy of AppState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$AppStateLoadedImplCopyWith<_$AppStateLoadedImpl> get copyWith => + __$$AppStateLoadedImplCopyWithImpl<_$AppStateLoadedImpl>( + this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(AppThemeType? theme, AppLocaleType? locale) + initial, + required TResult Function(AppThemeType theme, AppLocaleType locale) loaded, + required TResult Function(AppThemeType theme, AppLocaleType locale) changed, + }) { + return loaded(theme, locale); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(AppThemeType? theme, AppLocaleType? locale)? initial, + TResult? Function(AppThemeType theme, AppLocaleType locale)? loaded, + TResult? Function(AppThemeType theme, AppLocaleType locale)? changed, + }) { + return loaded?.call(theme, locale); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(AppThemeType? theme, AppLocaleType? locale)? initial, + TResult Function(AppThemeType theme, AppLocaleType locale)? loaded, + TResult Function(AppThemeType theme, AppLocaleType locale)? changed, + required TResult orElse(), + }) { + if (loaded != null) { + return loaded(theme, locale); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(AppStateInitial value) initial, + required TResult Function(AppStateLoaded value) loaded, + required TResult Function(AppStateChanged value) changed, + }) { + return loaded(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(AppStateInitial value)? initial, + TResult? Function(AppStateLoaded value)? loaded, + TResult? Function(AppStateChanged value)? changed, + }) { + return loaded?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(AppStateInitial value)? initial, + TResult Function(AppStateLoaded value)? loaded, + TResult Function(AppStateChanged value)? changed, + required TResult orElse(), + }) { + if (loaded != null) { + return loaded(this); + } + return orElse(); + } +} + +abstract class AppStateLoaded implements AppState { + const factory AppStateLoaded( {required final AppThemeType theme, - required final AppLocaleType locale}) = _$AppStateInitialImpl; + required final AppLocaleType locale}) = _$AppStateLoadedImpl; @override AppThemeType get theme; @@ -284,7 +497,7 @@ abstract class AppStateInitial implements AppState { /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$AppStateInitialImplCopyWith<_$AppStateInitialImpl> get copyWith => + _$$AppStateLoadedImplCopyWith<_$AppStateLoadedImpl> get copyWith => throw _privateConstructorUsedError; } @@ -331,6 +544,26 @@ class __$$AppStateChangedImplCopyWithImpl<$Res> as AppLocaleType, )); } + + /// Create a copy of AppState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $AppThemeTypeCopyWith<$Res> get theme { + return $AppThemeTypeCopyWith<$Res>(_value.theme, (value) { + return _then(_value.copyWith(theme: value)); + }); + } + + /// Create a copy of AppState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $AppLocaleTypeCopyWith<$Res> get locale { + return $AppLocaleTypeCopyWith<$Res>(_value.locale, (value) { + return _then(_value.copyWith(locale: value)); + }); + } } /// @nodoc @@ -372,7 +605,9 @@ class _$AppStateChangedImpl implements AppStateChanged { @override @optionalTypeArgs TResult when({ - required TResult Function(AppThemeType theme, AppLocaleType locale) initial, + required TResult Function(AppThemeType? theme, AppLocaleType? locale) + initial, + required TResult Function(AppThemeType theme, AppLocaleType locale) loaded, required TResult Function(AppThemeType theme, AppLocaleType locale) changed, }) { return changed(theme, locale); @@ -381,7 +616,8 @@ class _$AppStateChangedImpl implements AppStateChanged { @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function(AppThemeType theme, AppLocaleType locale)? initial, + TResult? Function(AppThemeType? theme, AppLocaleType? locale)? initial, + TResult? Function(AppThemeType theme, AppLocaleType locale)? loaded, TResult? Function(AppThemeType theme, AppLocaleType locale)? changed, }) { return changed?.call(theme, locale); @@ -390,7 +626,8 @@ class _$AppStateChangedImpl implements AppStateChanged { @override @optionalTypeArgs TResult maybeWhen({ - TResult Function(AppThemeType theme, AppLocaleType locale)? initial, + TResult Function(AppThemeType? theme, AppLocaleType? locale)? initial, + TResult Function(AppThemeType theme, AppLocaleType locale)? loaded, TResult Function(AppThemeType theme, AppLocaleType locale)? changed, required TResult orElse(), }) { @@ -404,6 +641,7 @@ class _$AppStateChangedImpl implements AppStateChanged { @optionalTypeArgs TResult map({ required TResult Function(AppStateInitial value) initial, + required TResult Function(AppStateLoaded value) loaded, required TResult Function(AppStateChanged value) changed, }) { return changed(this); @@ -413,6 +651,7 @@ class _$AppStateChangedImpl implements AppStateChanged { @optionalTypeArgs TResult? mapOrNull({ TResult? Function(AppStateInitial value)? initial, + TResult? Function(AppStateLoaded value)? loaded, TResult? Function(AppStateChanged value)? changed, }) { return changed?.call(this); @@ -422,6 +661,7 @@ class _$AppStateChangedImpl implements AppStateChanged { @optionalTypeArgs TResult maybeMap({ TResult Function(AppStateInitial value)? initial, + TResult Function(AppStateLoaded value)? loaded, TResult Function(AppStateChanged value)? changed, required TResult orElse(), }) { diff --git a/lib/ui/parcel_details/generate_barcode_dialog.dart b/lib/ui/parcel_details/generate_barcode_dialog.dart index 7471fcb..8b7e402 100644 --- a/lib/ui/parcel_details/generate_barcode_dialog.dart +++ b/lib/ui/parcel_details/generate_barcode_dialog.dart @@ -54,44 +54,52 @@ class _GenerateBarcodeDialogState extends State { child: AlertDialog( title: Text(S.of(context).generateBarcode), scrollable: true, - content: BarcodeWidget( - width: deviceType == DeviceScreenType.mobile ? minSize : 300, - height: deviceType == DeviceScreenType.mobile ? minSize / 3 : 150, - barcode: _getBarcodeByType(pref.barcodeGeneratorType), - data: widget.trackNumber, - errorBuilder: (context, error) { - log().e( - 'Unable to generate barcode for tracking number ${widget.trackNumber}', - error: error, - ); - return _GenerateBarcodeError( - error: error, - ); + content: FutureBuilder( + future: pref.barcodeGeneratorType, + builder: (context, snapshot) { + final barcodeType = snapshot.data; + if (barcodeType == null) { + return const CircularProgressIndicator(); + } else { + return BarcodeWidget( + width: deviceType == DeviceScreenType.mobile ? minSize : 300, + height: + deviceType == DeviceScreenType.mobile ? minSize / 3 : 150, + barcode: _getBarcodeByType(barcodeType), + data: widget.trackNumber, + errorBuilder: (context, error) { + log().e( + 'Unable to generate barcode for tracking number ${widget.trackNumber}', + error: error, + ); + return _GenerateBarcodeError( + error: error, + ); + }, + ); + } }, ), actions: [ - TextButton( - onPressed: () { - setState(() { - pref.barcodeGeneratorType.when( - code128: () { - pref.barcodeGeneratorType = - const BarcodeGeneratorType.qrCode(); - }, - qrCode: () { - pref.barcodeGeneratorType = - const BarcodeGeneratorType.code128(); - }, - ); - }); + FutureBuilder( + future: pref.barcodeGeneratorType, + builder: (context, snapshot) { + final barcodeType = snapshot.data; + return TextButton( + onPressed: barcodeType == null + ? null + : () => _saveBarcodeType(barcodeType), + child: Text( + barcodeType?.when( + code128: () => S.of(context).barcodeGeneratorShowQrCode, + qrCode: () => + S.of(context).barcodeGeneratorShowBarcodeCode, + ) ?? + "", + textAlign: TextAlign.end, + ), + ); }, - child: Text( - pref.barcodeGeneratorType.when( - code128: () => S.of(context).barcodeGeneratorShowQrCode, - qrCode: () => S.of(context).barcodeGeneratorShowBarcodeCode, - ), - textAlign: TextAlign.end, - ), ), TextButton( onPressed: () { @@ -106,6 +114,22 @@ class _GenerateBarcodeDialogState extends State { ), ); } + + Future _saveBarcodeType(BarcodeGeneratorType barcodeType) async { + await barcodeType.when( + code128: () async { + await pref.setBarcodeGeneratorType( + const BarcodeGeneratorType.qrCode(), + ); + }, + qrCode: () async { + await pref.setBarcodeGeneratorType( + const BarcodeGeneratorType.code128(), + ); + }, + ); + setState(() {}); + } } Barcode _getBarcodeByType(BarcodeGeneratorType type) { diff --git a/lib/ui/parcels/first_start_cubit.dart b/lib/ui/parcels/first_start_cubit.dart index 813014b..b09049a 100644 --- a/lib/ui/parcels/first_start_cubit.dart +++ b/lib/ui/parcels/first_start_cubit.dart @@ -43,7 +43,7 @@ class FirstStartCubit extends Cubit { ) : super(const FirstStartState.initial()); Future showAddAccountTip() async { - if (_pref.addAccountTipShown) { + if (await _pref.addAccountTipShown) { emit(const FirstStartState.hideAddAccountTip()); } else { final result = await _serviceRepo.getAllServices(); @@ -59,7 +59,7 @@ class FirstStartCubit extends Cubit { } Future addAccountTipShown() async { - _pref.addAccountTipShown = true; + await _pref.setAddAccountTipShown(true); emit(const FirstStartState.hideAddAccountTip()); } } diff --git a/lib/ui/parcels/parcels_cubit.dart b/lib/ui/parcels/parcels_cubit.dart index 866426f..9acca7b 100644 --- a/lib/ui/parcels/parcels_cubit.dart +++ b/lib/ui/parcels/parcels_cubit.dart @@ -67,16 +67,13 @@ class ParcelsCubit extends Cubit { this._trackingRepo, this._shipmentRepo, this._pref, - ) : super(ParcelsState.initial( - filters: _pref.parcelsFilters ?? ParcelsFilterBatch(), - sort: _pref.parcelsSort, - )); + ) : super(ParcelsState.initial(filters: ParcelsFilterBatch())); Future observeParcels() async { emit(ParcelsState.initial( - filters: state.filters, + filters: await _pref.parcelsFilters ?? ParcelsFilterBatch(), search: state.search, - sort: state.sort, + sort: await _pref.parcelsSort, )); final group = StreamGroup.mergeBroadcast([ @@ -151,51 +148,51 @@ class ParcelsCubit extends Cubit { )); } - void setErrorFilter({required bool enable}) { + Future setErrorFilter({required bool enable}) async { final filters = ParcelsFilterBatch.from(state.filters) ..errorFilter = enable ? const ParcelsFilter.error() : null; - _pref.parcelsFilters = filters; + await _pref.setParcelsFilters(filters); emit(state.copyWith(filters: filters)); } - void setNewInfoFilter({required bool enable}) { + Future setNewInfoFilter({required bool enable}) async { final filters = ParcelsFilterBatch.from(state.filters) ..newInfoFilter = enable ? const ParcelsFilter.newInfo() : null; - _pref.parcelsFilters = filters; + await _pref.setParcelsFilters(filters); emit(state.copyWith(filters: filters)); } - void setPostalServiceFilter(PostalServiceType? serviceType) { + Future setPostalServiceFilter(PostalServiceType? serviceType) async { final filters = ParcelsFilterBatch.from(state.filters) ..postalServiceFilter = ParcelsFilter.postalService( serviceType: serviceType, ); - _pref.parcelsFilters = filters; + await _pref.setParcelsFilters(filters); emit(state.copyWith(filters: filters)); } - void setStatusFilter(ShipmentStatusType? statusType) { + Future setStatusFilter(ShipmentStatusType? statusType) async { final filters = ParcelsFilterBatch.from(state.filters) ..statusFilter = ParcelsFilter.status(statusType: statusType); - _pref.parcelsFilters = filters; + await _pref.setParcelsFilters(filters); emit(state.copyWith(filters: filters)); } - void setActivityDateSort({bool oldestFirst = false}) { + Future setActivityDateSort({bool oldestFirst = false}) async { final sort = ParcelsSort.activityDate(oldestFirst: oldestFirst); - _pref.parcelsSort = sort; + await _pref.setParcelsSort(sort); emit(state.copyWith(sort: sort)); } - void setAlphabeticallySort({bool isDesc = false}) { + Future setAlphabeticallySort({bool isDesc = false}) async { final sort = ParcelsSort.alphabetically(isDesc: isDesc); - _pref.parcelsSort = sort; + await _pref.setParcelsSort(sort); emit(state.copyWith(sort: sort)); } - void setDateAddedSort({bool oldestFirst = false}) { + Future setDateAddedSort({bool oldestFirst = false}) async { final sort = ParcelsSort.dateAdded(oldestFirst: oldestFirst); - _pref.parcelsSort = sort; + await _pref.setParcelsSort(sort); emit(state.copyWith(sort: sort)); } diff --git a/lib/ui/parcels/parcels_filter_drawer.dart b/lib/ui/parcels/parcels_filter_drawer.dart index 5af6db3..8e18e00 100644 --- a/lib/ui/parcels/parcels_filter_drawer.dart +++ b/lib/ui/parcels/parcels_filter_drawer.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2021 Yaroslav Pronin +// Copyright (C) 2021-2024 Yaroslav Pronin // Copyright (C) 2021 Insurgo Inc. // // This file is part of LibreTrack. @@ -211,8 +211,8 @@ class _StatusFilterList extends StatelessWidget { title: S.of(context).status, value: statusType, items: items, - onChanged: (newValue) => - context.read().setStatusFilter(newValue), + onChanged: (newValue) async => + await context.read().setStatusFilter(newValue), ); } @@ -244,8 +244,8 @@ class _NewInfoFilter extends StatelessWidget { icon: Icon(MdiIcons.newBox), title: S.of(context).unreadParcels, value: enabled, - onChanged: (enable) => - context.read().setNewInfoFilter(enable: enable), + onChanged: (enable) async => + await context.read().setNewInfoFilter(enable: enable), ); } } @@ -261,8 +261,8 @@ class _ErrorFilter extends StatelessWidget { icon: const Icon(Icons.error_outline), title: S.of(context).error, value: enabled, - onChanged: (enable) => - context.read().setErrorFilter(enable: enable), + onChanged: (enable) async => + await context.read().setErrorFilter(enable: enable), ); } } @@ -304,8 +304,8 @@ class _PostalServiceFilter extends StatelessWidget { title: S.of(context).postalService, value: serviceType, items: items, - onChanged: (newValue) => - context.read().setPostalServiceFilter(newValue), + onChanged: (newValue) async => + await context.read().setPostalServiceFilter(newValue), ); } @@ -460,45 +460,48 @@ class _SortList extends StatelessWidget { children: [ _SortListItem( sort: const ParcelsSort.activityDate(), - currentSort: state.sort!, + currentSort: state.sort, title: S.of(context).sortActivityDateAsc, icon: Icon(MdiIcons.sortClockAscendingOutline), - onSelected: () => cubit.setActivityDateSort(), + onSelected: () async => await cubit.setActivityDateSort(), ), _SortListItem( sort: const ParcelsSort.activityDate(oldestFirst: true), - currentSort: state.sort!, + currentSort: state.sort, title: S.of(context).sortActivityDateDesc, icon: Icon(MdiIcons.sortClockDescendingOutline), - onSelected: () => cubit.setActivityDateSort(oldestFirst: true), + onSelected: () async => + await cubit.setActivityDateSort(oldestFirst: true), ), _SortListItem( sort: const ParcelsSort.dateAdded(), - currentSort: state.sort!, + currentSort: state.sort, title: S.of(context).sortDateAddedAsc, icon: Icon(MdiIcons.sortCalendarAscending), - onSelected: () => cubit.setDateAddedSort(), + onSelected: () async => await cubit.setDateAddedSort(), ), _SortListItem( sort: const ParcelsSort.dateAdded(oldestFirst: true), - currentSort: state.sort!, + currentSort: state.sort, title: S.of(context).sortDateAddedDesc, icon: Icon(MdiIcons.sortCalendarDescending), - onSelected: () => cubit.setDateAddedSort(oldestFirst: true), + onSelected: () async => + await cubit.setDateAddedSort(oldestFirst: true), ), _SortListItem( sort: const ParcelsSort.alphabetically(), - currentSort: state.sort!, + currentSort: state.sort, title: S.of(context).sortAlphabeticallyAsc, icon: Icon(MdiIcons.sortAlphabeticalAscending), - onSelected: () => cubit.setAlphabeticallySort(), + onSelected: () async => await cubit.setAlphabeticallySort(), ), _SortListItem( sort: const ParcelsSort.alphabetically(isDesc: true), - currentSort: state.sort!, + currentSort: state.sort, title: S.of(context).sortAlphabeticallyDesc, icon: Icon(MdiIcons.sortAlphabeticalDescending), - onSelected: () => cubit.setAlphabeticallySort(isDesc: true), + onSelected: () async => + await cubit.setAlphabeticallySort(isDesc: true), ), ], ); @@ -509,7 +512,7 @@ class _SortList extends StatelessWidget { class _SortListItem extends StatelessWidget { final ParcelsSort sort; - final ParcelsSort currentSort; + final ParcelsSort? currentSort; final String title; final Icon icon; final VoidCallback onSelected; diff --git a/lib/ui/parcels/parcels_page.dart b/lib/ui/parcels/parcels_page.dart index e588202..868f6e6 100644 --- a/lib/ui/parcels/parcels_page.dart +++ b/lib/ui/parcels/parcels_page.dart @@ -207,12 +207,12 @@ class _BodyState extends State<_Body> with SingleTickerProviderStateMixin { ); return _AddAccountBanner( expanded: expanded, - onAddAccount: () { - context.read().addAccountTipShown(); + onAddAccount: () async { + await context.read().addAccountTipShown(); widget.onAddAccount?.call(); }, - onClose: () => - context.read().addAccountTipShown(), + onClose: () async => + await context.read().addAccountTipShown(), ); }, ), diff --git a/lib/ui/settings/page/appearance_cubit.dart b/lib/ui/settings/page/appearance_cubit.dart index 4173a77..4c2fd90 100644 --- a/lib/ui/settings/page/appearance_cubit.dart +++ b/lib/ui/settings/page/appearance_cubit.dart @@ -25,10 +25,14 @@ import 'package:libretrack/ui/app_cubit.dart'; part 'appearance_cubit.freezed.dart'; @freezed -class AppearanceState with _$AppearanceState { - const factory AppearanceState.initial( +sealed class AppearanceState with _$AppearanceState { + const factory AppearanceState.initial({ + @Default(null) AppearanceInfo? info, + }) = AppearanceStateInitial; + + const factory AppearanceState.loaded( AppearanceInfo info, - ) = AppearanceStateInitial; + ) = AppearanceStateLoaded; const factory AppearanceState.themeChanged( AppearanceInfo info, @@ -71,53 +75,65 @@ class AppearanceSettingsCubit extends Cubit { this._pref, this._appCubit, this._systemTray, - ) : super( - AppearanceState.initial( - AppearanceInfo( - theme: _pref.theme, - trackingNotify: _pref.trackingNotifications, - locale: _pref.locale, - trackingErrorNotify: _pref.trackingErrorNotifications, - trayIcon: _pref.trayIcon, - ), - ), - ); - - void setTheme(AppThemeType theme) { - _pref.theme = theme; - _appCubit.setTheme(theme); - emit(AppearanceState.themeChanged( - state.info.copyWith(theme: theme), - )); + ) : super(const AppearanceState.initial()); + + Future load() async { + emit( + AppearanceState.loaded(AppearanceInfo( + theme: await _pref.theme, + trackingNotify: await _pref.trackingNotifications, + locale: await _pref.locale, + trackingErrorNotify: await _pref.trackingErrorNotifications, + trayIcon: await _pref.trayIcon, + )), + ); + } + + Future setTheme(AppThemeType theme) async { + if (state.info case final info?) { + await _pref.setTheme(theme); + _appCubit.setTheme(theme); + emit(AppearanceState.themeChanged( + info.copyWith(theme: theme), + )); + } } - void trackingNotify({required bool enable}) { - _pref.trackingNotifications = enable; - emit(AppearanceState.trackingNotifyChanged( - state.info.copyWith(trackingNotify: enable), - )); + Future trackingNotify({required bool enable}) async { + if (state.info case final info?) { + await _pref.setTrackingNotifications(enable); + emit(AppearanceState.trackingNotifyChanged( + info.copyWith(trackingNotify: enable), + )); + } } - void setLocale(AppLocaleType locale) { - _pref.locale = locale; - _appCubit.setLocale(locale); - emit(AppearanceState.localeChanged( - state.info.copyWith(locale: locale), - )); + Future setLocale(AppLocaleType locale) async { + if (state.info case final info?) { + await _pref.setLocale(locale); + _appCubit.setLocale(locale); + emit(AppearanceState.localeChanged( + info.copyWith(locale: locale), + )); + } } - void trackingErrorNotify({required bool enable}) { - _pref.trackingErrorNotifications = enable; - emit(AppearanceState.trackingErrorNotifyChanged( - state.info.copyWith(trackingErrorNotify: enable), - )); + Future trackingErrorNotify({required bool enable}) async { + if (state.info case final info?) { + await _pref.setTrackingErrorNotifications(enable); + emit(AppearanceState.trackingErrorNotifyChanged( + info.copyWith(trackingErrorNotify: enable), + )); + } } Future trayIcon({required bool enable}) async { - _pref.trayIcon = enable; - await _systemTray.switchTrayIcon(enable: enable); - emit(AppearanceState.trayIconChanged( - state.info.copyWith(trayIcon: enable), - )); + if (state.info case final info?) { + await _pref.setTrayIcon(enable); + await _systemTray.switchTrayIcon(enable: enable); + emit(AppearanceState.trayIconChanged( + info.copyWith(trayIcon: enable), + )); + } } } diff --git a/lib/ui/settings/page/appearance_cubit.freezed.dart b/lib/ui/settings/page/appearance_cubit.freezed.dart index b762c03..b018af2 100644 --- a/lib/ui/settings/page/appearance_cubit.freezed.dart +++ b/lib/ui/settings/page/appearance_cubit.freezed.dart @@ -16,10 +16,11 @@ final _privateConstructorUsedError = UnsupportedError( /// @nodoc mixin _$AppearanceState { - AppearanceInfo get info => throw _privateConstructorUsedError; + AppearanceInfo? get info => throw _privateConstructorUsedError; @optionalTypeArgs TResult when({ - required TResult Function(AppearanceInfo info) initial, + required TResult Function(AppearanceInfo? info) initial, + required TResult Function(AppearanceInfo info) loaded, required TResult Function(AppearanceInfo info) themeChanged, required TResult Function(AppearanceInfo info) trackingNotifyChanged, required TResult Function(AppearanceInfo info) localeChanged, @@ -29,7 +30,8 @@ mixin _$AppearanceState { throw _privateConstructorUsedError; @optionalTypeArgs TResult? whenOrNull({ - TResult? Function(AppearanceInfo info)? initial, + TResult? Function(AppearanceInfo? info)? initial, + TResult? Function(AppearanceInfo info)? loaded, TResult? Function(AppearanceInfo info)? themeChanged, TResult? Function(AppearanceInfo info)? trackingNotifyChanged, TResult? Function(AppearanceInfo info)? localeChanged, @@ -39,7 +41,8 @@ mixin _$AppearanceState { throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeWhen({ - TResult Function(AppearanceInfo info)? initial, + TResult Function(AppearanceInfo? info)? initial, + TResult Function(AppearanceInfo info)? loaded, TResult Function(AppearanceInfo info)? themeChanged, TResult Function(AppearanceInfo info)? trackingNotifyChanged, TResult Function(AppearanceInfo info)? localeChanged, @@ -51,6 +54,7 @@ mixin _$AppearanceState { @optionalTypeArgs TResult map({ required TResult Function(AppearanceStateInitial value) initial, + required TResult Function(AppearanceStateLoaded value) loaded, required TResult Function(AppearanceStateThemeChanged value) themeChanged, required TResult Function(AppearanceStateTrackingNotifyChanged value) trackingNotifyChanged, @@ -64,6 +68,7 @@ mixin _$AppearanceState { @optionalTypeArgs TResult? mapOrNull({ TResult? Function(AppearanceStateInitial value)? initial, + TResult? Function(AppearanceStateLoaded value)? loaded, TResult? Function(AppearanceStateThemeChanged value)? themeChanged, TResult? Function(AppearanceStateTrackingNotifyChanged value)? trackingNotifyChanged, @@ -76,6 +81,7 @@ mixin _$AppearanceState { @optionalTypeArgs TResult maybeMap({ TResult Function(AppearanceStateInitial value)? initial, + TResult Function(AppearanceStateLoaded value)? loaded, TResult Function(AppearanceStateThemeChanged value)? themeChanged, TResult Function(AppearanceStateTrackingNotifyChanged value)? trackingNotifyChanged, @@ -102,7 +108,7 @@ abstract class $AppearanceStateCopyWith<$Res> { @useResult $Res call({AppearanceInfo info}); - $AppearanceInfoCopyWith<$Res> get info; + $AppearanceInfoCopyWith<$Res>? get info; } /// @nodoc @@ -124,7 +130,7 @@ class _$AppearanceStateCopyWithImpl<$Res, $Val extends AppearanceState> }) { return _then(_value.copyWith( info: null == info - ? _value.info + ? _value.info! : info // ignore: cast_nullable_to_non_nullable as AppearanceInfo, ) as $Val); @@ -134,8 +140,12 @@ class _$AppearanceStateCopyWithImpl<$Res, $Val extends AppearanceState> /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') - $AppearanceInfoCopyWith<$Res> get info { - return $AppearanceInfoCopyWith<$Res>(_value.info, (value) { + $AppearanceInfoCopyWith<$Res>? get info { + if (_value.info == null) { + return null; + } + + return $AppearanceInfoCopyWith<$Res>(_value.info!, (value) { return _then(_value.copyWith(info: value) as $Val); }); } @@ -150,10 +160,10 @@ abstract class _$$AppearanceStateInitialImplCopyWith<$Res> __$$AppearanceStateInitialImplCopyWithImpl<$Res>; @override @useResult - $Res call({AppearanceInfo info}); + $Res call({AppearanceInfo? info}); @override - $AppearanceInfoCopyWith<$Res> get info; + $AppearanceInfoCopyWith<$Res>? get info; } /// @nodoc @@ -170,13 +180,13 @@ class __$$AppearanceStateInitialImplCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override $Res call({ - Object? info = null, + Object? info = freezed, }) { return _then(_$AppearanceStateInitialImpl( - null == info + info: freezed == info ? _value.info : info // ignore: cast_nullable_to_non_nullable - as AppearanceInfo, + as AppearanceInfo?, )); } } @@ -184,10 +194,11 @@ class __$$AppearanceStateInitialImplCopyWithImpl<$Res> /// @nodoc class _$AppearanceStateInitialImpl implements AppearanceStateInitial { - const _$AppearanceStateInitialImpl(this.info); + const _$AppearanceStateInitialImpl({this.info = null}); @override - final AppearanceInfo info; + @JsonKey() + final AppearanceInfo? info; @override String toString() { @@ -217,7 +228,8 @@ class _$AppearanceStateInitialImpl implements AppearanceStateInitial { @override @optionalTypeArgs TResult when({ - required TResult Function(AppearanceInfo info) initial, + required TResult Function(AppearanceInfo? info) initial, + required TResult Function(AppearanceInfo info) loaded, required TResult Function(AppearanceInfo info) themeChanged, required TResult Function(AppearanceInfo info) trackingNotifyChanged, required TResult Function(AppearanceInfo info) localeChanged, @@ -230,7 +242,8 @@ class _$AppearanceStateInitialImpl implements AppearanceStateInitial { @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function(AppearanceInfo info)? initial, + TResult? Function(AppearanceInfo? info)? initial, + TResult? Function(AppearanceInfo info)? loaded, TResult? Function(AppearanceInfo info)? themeChanged, TResult? Function(AppearanceInfo info)? trackingNotifyChanged, TResult? Function(AppearanceInfo info)? localeChanged, @@ -243,7 +256,8 @@ class _$AppearanceStateInitialImpl implements AppearanceStateInitial { @override @optionalTypeArgs TResult maybeWhen({ - TResult Function(AppearanceInfo info)? initial, + TResult Function(AppearanceInfo? info)? initial, + TResult Function(AppearanceInfo info)? loaded, TResult Function(AppearanceInfo info)? themeChanged, TResult Function(AppearanceInfo info)? trackingNotifyChanged, TResult Function(AppearanceInfo info)? localeChanged, @@ -261,6 +275,7 @@ class _$AppearanceStateInitialImpl implements AppearanceStateInitial { @optionalTypeArgs TResult map({ required TResult Function(AppearanceStateInitial value) initial, + required TResult Function(AppearanceStateLoaded value) loaded, required TResult Function(AppearanceStateThemeChanged value) themeChanged, required TResult Function(AppearanceStateTrackingNotifyChanged value) trackingNotifyChanged, @@ -277,6 +292,7 @@ class _$AppearanceStateInitialImpl implements AppearanceStateInitial { @optionalTypeArgs TResult? mapOrNull({ TResult? Function(AppearanceStateInitial value)? initial, + TResult? Function(AppearanceStateLoaded value)? loaded, TResult? Function(AppearanceStateThemeChanged value)? themeChanged, TResult? Function(AppearanceStateTrackingNotifyChanged value)? trackingNotifyChanged, @@ -292,6 +308,7 @@ class _$AppearanceStateInitialImpl implements AppearanceStateInitial { @optionalTypeArgs TResult maybeMap({ TResult Function(AppearanceStateInitial value)? initial, + TResult Function(AppearanceStateLoaded value)? loaded, TResult Function(AppearanceStateThemeChanged value)? themeChanged, TResult Function(AppearanceStateTrackingNotifyChanged value)? trackingNotifyChanged, @@ -309,11 +326,11 @@ class _$AppearanceStateInitialImpl implements AppearanceStateInitial { } abstract class AppearanceStateInitial implements AppearanceState { - const factory AppearanceStateInitial(final AppearanceInfo info) = + const factory AppearanceStateInitial({final AppearanceInfo? info}) = _$AppearanceStateInitialImpl; @override - AppearanceInfo get info; + AppearanceInfo? get info; /// Create a copy of AppearanceState /// with the given fields replaced by the non-null parameter values. @@ -323,6 +340,203 @@ abstract class AppearanceStateInitial implements AppearanceState { get copyWith => throw _privateConstructorUsedError; } +/// @nodoc +abstract class _$$AppearanceStateLoadedImplCopyWith<$Res> + implements $AppearanceStateCopyWith<$Res> { + factory _$$AppearanceStateLoadedImplCopyWith( + _$AppearanceStateLoadedImpl value, + $Res Function(_$AppearanceStateLoadedImpl) then) = + __$$AppearanceStateLoadedImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({AppearanceInfo info}); + + @override + $AppearanceInfoCopyWith<$Res> get info; +} + +/// @nodoc +class __$$AppearanceStateLoadedImplCopyWithImpl<$Res> + extends _$AppearanceStateCopyWithImpl<$Res, _$AppearanceStateLoadedImpl> + implements _$$AppearanceStateLoadedImplCopyWith<$Res> { + __$$AppearanceStateLoadedImplCopyWithImpl(_$AppearanceStateLoadedImpl _value, + $Res Function(_$AppearanceStateLoadedImpl) _then) + : super(_value, _then); + + /// Create a copy of AppearanceState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? info = null, + }) { + return _then(_$AppearanceStateLoadedImpl( + null == info + ? _value.info + : info // ignore: cast_nullable_to_non_nullable + as AppearanceInfo, + )); + } + + /// Create a copy of AppearanceState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $AppearanceInfoCopyWith<$Res> get info { + return $AppearanceInfoCopyWith<$Res>(_value.info, (value) { + return _then(_value.copyWith(info: value)); + }); + } +} + +/// @nodoc + +class _$AppearanceStateLoadedImpl implements AppearanceStateLoaded { + const _$AppearanceStateLoadedImpl(this.info); + + @override + final AppearanceInfo info; + + @override + String toString() { + return 'AppearanceState.loaded(info: $info)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AppearanceStateLoadedImpl && + (identical(other.info, info) || other.info == info)); + } + + @override + int get hashCode => Object.hash(runtimeType, info); + + /// Create a copy of AppearanceState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$AppearanceStateLoadedImplCopyWith<_$AppearanceStateLoadedImpl> + get copyWith => __$$AppearanceStateLoadedImplCopyWithImpl< + _$AppearanceStateLoadedImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(AppearanceInfo? info) initial, + required TResult Function(AppearanceInfo info) loaded, + required TResult Function(AppearanceInfo info) themeChanged, + required TResult Function(AppearanceInfo info) trackingNotifyChanged, + required TResult Function(AppearanceInfo info) localeChanged, + required TResult Function(AppearanceInfo info) trackingErrorNotifyChanged, + required TResult Function(AppearanceInfo info) trayIconChanged, + }) { + return loaded(info); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(AppearanceInfo? info)? initial, + TResult? Function(AppearanceInfo info)? loaded, + TResult? Function(AppearanceInfo info)? themeChanged, + TResult? Function(AppearanceInfo info)? trackingNotifyChanged, + TResult? Function(AppearanceInfo info)? localeChanged, + TResult? Function(AppearanceInfo info)? trackingErrorNotifyChanged, + TResult? Function(AppearanceInfo info)? trayIconChanged, + }) { + return loaded?.call(info); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(AppearanceInfo? info)? initial, + TResult Function(AppearanceInfo info)? loaded, + TResult Function(AppearanceInfo info)? themeChanged, + TResult Function(AppearanceInfo info)? trackingNotifyChanged, + TResult Function(AppearanceInfo info)? localeChanged, + TResult Function(AppearanceInfo info)? trackingErrorNotifyChanged, + TResult Function(AppearanceInfo info)? trayIconChanged, + required TResult orElse(), + }) { + if (loaded != null) { + return loaded(info); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(AppearanceStateInitial value) initial, + required TResult Function(AppearanceStateLoaded value) loaded, + required TResult Function(AppearanceStateThemeChanged value) themeChanged, + required TResult Function(AppearanceStateTrackingNotifyChanged value) + trackingNotifyChanged, + required TResult Function(AppearanceStateLocaleChanged value) localeChanged, + required TResult Function(AppearanceStateTrackingErrorNotifyChanged value) + trackingErrorNotifyChanged, + required TResult Function(AppearanceStateTrayIconChanged value) + trayIconChanged, + }) { + return loaded(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(AppearanceStateInitial value)? initial, + TResult? Function(AppearanceStateLoaded value)? loaded, + TResult? Function(AppearanceStateThemeChanged value)? themeChanged, + TResult? Function(AppearanceStateTrackingNotifyChanged value)? + trackingNotifyChanged, + TResult? Function(AppearanceStateLocaleChanged value)? localeChanged, + TResult? Function(AppearanceStateTrackingErrorNotifyChanged value)? + trackingErrorNotifyChanged, + TResult? Function(AppearanceStateTrayIconChanged value)? trayIconChanged, + }) { + return loaded?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(AppearanceStateInitial value)? initial, + TResult Function(AppearanceStateLoaded value)? loaded, + TResult Function(AppearanceStateThemeChanged value)? themeChanged, + TResult Function(AppearanceStateTrackingNotifyChanged value)? + trackingNotifyChanged, + TResult Function(AppearanceStateLocaleChanged value)? localeChanged, + TResult Function(AppearanceStateTrackingErrorNotifyChanged value)? + trackingErrorNotifyChanged, + TResult Function(AppearanceStateTrayIconChanged value)? trayIconChanged, + required TResult orElse(), + }) { + if (loaded != null) { + return loaded(this); + } + return orElse(); + } +} + +abstract class AppearanceStateLoaded implements AppearanceState { + const factory AppearanceStateLoaded(final AppearanceInfo info) = + _$AppearanceStateLoadedImpl; + + @override + AppearanceInfo get info; + + /// Create a copy of AppearanceState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$AppearanceStateLoadedImplCopyWith<_$AppearanceStateLoadedImpl> + get copyWith => throw _privateConstructorUsedError; +} + /// @nodoc abstract class _$$AppearanceStateThemeChangedImplCopyWith<$Res> implements $AppearanceStateCopyWith<$Res> { @@ -362,6 +576,16 @@ class __$$AppearanceStateThemeChangedImplCopyWithImpl<$Res> as AppearanceInfo, )); } + + /// Create a copy of AppearanceState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $AppearanceInfoCopyWith<$Res> get info { + return $AppearanceInfoCopyWith<$Res>(_value.info, (value) { + return _then(_value.copyWith(info: value)); + }); + } } /// @nodoc @@ -400,7 +624,8 @@ class _$AppearanceStateThemeChangedImpl implements AppearanceStateThemeChanged { @override @optionalTypeArgs TResult when({ - required TResult Function(AppearanceInfo info) initial, + required TResult Function(AppearanceInfo? info) initial, + required TResult Function(AppearanceInfo info) loaded, required TResult Function(AppearanceInfo info) themeChanged, required TResult Function(AppearanceInfo info) trackingNotifyChanged, required TResult Function(AppearanceInfo info) localeChanged, @@ -413,7 +638,8 @@ class _$AppearanceStateThemeChangedImpl implements AppearanceStateThemeChanged { @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function(AppearanceInfo info)? initial, + TResult? Function(AppearanceInfo? info)? initial, + TResult? Function(AppearanceInfo info)? loaded, TResult? Function(AppearanceInfo info)? themeChanged, TResult? Function(AppearanceInfo info)? trackingNotifyChanged, TResult? Function(AppearanceInfo info)? localeChanged, @@ -426,7 +652,8 @@ class _$AppearanceStateThemeChangedImpl implements AppearanceStateThemeChanged { @override @optionalTypeArgs TResult maybeWhen({ - TResult Function(AppearanceInfo info)? initial, + TResult Function(AppearanceInfo? info)? initial, + TResult Function(AppearanceInfo info)? loaded, TResult Function(AppearanceInfo info)? themeChanged, TResult Function(AppearanceInfo info)? trackingNotifyChanged, TResult Function(AppearanceInfo info)? localeChanged, @@ -444,6 +671,7 @@ class _$AppearanceStateThemeChangedImpl implements AppearanceStateThemeChanged { @optionalTypeArgs TResult map({ required TResult Function(AppearanceStateInitial value) initial, + required TResult Function(AppearanceStateLoaded value) loaded, required TResult Function(AppearanceStateThemeChanged value) themeChanged, required TResult Function(AppearanceStateTrackingNotifyChanged value) trackingNotifyChanged, @@ -460,6 +688,7 @@ class _$AppearanceStateThemeChangedImpl implements AppearanceStateThemeChanged { @optionalTypeArgs TResult? mapOrNull({ TResult? Function(AppearanceStateInitial value)? initial, + TResult? Function(AppearanceStateLoaded value)? loaded, TResult? Function(AppearanceStateThemeChanged value)? themeChanged, TResult? Function(AppearanceStateTrackingNotifyChanged value)? trackingNotifyChanged, @@ -475,6 +704,7 @@ class _$AppearanceStateThemeChangedImpl implements AppearanceStateThemeChanged { @optionalTypeArgs TResult maybeMap({ TResult Function(AppearanceStateInitial value)? initial, + TResult Function(AppearanceStateLoaded value)? loaded, TResult Function(AppearanceStateThemeChanged value)? themeChanged, TResult Function(AppearanceStateTrackingNotifyChanged value)? trackingNotifyChanged, @@ -545,6 +775,16 @@ class __$$AppearanceStateTrackingNotifyChangedImplCopyWithImpl<$Res> as AppearanceInfo, )); } + + /// Create a copy of AppearanceState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $AppearanceInfoCopyWith<$Res> get info { + return $AppearanceInfoCopyWith<$Res>(_value.info, (value) { + return _then(_value.copyWith(info: value)); + }); + } } /// @nodoc @@ -585,7 +825,8 @@ class _$AppearanceStateTrackingNotifyChangedImpl @override @optionalTypeArgs TResult when({ - required TResult Function(AppearanceInfo info) initial, + required TResult Function(AppearanceInfo? info) initial, + required TResult Function(AppearanceInfo info) loaded, required TResult Function(AppearanceInfo info) themeChanged, required TResult Function(AppearanceInfo info) trackingNotifyChanged, required TResult Function(AppearanceInfo info) localeChanged, @@ -598,7 +839,8 @@ class _$AppearanceStateTrackingNotifyChangedImpl @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function(AppearanceInfo info)? initial, + TResult? Function(AppearanceInfo? info)? initial, + TResult? Function(AppearanceInfo info)? loaded, TResult? Function(AppearanceInfo info)? themeChanged, TResult? Function(AppearanceInfo info)? trackingNotifyChanged, TResult? Function(AppearanceInfo info)? localeChanged, @@ -611,7 +853,8 @@ class _$AppearanceStateTrackingNotifyChangedImpl @override @optionalTypeArgs TResult maybeWhen({ - TResult Function(AppearanceInfo info)? initial, + TResult Function(AppearanceInfo? info)? initial, + TResult Function(AppearanceInfo info)? loaded, TResult Function(AppearanceInfo info)? themeChanged, TResult Function(AppearanceInfo info)? trackingNotifyChanged, TResult Function(AppearanceInfo info)? localeChanged, @@ -629,6 +872,7 @@ class _$AppearanceStateTrackingNotifyChangedImpl @optionalTypeArgs TResult map({ required TResult Function(AppearanceStateInitial value) initial, + required TResult Function(AppearanceStateLoaded value) loaded, required TResult Function(AppearanceStateThemeChanged value) themeChanged, required TResult Function(AppearanceStateTrackingNotifyChanged value) trackingNotifyChanged, @@ -645,6 +889,7 @@ class _$AppearanceStateTrackingNotifyChangedImpl @optionalTypeArgs TResult? mapOrNull({ TResult? Function(AppearanceStateInitial value)? initial, + TResult? Function(AppearanceStateLoaded value)? loaded, TResult? Function(AppearanceStateThemeChanged value)? themeChanged, TResult? Function(AppearanceStateTrackingNotifyChanged value)? trackingNotifyChanged, @@ -660,6 +905,7 @@ class _$AppearanceStateTrackingNotifyChangedImpl @optionalTypeArgs TResult maybeMap({ TResult Function(AppearanceStateInitial value)? initial, + TResult Function(AppearanceStateLoaded value)? loaded, TResult Function(AppearanceStateThemeChanged value)? themeChanged, TResult Function(AppearanceStateTrackingNotifyChanged value)? trackingNotifyChanged, @@ -731,6 +977,16 @@ class __$$AppearanceStateLocaleChangedImplCopyWithImpl<$Res> as AppearanceInfo, )); } + + /// Create a copy of AppearanceState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $AppearanceInfoCopyWith<$Res> get info { + return $AppearanceInfoCopyWith<$Res>(_value.info, (value) { + return _then(_value.copyWith(info: value)); + }); + } } /// @nodoc @@ -771,7 +1027,8 @@ class _$AppearanceStateLocaleChangedImpl @override @optionalTypeArgs TResult when({ - required TResult Function(AppearanceInfo info) initial, + required TResult Function(AppearanceInfo? info) initial, + required TResult Function(AppearanceInfo info) loaded, required TResult Function(AppearanceInfo info) themeChanged, required TResult Function(AppearanceInfo info) trackingNotifyChanged, required TResult Function(AppearanceInfo info) localeChanged, @@ -784,7 +1041,8 @@ class _$AppearanceStateLocaleChangedImpl @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function(AppearanceInfo info)? initial, + TResult? Function(AppearanceInfo? info)? initial, + TResult? Function(AppearanceInfo info)? loaded, TResult? Function(AppearanceInfo info)? themeChanged, TResult? Function(AppearanceInfo info)? trackingNotifyChanged, TResult? Function(AppearanceInfo info)? localeChanged, @@ -797,7 +1055,8 @@ class _$AppearanceStateLocaleChangedImpl @override @optionalTypeArgs TResult maybeWhen({ - TResult Function(AppearanceInfo info)? initial, + TResult Function(AppearanceInfo? info)? initial, + TResult Function(AppearanceInfo info)? loaded, TResult Function(AppearanceInfo info)? themeChanged, TResult Function(AppearanceInfo info)? trackingNotifyChanged, TResult Function(AppearanceInfo info)? localeChanged, @@ -815,6 +1074,7 @@ class _$AppearanceStateLocaleChangedImpl @optionalTypeArgs TResult map({ required TResult Function(AppearanceStateInitial value) initial, + required TResult Function(AppearanceStateLoaded value) loaded, required TResult Function(AppearanceStateThemeChanged value) themeChanged, required TResult Function(AppearanceStateTrackingNotifyChanged value) trackingNotifyChanged, @@ -831,6 +1091,7 @@ class _$AppearanceStateLocaleChangedImpl @optionalTypeArgs TResult? mapOrNull({ TResult? Function(AppearanceStateInitial value)? initial, + TResult? Function(AppearanceStateLoaded value)? loaded, TResult? Function(AppearanceStateThemeChanged value)? themeChanged, TResult? Function(AppearanceStateTrackingNotifyChanged value)? trackingNotifyChanged, @@ -846,6 +1107,7 @@ class _$AppearanceStateLocaleChangedImpl @optionalTypeArgs TResult maybeMap({ TResult Function(AppearanceStateInitial value)? initial, + TResult Function(AppearanceStateLoaded value)? loaded, TResult Function(AppearanceStateThemeChanged value)? themeChanged, TResult Function(AppearanceStateTrackingNotifyChanged value)? trackingNotifyChanged, @@ -917,6 +1179,16 @@ class __$$AppearanceStateTrackingErrorNotifyChangedImplCopyWithImpl<$Res> as AppearanceInfo, )); } + + /// Create a copy of AppearanceState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $AppearanceInfoCopyWith<$Res> get info { + return $AppearanceInfoCopyWith<$Res>(_value.info, (value) { + return _then(_value.copyWith(info: value)); + }); + } } /// @nodoc @@ -959,7 +1231,8 @@ class _$AppearanceStateTrackingErrorNotifyChangedImpl @override @optionalTypeArgs TResult when({ - required TResult Function(AppearanceInfo info) initial, + required TResult Function(AppearanceInfo? info) initial, + required TResult Function(AppearanceInfo info) loaded, required TResult Function(AppearanceInfo info) themeChanged, required TResult Function(AppearanceInfo info) trackingNotifyChanged, required TResult Function(AppearanceInfo info) localeChanged, @@ -972,7 +1245,8 @@ class _$AppearanceStateTrackingErrorNotifyChangedImpl @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function(AppearanceInfo info)? initial, + TResult? Function(AppearanceInfo? info)? initial, + TResult? Function(AppearanceInfo info)? loaded, TResult? Function(AppearanceInfo info)? themeChanged, TResult? Function(AppearanceInfo info)? trackingNotifyChanged, TResult? Function(AppearanceInfo info)? localeChanged, @@ -985,7 +1259,8 @@ class _$AppearanceStateTrackingErrorNotifyChangedImpl @override @optionalTypeArgs TResult maybeWhen({ - TResult Function(AppearanceInfo info)? initial, + TResult Function(AppearanceInfo? info)? initial, + TResult Function(AppearanceInfo info)? loaded, TResult Function(AppearanceInfo info)? themeChanged, TResult Function(AppearanceInfo info)? trackingNotifyChanged, TResult Function(AppearanceInfo info)? localeChanged, @@ -1003,6 +1278,7 @@ class _$AppearanceStateTrackingErrorNotifyChangedImpl @optionalTypeArgs TResult map({ required TResult Function(AppearanceStateInitial value) initial, + required TResult Function(AppearanceStateLoaded value) loaded, required TResult Function(AppearanceStateThemeChanged value) themeChanged, required TResult Function(AppearanceStateTrackingNotifyChanged value) trackingNotifyChanged, @@ -1019,6 +1295,7 @@ class _$AppearanceStateTrackingErrorNotifyChangedImpl @optionalTypeArgs TResult? mapOrNull({ TResult? Function(AppearanceStateInitial value)? initial, + TResult? Function(AppearanceStateLoaded value)? loaded, TResult? Function(AppearanceStateThemeChanged value)? themeChanged, TResult? Function(AppearanceStateTrackingNotifyChanged value)? trackingNotifyChanged, @@ -1034,6 +1311,7 @@ class _$AppearanceStateTrackingErrorNotifyChangedImpl @optionalTypeArgs TResult maybeMap({ TResult Function(AppearanceStateInitial value)? initial, + TResult Function(AppearanceStateLoaded value)? loaded, TResult Function(AppearanceStateThemeChanged value)? themeChanged, TResult Function(AppearanceStateTrackingNotifyChanged value)? trackingNotifyChanged, @@ -1107,6 +1385,16 @@ class __$$AppearanceStateTrayIconChangedImplCopyWithImpl<$Res> as AppearanceInfo, )); } + + /// Create a copy of AppearanceState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $AppearanceInfoCopyWith<$Res> get info { + return $AppearanceInfoCopyWith<$Res>(_value.info, (value) { + return _then(_value.copyWith(info: value)); + }); + } } /// @nodoc @@ -1147,7 +1435,8 @@ class _$AppearanceStateTrayIconChangedImpl @override @optionalTypeArgs TResult when({ - required TResult Function(AppearanceInfo info) initial, + required TResult Function(AppearanceInfo? info) initial, + required TResult Function(AppearanceInfo info) loaded, required TResult Function(AppearanceInfo info) themeChanged, required TResult Function(AppearanceInfo info) trackingNotifyChanged, required TResult Function(AppearanceInfo info) localeChanged, @@ -1160,7 +1449,8 @@ class _$AppearanceStateTrayIconChangedImpl @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function(AppearanceInfo info)? initial, + TResult? Function(AppearanceInfo? info)? initial, + TResult? Function(AppearanceInfo info)? loaded, TResult? Function(AppearanceInfo info)? themeChanged, TResult? Function(AppearanceInfo info)? trackingNotifyChanged, TResult? Function(AppearanceInfo info)? localeChanged, @@ -1173,7 +1463,8 @@ class _$AppearanceStateTrayIconChangedImpl @override @optionalTypeArgs TResult maybeWhen({ - TResult Function(AppearanceInfo info)? initial, + TResult Function(AppearanceInfo? info)? initial, + TResult Function(AppearanceInfo info)? loaded, TResult Function(AppearanceInfo info)? themeChanged, TResult Function(AppearanceInfo info)? trackingNotifyChanged, TResult Function(AppearanceInfo info)? localeChanged, @@ -1191,6 +1482,7 @@ class _$AppearanceStateTrayIconChangedImpl @optionalTypeArgs TResult map({ required TResult Function(AppearanceStateInitial value) initial, + required TResult Function(AppearanceStateLoaded value) loaded, required TResult Function(AppearanceStateThemeChanged value) themeChanged, required TResult Function(AppearanceStateTrackingNotifyChanged value) trackingNotifyChanged, @@ -1207,6 +1499,7 @@ class _$AppearanceStateTrayIconChangedImpl @optionalTypeArgs TResult? mapOrNull({ TResult? Function(AppearanceStateInitial value)? initial, + TResult? Function(AppearanceStateLoaded value)? loaded, TResult? Function(AppearanceStateThemeChanged value)? themeChanged, TResult? Function(AppearanceStateTrackingNotifyChanged value)? trackingNotifyChanged, @@ -1222,6 +1515,7 @@ class _$AppearanceStateTrayIconChangedImpl @optionalTypeArgs TResult maybeMap({ TResult Function(AppearanceStateInitial value)? initial, + TResult Function(AppearanceStateLoaded value)? loaded, TResult Function(AppearanceStateThemeChanged value)? themeChanged, TResult Function(AppearanceStateTrackingNotifyChanged value)? trackingNotifyChanged, diff --git a/lib/ui/settings/page/appearance_page.dart b/lib/ui/settings/page/appearance_page.dart index 1ba89cb..cbc9db1 100644 --- a/lib/ui/settings/page/appearance_page.dart +++ b/lib/ui/settings/page/appearance_page.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2021 Yaroslav Pronin +// Copyright (C) 2021-2024 Yaroslav Pronin // Copyright (C) 2021 Insurgo Inc. // // This file is part of LibreTrack. @@ -38,31 +38,40 @@ class AppearanceSettingsPage extends StatelessWidget { appBar: AppBar( title: Text(S.of(context).settingsAppearance), ), - body: SettingsList( - key: const PageStorageKey('appearance_list'), - groups: [ - SettingsListGroup( - items: [ - _buildThemeOption(context), - _buildLanguageOption(context), - ], - ), - SettingsListGroup( - title: S.of(context).settingsNotificationsSection, - items: [ - _buildTrackingNotifyOption(context), - _buildTrackingErrorNotifyOption(context), - ], - ), - // TODO: Windows/macOS support - if (getIt().isLinux) - SettingsListGroup( - title: S.of(context).settingsDesktopSection, - items: [ - _buildSystemTrayIconOption(context), - ], - ), - ], + body: BlocBuilder( + buildWhen: (prev, next) => next is AppearanceStateLoaded, + builder: (context, state) { + return switch (state) { + AppearanceStateInitial() => + const Center(child: CircularProgressIndicator()), + _ => SettingsList( + key: const PageStorageKey('appearance_list'), + groups: [ + SettingsListGroup( + items: [ + _buildThemeOption(context), + _buildLanguageOption(context), + ], + ), + SettingsListGroup( + title: S.of(context).settingsNotificationsSection, + items: [ + _buildTrackingNotifyOption(context), + _buildTrackingErrorNotifyOption(context), + ], + ), + // TODO: Windows/macOS support + if (getIt().isLinux) + SettingsListGroup( + title: S.of(context).settingsDesktopSection, + items: [ + _buildSystemTrayIconOption(context), + ], + ), + ], + ), + }; + }, ), ); } @@ -74,7 +83,7 @@ class AppearanceSettingsPage extends StatelessWidget { buildWhen: (prev, current) => current is AppearanceStateThemeChanged, builder: (context, state) { return Text( - state.info.theme.toLocalizedString(context), + state.info!.theme.toLocalizedString(context), ); }, ), @@ -98,10 +107,12 @@ class AppearanceSettingsPage extends StatelessWidget { current is AppearanceStateThemeChanged, builder: (context, state) { return _ThemeList( - initialValue: state.info.theme, - onSelected: (theme) { - cubit.setTheme(theme); - Navigator.of(context).pop(); + initialValue: state.info!.theme, + onSelected: (theme) async { + await cubit.setTheme(theme); + if (context.mounted) { + Navigator.of(context).pop(); + } }, ); }, @@ -130,10 +141,10 @@ class AppearanceSettingsPage extends StatelessWidget { }, builder: (context, state) { return SwitchListTile( - value: state.info.trackingNotify, + value: state.info!.trackingNotify, title: Text(S.of(context).settingsTrackingNotifications), secondary: Icon(MdiIcons.cubeSend), - onChanged: (value) => context + onChanged: (value) async => await context .read() .trackingNotify(enable: value), ); @@ -148,7 +159,7 @@ class AppearanceSettingsPage extends StatelessWidget { buildWhen: (prev, current) => current is AppearanceStateLocaleChanged, builder: (context, state) { return Text( - state.info.locale.when( + state.info!.locale.when( system: () => S.of(context).settingsSystemLanguageOption, inner: (locale) => UiUtils.localeToLocalizedStr( locale.toLocaleString(), @@ -177,10 +188,12 @@ class AppearanceSettingsPage extends StatelessWidget { current is AppearanceStateLocaleChanged, builder: (context, state) { return _LanguageList( - initialValue: state.info.locale, - onSelected: (locale) { - cubit.setLocale(locale); - Navigator.of(context).pop(); + initialValue: state.info!.locale, + onSelected: (locale) async { + await cubit.setLocale(locale); + if (context.mounted) { + Navigator.of(context).pop(); + } }, ); }, @@ -209,10 +222,10 @@ class AppearanceSettingsPage extends StatelessWidget { }, builder: (context, state) { return SwitchListTile( - value: state.info.trackingErrorNotify, + value: state.info!.trackingErrorNotify, title: Text(S.of(context).settingsTrackingErrorNotifications), secondary: const Icon(Icons.error_outline), - onChanged: (value) => context + onChanged: (value) async => await context .read() .trackingErrorNotify(enable: value), ); @@ -228,7 +241,7 @@ class AppearanceSettingsPage extends StatelessWidget { builder: (context, state) { final textTheme = Theme.of(context).textTheme; return SwitchListTile( - value: state.info.trayIcon, + value: state.info!.trayIcon, title: Text(S.of(context).settingsSystemTrayIcon), subtitle: getIt().isLinux ? LinkText( @@ -239,8 +252,9 @@ class AppearanceSettingsPage extends StatelessWidget { ) : null, secondary: const Icon(Icons.monitor), - onChanged: (value) => - context.read().trayIcon(enable: value), + onChanged: (value) async => await context + .read() + .trayIcon(enable: value), ); }, ); diff --git a/lib/ui/settings/page/behavior_cubit.dart b/lib/ui/settings/page/behavior_cubit.dart index bb70268..150d158 100644 --- a/lib/ui/settings/page/behavior_cubit.dart +++ b/lib/ui/settings/page/behavior_cubit.dart @@ -25,9 +25,13 @@ part 'behavior_cubit.freezed.dart'; @freezed class BehaviorState with _$BehaviorState { - const factory BehaviorState.initial( + const factory BehaviorState.initial({ + @Default(null) BehaviorInfo? info, + }) = BehaviorStateInitial; + + const factory BehaviorState.loaded( BehaviorInfo info, - ) = BehaviorStateInitial; + ) = BehaviorStateLoaded; const factory BehaviorState.trackingLimitChanged( BehaviorInfo info, @@ -60,45 +64,59 @@ class BehaviorSettingsCubit extends Cubit { final AppSettings _pref; final TrackingScheduler _trackingScheduler; - BehaviorSettingsCubit(this._pref, this._trackingScheduler) - : super( - BehaviorState.initial( - BehaviorInfo( - trackingLimit: _pref.trackingFrequencyLimit, - autoTracking: _pref.autoTracking, - autoTrackingFreq: _pref.autoTrackingFreq, - trackingHistorySize: _pref.trackingHistorySize, - ), - ), - ); - - void setTrackingLimit(TrackingFreqLimit limit) { - _pref.trackingFrequencyLimit = limit; - emit(BehaviorState.trackingLimitChanged( - state.info.copyWith(trackingLimit: limit), - )); + BehaviorSettingsCubit( + this._pref, + this._trackingScheduler, + ) : super(const BehaviorState.initial()); + + Future load() async { + emit( + BehaviorState.autoTrackingChanged( + BehaviorInfo( + trackingLimit: await _pref.trackingFrequencyLimit, + autoTracking: await _pref.autoTracking, + autoTrackingFreq: await _pref.autoTrackingFreq, + trackingHistorySize: await _pref.trackingHistorySize, + ), + ), + ); + } + + Future setTrackingLimit(TrackingFreqLimit limit) async { + if (state.info case final info?) { + await _pref.setTrackingFrequencyLimit(limit); + emit(BehaviorState.trackingLimitChanged( + info.copyWith(trackingLimit: limit), + )); + } } - void autoTracking({required bool enable}) { - _pref.autoTracking = enable; - _trackingScheduler.reenqueueAll(); - emit(BehaviorState.autoTrackingChanged( - state.info.copyWith(autoTracking: enable), - )); + Future autoTracking({required bool enable}) async { + if (state.info case final info?) { + await _pref.setAutoTracking(enable); + await _trackingScheduler.reenqueueAll(); + emit(BehaviorState.autoTrackingChanged( + info.copyWith(autoTracking: enable), + )); + } } - void setAutoTrackingFreq(AutoTrackingFreq freq) { - _pref.autoTrackingFreq = freq; - _trackingScheduler.reenqueueAll(); - emit(BehaviorState.autoTrackingFreqChanged( - state.info.copyWith(autoTrackingFreq: freq), - )); + Future setAutoTrackingFreq(AutoTrackingFreq freq) async { + if (state.info case final info?) { + await _pref.setAutoTrackingFreq(freq); + _trackingScheduler.reenqueueAll(); + emit(BehaviorState.autoTrackingFreqChanged( + info.copyWith(autoTrackingFreq: freq), + )); + } } - void setTrackingHistorySize(int size) { - _pref.trackingHistorySize = size; - emit(BehaviorState.trackingHistorySizeChanged( - state.info.copyWith(trackingHistorySize: size), - )); + Future setTrackingHistorySize(int size) async { + if (state.info case final info?) { + await _pref.setTrackingHistorySize(size); + emit(BehaviorState.trackingHistorySizeChanged( + info.copyWith(trackingHistorySize: size), + )); + } } } diff --git a/lib/ui/settings/page/behavior_cubit.freezed.dart b/lib/ui/settings/page/behavior_cubit.freezed.dart index c40496e..06b8e7b 100644 --- a/lib/ui/settings/page/behavior_cubit.freezed.dart +++ b/lib/ui/settings/page/behavior_cubit.freezed.dart @@ -16,10 +16,11 @@ final _privateConstructorUsedError = UnsupportedError( /// @nodoc mixin _$BehaviorState { - BehaviorInfo get info => throw _privateConstructorUsedError; + BehaviorInfo? get info => throw _privateConstructorUsedError; @optionalTypeArgs TResult when({ - required TResult Function(BehaviorInfo info) initial, + required TResult Function(BehaviorInfo? info) initial, + required TResult Function(BehaviorInfo info) loaded, required TResult Function(BehaviorInfo info) trackingLimitChanged, required TResult Function(BehaviorInfo info) autoTrackingChanged, required TResult Function(BehaviorInfo info) autoTrackingFreqChanged, @@ -28,7 +29,8 @@ mixin _$BehaviorState { throw _privateConstructorUsedError; @optionalTypeArgs TResult? whenOrNull({ - TResult? Function(BehaviorInfo info)? initial, + TResult? Function(BehaviorInfo? info)? initial, + TResult? Function(BehaviorInfo info)? loaded, TResult? Function(BehaviorInfo info)? trackingLimitChanged, TResult? Function(BehaviorInfo info)? autoTrackingChanged, TResult? Function(BehaviorInfo info)? autoTrackingFreqChanged, @@ -37,7 +39,8 @@ mixin _$BehaviorState { throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeWhen({ - TResult Function(BehaviorInfo info)? initial, + TResult Function(BehaviorInfo? info)? initial, + TResult Function(BehaviorInfo info)? loaded, TResult Function(BehaviorInfo info)? trackingLimitChanged, TResult Function(BehaviorInfo info)? autoTrackingChanged, TResult Function(BehaviorInfo info)? autoTrackingFreqChanged, @@ -48,6 +51,7 @@ mixin _$BehaviorState { @optionalTypeArgs TResult map({ required TResult Function(BehaviorStateInitial value) initial, + required TResult Function(BehaviorStateLoaded value) loaded, required TResult Function(BehaviorStateTrackingLimitChanged value) trackingLimitChanged, required TResult Function(BehaviorStateAutoTrackingChanged value) @@ -61,6 +65,7 @@ mixin _$BehaviorState { @optionalTypeArgs TResult? mapOrNull({ TResult? Function(BehaviorStateInitial value)? initial, + TResult? Function(BehaviorStateLoaded value)? loaded, TResult? Function(BehaviorStateTrackingLimitChanged value)? trackingLimitChanged, TResult? Function(BehaviorStateAutoTrackingChanged value)? @@ -74,6 +79,7 @@ mixin _$BehaviorState { @optionalTypeArgs TResult maybeMap({ TResult Function(BehaviorStateInitial value)? initial, + TResult Function(BehaviorStateLoaded value)? loaded, TResult Function(BehaviorStateTrackingLimitChanged value)? trackingLimitChanged, TResult Function(BehaviorStateAutoTrackingChanged value)? @@ -101,7 +107,7 @@ abstract class $BehaviorStateCopyWith<$Res> { @useResult $Res call({BehaviorInfo info}); - $BehaviorInfoCopyWith<$Res> get info; + $BehaviorInfoCopyWith<$Res>? get info; } /// @nodoc @@ -123,7 +129,7 @@ class _$BehaviorStateCopyWithImpl<$Res, $Val extends BehaviorState> }) { return _then(_value.copyWith( info: null == info - ? _value.info + ? _value.info! : info // ignore: cast_nullable_to_non_nullable as BehaviorInfo, ) as $Val); @@ -133,8 +139,12 @@ class _$BehaviorStateCopyWithImpl<$Res, $Val extends BehaviorState> /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') - $BehaviorInfoCopyWith<$Res> get info { - return $BehaviorInfoCopyWith<$Res>(_value.info, (value) { + $BehaviorInfoCopyWith<$Res>? get info { + if (_value.info == null) { + return null; + } + + return $BehaviorInfoCopyWith<$Res>(_value.info!, (value) { return _then(_value.copyWith(info: value) as $Val); }); } @@ -148,10 +158,10 @@ abstract class _$$BehaviorStateInitialImplCopyWith<$Res> __$$BehaviorStateInitialImplCopyWithImpl<$Res>; @override @useResult - $Res call({BehaviorInfo info}); + $Res call({BehaviorInfo? info}); @override - $BehaviorInfoCopyWith<$Res> get info; + $BehaviorInfoCopyWith<$Res>? get info; } /// @nodoc @@ -167,13 +177,13 @@ class __$$BehaviorStateInitialImplCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override $Res call({ - Object? info = null, + Object? info = freezed, }) { return _then(_$BehaviorStateInitialImpl( - null == info + info: freezed == info ? _value.info : info // ignore: cast_nullable_to_non_nullable - as BehaviorInfo, + as BehaviorInfo?, )); } } @@ -181,10 +191,11 @@ class __$$BehaviorStateInitialImplCopyWithImpl<$Res> /// @nodoc class _$BehaviorStateInitialImpl implements BehaviorStateInitial { - const _$BehaviorStateInitialImpl(this.info); + const _$BehaviorStateInitialImpl({this.info = null}); @override - final BehaviorInfo info; + @JsonKey() + final BehaviorInfo? info; @override String toString() { @@ -215,7 +226,8 @@ class _$BehaviorStateInitialImpl implements BehaviorStateInitial { @override @optionalTypeArgs TResult when({ - required TResult Function(BehaviorInfo info) initial, + required TResult Function(BehaviorInfo? info) initial, + required TResult Function(BehaviorInfo info) loaded, required TResult Function(BehaviorInfo info) trackingLimitChanged, required TResult Function(BehaviorInfo info) autoTrackingChanged, required TResult Function(BehaviorInfo info) autoTrackingFreqChanged, @@ -227,7 +239,8 @@ class _$BehaviorStateInitialImpl implements BehaviorStateInitial { @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function(BehaviorInfo info)? initial, + TResult? Function(BehaviorInfo? info)? initial, + TResult? Function(BehaviorInfo info)? loaded, TResult? Function(BehaviorInfo info)? trackingLimitChanged, TResult? Function(BehaviorInfo info)? autoTrackingChanged, TResult? Function(BehaviorInfo info)? autoTrackingFreqChanged, @@ -239,7 +252,8 @@ class _$BehaviorStateInitialImpl implements BehaviorStateInitial { @override @optionalTypeArgs TResult maybeWhen({ - TResult Function(BehaviorInfo info)? initial, + TResult Function(BehaviorInfo? info)? initial, + TResult Function(BehaviorInfo info)? loaded, TResult Function(BehaviorInfo info)? trackingLimitChanged, TResult Function(BehaviorInfo info)? autoTrackingChanged, TResult Function(BehaviorInfo info)? autoTrackingFreqChanged, @@ -256,6 +270,7 @@ class _$BehaviorStateInitialImpl implements BehaviorStateInitial { @optionalTypeArgs TResult map({ required TResult Function(BehaviorStateInitial value) initial, + required TResult Function(BehaviorStateLoaded value) loaded, required TResult Function(BehaviorStateTrackingLimitChanged value) trackingLimitChanged, required TResult Function(BehaviorStateAutoTrackingChanged value) @@ -272,6 +287,7 @@ class _$BehaviorStateInitialImpl implements BehaviorStateInitial { @optionalTypeArgs TResult? mapOrNull({ TResult? Function(BehaviorStateInitial value)? initial, + TResult? Function(BehaviorStateLoaded value)? loaded, TResult? Function(BehaviorStateTrackingLimitChanged value)? trackingLimitChanged, TResult? Function(BehaviorStateAutoTrackingChanged value)? @@ -288,6 +304,7 @@ class _$BehaviorStateInitialImpl implements BehaviorStateInitial { @optionalTypeArgs TResult maybeMap({ TResult Function(BehaviorStateInitial value)? initial, + TResult Function(BehaviorStateLoaded value)? loaded, TResult Function(BehaviorStateTrackingLimitChanged value)? trackingLimitChanged, TResult Function(BehaviorStateAutoTrackingChanged value)? @@ -306,11 +323,11 @@ class _$BehaviorStateInitialImpl implements BehaviorStateInitial { } abstract class BehaviorStateInitial implements BehaviorState { - const factory BehaviorStateInitial(final BehaviorInfo info) = + const factory BehaviorStateInitial({final BehaviorInfo? info}) = _$BehaviorStateInitialImpl; @override - BehaviorInfo get info; + BehaviorInfo? get info; /// Create a copy of BehaviorState /// with the given fields replaced by the non-null parameter values. @@ -320,6 +337,201 @@ abstract class BehaviorStateInitial implements BehaviorState { get copyWith => throw _privateConstructorUsedError; } +/// @nodoc +abstract class _$$BehaviorStateLoadedImplCopyWith<$Res> + implements $BehaviorStateCopyWith<$Res> { + factory _$$BehaviorStateLoadedImplCopyWith(_$BehaviorStateLoadedImpl value, + $Res Function(_$BehaviorStateLoadedImpl) then) = + __$$BehaviorStateLoadedImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({BehaviorInfo info}); + + @override + $BehaviorInfoCopyWith<$Res> get info; +} + +/// @nodoc +class __$$BehaviorStateLoadedImplCopyWithImpl<$Res> + extends _$BehaviorStateCopyWithImpl<$Res, _$BehaviorStateLoadedImpl> + implements _$$BehaviorStateLoadedImplCopyWith<$Res> { + __$$BehaviorStateLoadedImplCopyWithImpl(_$BehaviorStateLoadedImpl _value, + $Res Function(_$BehaviorStateLoadedImpl) _then) + : super(_value, _then); + + /// Create a copy of BehaviorState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? info = null, + }) { + return _then(_$BehaviorStateLoadedImpl( + null == info + ? _value.info + : info // ignore: cast_nullable_to_non_nullable + as BehaviorInfo, + )); + } + + /// Create a copy of BehaviorState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $BehaviorInfoCopyWith<$Res> get info { + return $BehaviorInfoCopyWith<$Res>(_value.info, (value) { + return _then(_value.copyWith(info: value)); + }); + } +} + +/// @nodoc + +class _$BehaviorStateLoadedImpl implements BehaviorStateLoaded { + const _$BehaviorStateLoadedImpl(this.info); + + @override + final BehaviorInfo info; + + @override + String toString() { + return 'BehaviorState.loaded(info: $info)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$BehaviorStateLoadedImpl && + (identical(other.info, info) || other.info == info)); + } + + @override + int get hashCode => Object.hash(runtimeType, info); + + /// Create a copy of BehaviorState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$BehaviorStateLoadedImplCopyWith<_$BehaviorStateLoadedImpl> get copyWith => + __$$BehaviorStateLoadedImplCopyWithImpl<_$BehaviorStateLoadedImpl>( + this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(BehaviorInfo? info) initial, + required TResult Function(BehaviorInfo info) loaded, + required TResult Function(BehaviorInfo info) trackingLimitChanged, + required TResult Function(BehaviorInfo info) autoTrackingChanged, + required TResult Function(BehaviorInfo info) autoTrackingFreqChanged, + required TResult Function(BehaviorInfo info) trackingHistorySizeChanged, + }) { + return loaded(info); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(BehaviorInfo? info)? initial, + TResult? Function(BehaviorInfo info)? loaded, + TResult? Function(BehaviorInfo info)? trackingLimitChanged, + TResult? Function(BehaviorInfo info)? autoTrackingChanged, + TResult? Function(BehaviorInfo info)? autoTrackingFreqChanged, + TResult? Function(BehaviorInfo info)? trackingHistorySizeChanged, + }) { + return loaded?.call(info); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(BehaviorInfo? info)? initial, + TResult Function(BehaviorInfo info)? loaded, + TResult Function(BehaviorInfo info)? trackingLimitChanged, + TResult Function(BehaviorInfo info)? autoTrackingChanged, + TResult Function(BehaviorInfo info)? autoTrackingFreqChanged, + TResult Function(BehaviorInfo info)? trackingHistorySizeChanged, + required TResult orElse(), + }) { + if (loaded != null) { + return loaded(info); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(BehaviorStateInitial value) initial, + required TResult Function(BehaviorStateLoaded value) loaded, + required TResult Function(BehaviorStateTrackingLimitChanged value) + trackingLimitChanged, + required TResult Function(BehaviorStateAutoTrackingChanged value) + autoTrackingChanged, + required TResult Function(BehaviorStateAutoTrackingFreqChanged value) + autoTrackingFreqChanged, + required TResult Function(BehaviorStateTrackingHistorySizeChanged value) + trackingHistorySizeChanged, + }) { + return loaded(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(BehaviorStateInitial value)? initial, + TResult? Function(BehaviorStateLoaded value)? loaded, + TResult? Function(BehaviorStateTrackingLimitChanged value)? + trackingLimitChanged, + TResult? Function(BehaviorStateAutoTrackingChanged value)? + autoTrackingChanged, + TResult? Function(BehaviorStateAutoTrackingFreqChanged value)? + autoTrackingFreqChanged, + TResult? Function(BehaviorStateTrackingHistorySizeChanged value)? + trackingHistorySizeChanged, + }) { + return loaded?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(BehaviorStateInitial value)? initial, + TResult Function(BehaviorStateLoaded value)? loaded, + TResult Function(BehaviorStateTrackingLimitChanged value)? + trackingLimitChanged, + TResult Function(BehaviorStateAutoTrackingChanged value)? + autoTrackingChanged, + TResult Function(BehaviorStateAutoTrackingFreqChanged value)? + autoTrackingFreqChanged, + TResult Function(BehaviorStateTrackingHistorySizeChanged value)? + trackingHistorySizeChanged, + required TResult orElse(), + }) { + if (loaded != null) { + return loaded(this); + } + return orElse(); + } +} + +abstract class BehaviorStateLoaded implements BehaviorState { + const factory BehaviorStateLoaded(final BehaviorInfo info) = + _$BehaviorStateLoadedImpl; + + @override + BehaviorInfo get info; + + /// Create a copy of BehaviorState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$BehaviorStateLoadedImplCopyWith<_$BehaviorStateLoadedImpl> get copyWith => + throw _privateConstructorUsedError; +} + /// @nodoc abstract class _$$BehaviorStateTrackingLimitChangedImplCopyWith<$Res> implements $BehaviorStateCopyWith<$Res> { @@ -359,6 +571,16 @@ class __$$BehaviorStateTrackingLimitChangedImplCopyWithImpl<$Res> as BehaviorInfo, )); } + + /// Create a copy of BehaviorState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $BehaviorInfoCopyWith<$Res> get info { + return $BehaviorInfoCopyWith<$Res>(_value.info, (value) { + return _then(_value.copyWith(info: value)); + }); + } } /// @nodoc @@ -399,7 +621,8 @@ class _$BehaviorStateTrackingLimitChangedImpl @override @optionalTypeArgs TResult when({ - required TResult Function(BehaviorInfo info) initial, + required TResult Function(BehaviorInfo? info) initial, + required TResult Function(BehaviorInfo info) loaded, required TResult Function(BehaviorInfo info) trackingLimitChanged, required TResult Function(BehaviorInfo info) autoTrackingChanged, required TResult Function(BehaviorInfo info) autoTrackingFreqChanged, @@ -411,7 +634,8 @@ class _$BehaviorStateTrackingLimitChangedImpl @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function(BehaviorInfo info)? initial, + TResult? Function(BehaviorInfo? info)? initial, + TResult? Function(BehaviorInfo info)? loaded, TResult? Function(BehaviorInfo info)? trackingLimitChanged, TResult? Function(BehaviorInfo info)? autoTrackingChanged, TResult? Function(BehaviorInfo info)? autoTrackingFreqChanged, @@ -423,7 +647,8 @@ class _$BehaviorStateTrackingLimitChangedImpl @override @optionalTypeArgs TResult maybeWhen({ - TResult Function(BehaviorInfo info)? initial, + TResult Function(BehaviorInfo? info)? initial, + TResult Function(BehaviorInfo info)? loaded, TResult Function(BehaviorInfo info)? trackingLimitChanged, TResult Function(BehaviorInfo info)? autoTrackingChanged, TResult Function(BehaviorInfo info)? autoTrackingFreqChanged, @@ -440,6 +665,7 @@ class _$BehaviorStateTrackingLimitChangedImpl @optionalTypeArgs TResult map({ required TResult Function(BehaviorStateInitial value) initial, + required TResult Function(BehaviorStateLoaded value) loaded, required TResult Function(BehaviorStateTrackingLimitChanged value) trackingLimitChanged, required TResult Function(BehaviorStateAutoTrackingChanged value) @@ -456,6 +682,7 @@ class _$BehaviorStateTrackingLimitChangedImpl @optionalTypeArgs TResult? mapOrNull({ TResult? Function(BehaviorStateInitial value)? initial, + TResult? Function(BehaviorStateLoaded value)? loaded, TResult? Function(BehaviorStateTrackingLimitChanged value)? trackingLimitChanged, TResult? Function(BehaviorStateAutoTrackingChanged value)? @@ -472,6 +699,7 @@ class _$BehaviorStateTrackingLimitChangedImpl @optionalTypeArgs TResult maybeMap({ TResult Function(BehaviorStateInitial value)? initial, + TResult Function(BehaviorStateLoaded value)? loaded, TResult Function(BehaviorStateTrackingLimitChanged value)? trackingLimitChanged, TResult Function(BehaviorStateAutoTrackingChanged value)? @@ -544,6 +772,16 @@ class __$$BehaviorStateAutoTrackingChangedImplCopyWithImpl<$Res> as BehaviorInfo, )); } + + /// Create a copy of BehaviorState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $BehaviorInfoCopyWith<$Res> get info { + return $BehaviorInfoCopyWith<$Res>(_value.info, (value) { + return _then(_value.copyWith(info: value)); + }); + } } /// @nodoc @@ -584,7 +822,8 @@ class _$BehaviorStateAutoTrackingChangedImpl @override @optionalTypeArgs TResult when({ - required TResult Function(BehaviorInfo info) initial, + required TResult Function(BehaviorInfo? info) initial, + required TResult Function(BehaviorInfo info) loaded, required TResult Function(BehaviorInfo info) trackingLimitChanged, required TResult Function(BehaviorInfo info) autoTrackingChanged, required TResult Function(BehaviorInfo info) autoTrackingFreqChanged, @@ -596,7 +835,8 @@ class _$BehaviorStateAutoTrackingChangedImpl @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function(BehaviorInfo info)? initial, + TResult? Function(BehaviorInfo? info)? initial, + TResult? Function(BehaviorInfo info)? loaded, TResult? Function(BehaviorInfo info)? trackingLimitChanged, TResult? Function(BehaviorInfo info)? autoTrackingChanged, TResult? Function(BehaviorInfo info)? autoTrackingFreqChanged, @@ -608,7 +848,8 @@ class _$BehaviorStateAutoTrackingChangedImpl @override @optionalTypeArgs TResult maybeWhen({ - TResult Function(BehaviorInfo info)? initial, + TResult Function(BehaviorInfo? info)? initial, + TResult Function(BehaviorInfo info)? loaded, TResult Function(BehaviorInfo info)? trackingLimitChanged, TResult Function(BehaviorInfo info)? autoTrackingChanged, TResult Function(BehaviorInfo info)? autoTrackingFreqChanged, @@ -625,6 +866,7 @@ class _$BehaviorStateAutoTrackingChangedImpl @optionalTypeArgs TResult map({ required TResult Function(BehaviorStateInitial value) initial, + required TResult Function(BehaviorStateLoaded value) loaded, required TResult Function(BehaviorStateTrackingLimitChanged value) trackingLimitChanged, required TResult Function(BehaviorStateAutoTrackingChanged value) @@ -641,6 +883,7 @@ class _$BehaviorStateAutoTrackingChangedImpl @optionalTypeArgs TResult? mapOrNull({ TResult? Function(BehaviorStateInitial value)? initial, + TResult? Function(BehaviorStateLoaded value)? loaded, TResult? Function(BehaviorStateTrackingLimitChanged value)? trackingLimitChanged, TResult? Function(BehaviorStateAutoTrackingChanged value)? @@ -657,6 +900,7 @@ class _$BehaviorStateAutoTrackingChangedImpl @optionalTypeArgs TResult maybeMap({ TResult Function(BehaviorStateInitial value)? initial, + TResult Function(BehaviorStateLoaded value)? loaded, TResult Function(BehaviorStateTrackingLimitChanged value)? trackingLimitChanged, TResult Function(BehaviorStateAutoTrackingChanged value)? @@ -729,6 +973,16 @@ class __$$BehaviorStateAutoTrackingFreqChangedImplCopyWithImpl<$Res> as BehaviorInfo, )); } + + /// Create a copy of BehaviorState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $BehaviorInfoCopyWith<$Res> get info { + return $BehaviorInfoCopyWith<$Res>(_value.info, (value) { + return _then(_value.copyWith(info: value)); + }); + } } /// @nodoc @@ -769,7 +1023,8 @@ class _$BehaviorStateAutoTrackingFreqChangedImpl @override @optionalTypeArgs TResult when({ - required TResult Function(BehaviorInfo info) initial, + required TResult Function(BehaviorInfo? info) initial, + required TResult Function(BehaviorInfo info) loaded, required TResult Function(BehaviorInfo info) trackingLimitChanged, required TResult Function(BehaviorInfo info) autoTrackingChanged, required TResult Function(BehaviorInfo info) autoTrackingFreqChanged, @@ -781,7 +1036,8 @@ class _$BehaviorStateAutoTrackingFreqChangedImpl @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function(BehaviorInfo info)? initial, + TResult? Function(BehaviorInfo? info)? initial, + TResult? Function(BehaviorInfo info)? loaded, TResult? Function(BehaviorInfo info)? trackingLimitChanged, TResult? Function(BehaviorInfo info)? autoTrackingChanged, TResult? Function(BehaviorInfo info)? autoTrackingFreqChanged, @@ -793,7 +1049,8 @@ class _$BehaviorStateAutoTrackingFreqChangedImpl @override @optionalTypeArgs TResult maybeWhen({ - TResult Function(BehaviorInfo info)? initial, + TResult Function(BehaviorInfo? info)? initial, + TResult Function(BehaviorInfo info)? loaded, TResult Function(BehaviorInfo info)? trackingLimitChanged, TResult Function(BehaviorInfo info)? autoTrackingChanged, TResult Function(BehaviorInfo info)? autoTrackingFreqChanged, @@ -810,6 +1067,7 @@ class _$BehaviorStateAutoTrackingFreqChangedImpl @optionalTypeArgs TResult map({ required TResult Function(BehaviorStateInitial value) initial, + required TResult Function(BehaviorStateLoaded value) loaded, required TResult Function(BehaviorStateTrackingLimitChanged value) trackingLimitChanged, required TResult Function(BehaviorStateAutoTrackingChanged value) @@ -826,6 +1084,7 @@ class _$BehaviorStateAutoTrackingFreqChangedImpl @optionalTypeArgs TResult? mapOrNull({ TResult? Function(BehaviorStateInitial value)? initial, + TResult? Function(BehaviorStateLoaded value)? loaded, TResult? Function(BehaviorStateTrackingLimitChanged value)? trackingLimitChanged, TResult? Function(BehaviorStateAutoTrackingChanged value)? @@ -842,6 +1101,7 @@ class _$BehaviorStateAutoTrackingFreqChangedImpl @optionalTypeArgs TResult maybeMap({ TResult Function(BehaviorStateInitial value)? initial, + TResult Function(BehaviorStateLoaded value)? loaded, TResult Function(BehaviorStateTrackingLimitChanged value)? trackingLimitChanged, TResult Function(BehaviorStateAutoTrackingChanged value)? @@ -914,6 +1174,16 @@ class __$$BehaviorStateTrackingHistorySizeChangedImplCopyWithImpl<$Res> as BehaviorInfo, )); } + + /// Create a copy of BehaviorState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $BehaviorInfoCopyWith<$Res> get info { + return $BehaviorInfoCopyWith<$Res>(_value.info, (value) { + return _then(_value.copyWith(info: value)); + }); + } } /// @nodoc @@ -955,7 +1225,8 @@ class _$BehaviorStateTrackingHistorySizeChangedImpl @override @optionalTypeArgs TResult when({ - required TResult Function(BehaviorInfo info) initial, + required TResult Function(BehaviorInfo? info) initial, + required TResult Function(BehaviorInfo info) loaded, required TResult Function(BehaviorInfo info) trackingLimitChanged, required TResult Function(BehaviorInfo info) autoTrackingChanged, required TResult Function(BehaviorInfo info) autoTrackingFreqChanged, @@ -967,7 +1238,8 @@ class _$BehaviorStateTrackingHistorySizeChangedImpl @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function(BehaviorInfo info)? initial, + TResult? Function(BehaviorInfo? info)? initial, + TResult? Function(BehaviorInfo info)? loaded, TResult? Function(BehaviorInfo info)? trackingLimitChanged, TResult? Function(BehaviorInfo info)? autoTrackingChanged, TResult? Function(BehaviorInfo info)? autoTrackingFreqChanged, @@ -979,7 +1251,8 @@ class _$BehaviorStateTrackingHistorySizeChangedImpl @override @optionalTypeArgs TResult maybeWhen({ - TResult Function(BehaviorInfo info)? initial, + TResult Function(BehaviorInfo? info)? initial, + TResult Function(BehaviorInfo info)? loaded, TResult Function(BehaviorInfo info)? trackingLimitChanged, TResult Function(BehaviorInfo info)? autoTrackingChanged, TResult Function(BehaviorInfo info)? autoTrackingFreqChanged, @@ -996,6 +1269,7 @@ class _$BehaviorStateTrackingHistorySizeChangedImpl @optionalTypeArgs TResult map({ required TResult Function(BehaviorStateInitial value) initial, + required TResult Function(BehaviorStateLoaded value) loaded, required TResult Function(BehaviorStateTrackingLimitChanged value) trackingLimitChanged, required TResult Function(BehaviorStateAutoTrackingChanged value) @@ -1012,6 +1286,7 @@ class _$BehaviorStateTrackingHistorySizeChangedImpl @optionalTypeArgs TResult? mapOrNull({ TResult? Function(BehaviorStateInitial value)? initial, + TResult? Function(BehaviorStateLoaded value)? loaded, TResult? Function(BehaviorStateTrackingLimitChanged value)? trackingLimitChanged, TResult? Function(BehaviorStateAutoTrackingChanged value)? @@ -1028,6 +1303,7 @@ class _$BehaviorStateTrackingHistorySizeChangedImpl @optionalTypeArgs TResult maybeMap({ TResult Function(BehaviorStateInitial value)? initial, + TResult Function(BehaviorStateLoaded value)? loaded, TResult Function(BehaviorStateTrackingLimitChanged value)? trackingLimitChanged, TResult Function(BehaviorStateAutoTrackingChanged value)? diff --git a/lib/ui/settings/page/behavior_page.dart b/lib/ui/settings/page/behavior_page.dart index 8dd1ae3..fefd001 100644 --- a/lib/ui/settings/page/behavior_page.dart +++ b/lib/ui/settings/page/behavior_page.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2021 Yaroslav Pronin +// Copyright (C) 2021-2024 Yaroslav Pronin // Copyright (C) 2021 Insurgo Inc. // // This file is part of LibreTrack. @@ -37,23 +37,32 @@ class BehaviorPage extends StatelessWidget { appBar: AppBar( title: Text(S.of(context).settingsBehavior), ), - body: SettingsList( - key: const PageStorageKey('behavior_list'), - groups: [ - SettingsListGroup( - title: S.of(context).settingsTrackingSection, - items: [ - _buildTrackingFreqLimitOption(context), - _buildTrackingHistorySizeOption(context), - ], - ), - SettingsListGroup( - items: [ - _buildAutoTrackingOption(context), - _buildAutoTrackingFreqOption(context), - ], - ), - ], + body: BlocBuilder( + buildWhen: (prev, next) => next is BehaviorStateLoaded, + builder: (context, state) { + return switch (state) { + BehaviorStateInitial() => + const Center(child: CircularProgressIndicator()), + _ => SettingsList( + key: const PageStorageKey('behavior_list'), + groups: [ + SettingsListGroup( + title: S.of(context).settingsTrackingSection, + items: [ + _buildTrackingFreqLimitOption(context), + _buildTrackingHistorySizeOption(context), + ], + ), + SettingsListGroup( + items: [ + _buildAutoTrackingOption(context), + _buildAutoTrackingFreqOption(context), + ], + ), + ], + ), + }; + }, ), ); } @@ -66,11 +75,11 @@ class BehaviorPage extends StatelessWidget { current is BehaviorStateTrackingLimitChanged, builder: (context, state) { return Text( - state.info.trackingLimit.maybeWhen( + state.info!.trackingLimit.maybeWhen( unlimited: () => - state.info.trackingLimit.toLocalizedString(context), + state.info!.trackingLimit.toLocalizedString(context), orElse: () => S.of(context).settingsTrackingFreqLimitSummary( - state.info.trackingLimit.toLocalizedString(context), + state.info!.trackingLimit.toLocalizedString(context), ), ), ); @@ -96,10 +105,12 @@ class BehaviorPage extends StatelessWidget { current is BehaviorStateTrackingLimitChanged, builder: (context, state) { return _TrackingLimitList( - initialValue: state.info.trackingLimit, - onSelected: (limit) { - cubit.setTrackingLimit(limit); - Navigator.of(context).pop(); + initialValue: state.info!.trackingLimit, + onSelected: (limit) async { + await cubit.setTrackingLimit(limit); + if (context.mounted) { + Navigator.of(context).pop(); + } }, ); }, @@ -126,11 +137,13 @@ class BehaviorPage extends StatelessWidget { buildWhen: (prev, current) => current is BehaviorStateAutoTrackingChanged, builder: (context, state) { return SwitchListTile( - value: state.info.autoTracking, + value: state.info!.autoTracking, title: Text(S.of(context).settingsAutoTracking), secondary: const Icon(Icons.refresh), - onChanged: (value) { - context.read().autoTracking(enable: value); + onChanged: (value) async { + await context + .read() + .autoTracking(enable: value); }, ); }, @@ -143,9 +156,9 @@ class BehaviorPage extends StatelessWidget { current is BehaviorStateAutoTrackingFreqChanged || current is BehaviorStateAutoTrackingChanged, builder: (context, state) { - final freq = state.info.autoTrackingFreq.toLocalizedString(context); + final freq = state.info!.autoTrackingFreq.toLocalizedString(context); return ListTile( - enabled: state.info.autoTracking, + enabled: state.info!.autoTracking, isThreeLine: true, title: Text(S.of(context).settingsAutoTrackingFreq), subtitle: Text( @@ -173,10 +186,12 @@ class BehaviorPage extends StatelessWidget { current is BehaviorStateAutoTrackingFreqChanged, builder: (context, state) { return _AutoTrackingFreqList( - initialValue: state.info.autoTrackingFreq, - onSelected: (limit) { - cubit.setAutoTrackingFreq(limit); - Navigator.of(context).pop(); + initialValue: state.info!.autoTrackingFreq, + onSelected: (limit) async { + await cubit.setAutoTrackingFreq(limit); + if (context.mounted) { + Navigator.of(context).pop(); + } }, ); }, @@ -205,7 +220,7 @@ class BehaviorPage extends StatelessWidget { buildWhen: (prev, current) => current is BehaviorStateTrackingHistorySizeChanged, builder: (context, state) { - final size = state.info.trackingHistorySize; + final size = state.info!.trackingHistorySize; return Text( '$size\n\n${S.of(context).settingsTrackingHistorySizeDescr}', ); @@ -227,8 +242,9 @@ class BehaviorPage extends StatelessWidget { current is BehaviorStateTrackingHistorySizeChanged, builder: (context, state) { return _TrackingHistoryDialog( - initialValue: state.info.trackingHistorySize, - onChanged: (value) => cubit.setTrackingHistorySize(value), + initialValue: state.info!.trackingHistorySize, + onChanged: (value) async => + await cubit.setTrackingHistorySize(value), ); }, ), diff --git a/lib/ui/settings/settings_page.dart b/lib/ui/settings/settings_page.dart index a66a4cb..a12f2fe 100644 --- a/lib/ui/settings/settings_page.dart +++ b/lib/ui/settings/settings_page.dart @@ -18,6 +18,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:libretrack/ui/app_router.dart'; import 'package:libretrack/ui/settings/settings.dart'; @@ -54,6 +55,13 @@ class SettingsPageState extends State { void initState() { super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) async { + await Future.wait([ + context.read().load(), + context.read().load(), + ]); + }); + _routeInfoProvider = PlatformRouteInformationProvider( initialRouteInformation: _routeInfoParser.restoreRouteInformation( const AppRoutePath.settings(), diff --git a/test/platform/system_tray_test.dart b/test/platform/system_tray_test.dart index 7f62956..f866c54 100644 --- a/test/platform/system_tray_test.dart +++ b/test/platform/system_tray_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2021 Yaroslav Pronin +// Copyright (C) 2021-2024 Yaroslav Pronin // Copyright (C) 2021 Insurgo Inc. // // This file is part of LibreTrack. @@ -56,9 +56,9 @@ void main() { log.clear(); }); - test('Init', () { - when(() => mockPref.trayIcon).thenReturn(true); - systemTray.init(); + test('Init', () async { + when(() => mockPref.trayIcon).thenAnswer((_) async => true); + await systemTray.init(); expect(log, [ isMethodCall('enableTrayIcon', arguments: null), ]); diff --git a/test/settings/shared_pref_migration_test.dart b/test/settings/shared_pref_migration_test.dart new file mode 100644 index 0000000..615aec7 --- /dev/null +++ b/test/settings/shared_pref_migration_test.dart @@ -0,0 +1,119 @@ +// Copyright (C) 2024 Yaroslav Pronin +// +// This file is part of LibreTrack. +// +// LibreTrack is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// LibreTrack is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with LibreTrack. If not, see . + +import 'package:flutter_test/flutter_test.dart'; +import 'package:libretrack/core/settings/shared_pref_migrator.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +// ignore: depend_on_referenced_packages +import 'package:shared_preferences_platform_interface/in_memory_shared_preferences_async.dart'; +// ignore: depend_on_referenced_packages +import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart'; + +void main() { + group('SharedPreferences migration |', () { + late SharedPreferencesAsync sharedPref; + late SharedPreferences sharedPrefOld; + late SharedPreferences mockSharedPrefOld; + + setUpAll(() async { + SharedPreferences.setMockInitialValues({}); + SharedPreferencesAsyncPlatform.instance = + InMemorySharedPreferencesAsync.empty(); + + sharedPref = SharedPreferencesAsync(); + sharedPrefOld = await SharedPreferences.getInstance(); + mockSharedPrefOld = _MockSharedPreferences(); + }); + + tearDownAll(() { + sharedPref.clear(); + sharedPrefOld.clear(); + }); + + test('Migrate', () async { + sharedPref + ..setBool('bool1', true) + ..setBool('bool2', false) + ..setDouble('double1', 1.1) + ..setDouble('double2', 1.2) + ..setInt('int1', 1) + ..setInt('int2', 2) + ..setString('string1', '1') + ..setString('string2', '2') + ..setStringList('stringList1', ['1', '2', '3']) + ..setStringList('stringList2', ['4', '5', '6']); + + final migrator = SharedPreferencesMigrator( + oldPrefs: sharedPrefOld, + newPrefs: sharedPref, + ); + await migrator.migrate(); + expect(migrator.didMigrate, isTrue); + expect( + sharedPrefOld.getBool('shared_preferences_package_did_migrate'), + isTrue, + ); + + expect(await sharedPref.getBool('bool1'), true); + expect(await sharedPref.getBool('bool2'), false); + expect(await sharedPref.getDouble('double1'), 1.1); + expect(await sharedPref.getDouble('double2'), 1.2); + expect(await sharedPref.getInt('int1'), 1); + expect(await sharedPref.getInt('int2'), 2); + expect(await sharedPref.getString('string1'), '1'); + expect(await sharedPref.getString('string2'), '2'); + expect(await sharedPref.getStringList('stringList1'), ['1', '2', '3']); + expect(await sharedPref.getStringList('stringList2'), ['4', '5', '6']); + }); + + test('Already migrate', () async { + final migrator = SharedPreferencesMigrator( + oldPrefs: mockSharedPrefOld, + newPrefs: sharedPref, + ); + expect( + sharedPrefOld.getBool('shared_preferences_package_did_migrate'), + isTrue, + ); + + when(() => mockSharedPrefOld.getBool( + 'shared_preferences_package_did_migrate', + )).thenReturn(true); + when(() => mockSharedPrefOld.setBool(any(), any())) + .thenAnswer((_) async => true); + when(() => mockSharedPrefOld.setDouble(any(), any())) + .thenAnswer((_) async => true); + when(() => mockSharedPrefOld.setInt(any(), any())) + .thenAnswer((_) async => true); + when(() => mockSharedPrefOld.setString(any(), any())) + .thenAnswer((_) async => true); + when(() => mockSharedPrefOld.setStringList(any(), any())) + .thenAnswer((_) async => true); + + await migrator.migrate(); + + verifyNever(() => mockSharedPrefOld.setBool(any(), any())); + verifyNever(() => mockSharedPrefOld.setDouble(any(), any())); + verifyNever(() => mockSharedPrefOld.setInt(any(), any())); + verifyNever(() => mockSharedPrefOld.setString(any(), any())); + verifyNever(() => mockSharedPrefOld.setStringList(any(), any())); + }); + }); +} + +class _MockSharedPreferences extends Mock implements SharedPreferences {} diff --git a/test/tracking_limiter_test.dart b/test/tracking_limiter_test.dart index 94cf990..c26538a 100644 --- a/test/tracking_limiter_test.dart +++ b/test/tracking_limiter_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2021 Yaroslav Pronin +// Copyright (C) 2021-2024 Yaroslav Pronin // Copyright (C) 2021 Insurgo Inc. // // This file is part of LibreTrack. @@ -56,7 +56,7 @@ void main() { ); when(() => mockPref.trackingFrequencyLimit) - .thenReturn(const TrackingFreqLimit.fifteenMin()); + .thenAnswer((_) async => const TrackingFreqLimit.fifteenMin()); when( () => mockTrackRepo.getTrackingInfoByTrack(defaultTrackNumber), ).thenAnswer( diff --git a/test/tracking_scheduler_test.dart b/test/tracking_scheduler_test.dart index e81ef4d..4c20c72 100644 --- a/test/tracking_scheduler_test.dart +++ b/test/tracking_scheduler_test.dart @@ -50,7 +50,7 @@ void main() { }); test('Init', () async { - when(() => mockPref.autoTracking).thenReturn(true); + when(() => mockPref.autoTracking).thenAnswer((_) async => true); when(() => mockWorkerManager.trackingPeriodic()).thenAnswer((_) async {}); await trackingScheduler.init(); @@ -159,14 +159,14 @@ void main() { ), ]; when(() => mockPref.autoTrackingFreq) - .thenReturn(const AutoTrackingFreq.twelveHours()); + .thenAnswer((_) async => const AutoTrackingFreq.twelveHours()); when( () => mockWorkerManager.trackingPeriodic( initialDelay: const Duration(hours: 12), ), ).thenAnswer((_) async {}); - when(() => mockPref.autoTracking).thenReturn(true); + when(() => mockPref.autoTracking).thenAnswer((_) async => true); expect( await trackingScheduler.enqueueOneshot(trackNumbersList), expectedResult, @@ -177,7 +177,7 @@ void main() { ), ).called(1); - when(() => mockPref.autoTracking).thenReturn(false); + when(() => mockPref.autoTracking).thenAnswer((_) async => false); expect( await trackingScheduler.enqueueOneshot(trackNumbersList), expectedResult, @@ -192,14 +192,14 @@ void main() { test('Enqueue oneshot all', () async { when(() => mockWorkerManager.trackingAll()).thenAnswer((_) async {}); when(() => mockPref.autoTrackingFreq) - .thenReturn(const AutoTrackingFreq.twelveHours()); + .thenAnswer((_) async => const AutoTrackingFreq.twelveHours()); when( () => mockWorkerManager.trackingPeriodic( initialDelay: const Duration(hours: 12), ), ).thenAnswer((_) async {}); - when(() => mockPref.autoTracking).thenReturn(true); + when(() => mockPref.autoTracking).thenAnswer((_) async => true); await trackingScheduler.enqueueOneshotAll(); verify(() => mockWorkerManager.trackingAll()).called(1); verify( @@ -208,7 +208,7 @@ void main() { ), ).called(1); - when(() => mockPref.autoTracking).thenReturn(false); + when(() => mockPref.autoTracking).thenAnswer((_) async => false); await trackingScheduler.enqueueOneshotAll(); verify(() => mockWorkerManager.trackingAll()).called(1); verifyNever( @@ -225,7 +225,7 @@ void main() { ), ).thenAnswer((_) async {}); - when(() => mockPref.autoTracking).thenReturn(true); + when(() => mockPref.autoTracking).thenAnswer((_) async => true); await trackingScheduler.reenqueueAll(); verify( () => mockWorkerManager.trackingPeriodic( @@ -233,7 +233,7 @@ void main() { ), ).called(1); - when(() => mockPref.autoTracking).thenReturn(false); + when(() => mockPref.autoTracking).thenAnswer((_) async => false); await trackingScheduler.reenqueueAll(); verifyNever( () => mockWorkerManager.trackingPeriodic( diff --git a/test/ui/app_theme_cubit_test.dart b/test/ui/app_theme_cubit_test.dart index a8165a2..f9c718c 100644 --- a/test/ui/app_theme_cubit_test.dart +++ b/test/ui/app_theme_cubit_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2021 Yaroslav Pronin +// Copyright (C) 202-2024 Yaroslav Pronin // Copyright (C) 2021 Insurgo Inc. // // This file is part of LibreTrack. @@ -32,14 +32,16 @@ void main() { setUpAll(() { mockSettings = MockAppSettings(); - when(() => mockSettings.theme).thenReturn( - const AppThemeType.system(), + when(() => mockSettings.theme).thenAnswer( + (_) async => const AppThemeType.system(), ); - when(() => mockSettings.locale).thenReturn(const AppLocaleType.system()); + when(() => mockSettings.locale) + .thenAnswer((_) async => const AppLocaleType.system()); }); - setUp(() { + setUp(() async { cubit = AppCubit(mockSettings); + await cubit.load(); }); blocTest( diff --git a/test/ui/parcels/first_start_cubit_test.dart b/test/ui/parcels/first_start_cubit_test.dart index 3382dc0..0021622 100644 --- a/test/ui/parcels/first_start_cubit_test.dart +++ b/test/ui/parcels/first_start_cubit_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2021 Yaroslav Pronin +// Copyright (C) 2021-2024 Yaroslav Pronin // Copyright (C) 2021 Insurgo Inc. // // This file is part of LibreTrack. @@ -64,7 +64,8 @@ void main() { ), ]), ); - when(() => mockAppSettings.addAccountTipShown).thenAnswer((_) => false); + when(() => mockAppSettings.addAccountTipShown) + .thenAnswer((_) async => false); await cubit.showAddAccountTip(); }, expect: () => [const FirstStartState.hideAddAccountTip()], @@ -86,7 +87,8 @@ void main() { ), ]), ); - when(() => mockAppSettings.addAccountTipShown).thenAnswer((_) => true); + when(() => mockAppSettings.addAccountTipShown) + .thenAnswer((_) async => true); await cubit.showAddAccountTip(); }, expect: () => [const FirstStartState.hideAddAccountTip()], @@ -99,7 +101,8 @@ void main() { when(() => mockServiceRepo.getAllServices()).thenAnswer( (_) async => const StorageResult([]), ); - when(() => mockAppSettings.addAccountTipShown).thenAnswer((_) => false); + when(() => mockAppSettings.addAccountTipShown) + .thenAnswer((_) async => false); await cubit.showAddAccountTip(); }, expect: () => [const FirstStartState.showAddAccountTip()], diff --git a/test/ui/parcels/parcels_cubit_test.dart b/test/ui/parcels/parcels_cubit_test.dart index 349905d..6b67c69 100644 --- a/test/ui/parcels/parcels_cubit_test.dart +++ b/test/ui/parcels/parcels_cubit_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2021 Yaroslav Pronin +// Copyright (C) 2021-2024 Yaroslav Pronin // Copyright (C) 2021 Insurgo Inc. // // This file is part of LibreTrack. @@ -19,10 +19,10 @@ import 'package:bloc_test/bloc_test.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:libretrack/core/entity/entity.dart'; +import 'package:libretrack/core/settings/settings.dart'; import 'package:libretrack/core/storage/shipment_repository.dart'; import 'package:libretrack/core/storage/track_number_repository.dart'; import 'package:libretrack/core/storage/tracking_repository.dart'; -import 'package:libretrack/core/settings/settings.dart'; import 'package:libretrack/ui/parcels/parcels.dart'; import 'package:mocktail/mocktail.dart'; @@ -106,26 +106,29 @@ void main() { 'Status filter', build: () => cubit, act: (ParcelsCubit cubit) { + when(() => mockPref.setParcelsFilters(any())) + .thenAnswer((_) async => {}); + cubit.setStatusFilter(ShipmentStatusType.inTransit); verify( - () => mockPref.parcelsFilters = ParcelsFilterBatch() + () => mockPref.setParcelsFilters(ParcelsFilterBatch() ..statusFilter = const ParcelsFilter.status( statusType: ShipmentStatusType.inTransit, - ), + )), ).called(1); cubit.setStatusFilter(ShipmentStatusType.delivered); verify( - () => mockPref.parcelsFilters = ParcelsFilterBatch() + () => mockPref.setParcelsFilters(ParcelsFilterBatch() ..statusFilter = const ParcelsFilter.status( statusType: ShipmentStatusType.delivered, - ), + )), ).called(1); cubit.setStatusFilter(null); verify( - () => mockPref.parcelsFilters = ParcelsFilterBatch() - ..statusFilter = const ParcelsFilter.status(), + () => mockPref.setParcelsFilters(ParcelsFilterBatch() + ..statusFilter = const ParcelsFilter.status()), ).called(1); }, expect: () => [ @@ -152,16 +155,20 @@ void main() { 'New info filter', build: () => cubit, act: (ParcelsCubit cubit) { + when(() => mockPref.setParcelsFilters(any())) + .thenAnswer((_) async => {}); + cubit.setNewInfoFilter(enable: true); verify( - () => mockPref.parcelsFilters = ParcelsFilterBatch() - ..newInfoFilter = const ParcelsFilter.newInfo(), + () => mockPref.setParcelsFilters(ParcelsFilterBatch() + ..newInfoFilter = const ParcelsFilter.newInfo()), ).called(1); cubit.setNewInfoFilter(enable: false); verify( - () => mockPref.parcelsFilters = ParcelsFilterBatch() - ..newInfoFilter = null, + () => mockPref.setParcelsFilters( + ParcelsFilterBatch()..newInfoFilter = null, + ), ).called(1); }, expect: () => [ @@ -179,16 +186,19 @@ void main() { 'Error filter', build: () => cubit, act: (ParcelsCubit cubit) { + when(() => mockPref.setParcelsFilters(any())) + .thenAnswer((_) async => {}); + cubit.setErrorFilter(enable: true); verify( - () => mockPref.parcelsFilters = ParcelsFilterBatch() - ..errorFilter = const ParcelsFilter.error(), + () => mockPref.setParcelsFilters( + ParcelsFilterBatch()..errorFilter = const ParcelsFilter.error()), ).called(1); cubit.setErrorFilter(enable: false); verify( - () => mockPref.parcelsFilters = ParcelsFilterBatch() - ..errorFilter = null, + () => mockPref + .setParcelsFilters(ParcelsFilterBatch()..errorFilter = null), ).called(1); }, expect: () => [ @@ -206,18 +216,21 @@ void main() { 'Postal service filter', build: () => cubit, act: (ParcelsCubit cubit) { + when(() => mockPref.setParcelsFilters(any())) + .thenAnswer((_) async => {}); + cubit.setPostalServiceFilter(PostalServiceType.ups); verify( - () => mockPref.parcelsFilters = ParcelsFilterBatch() + () => mockPref.setParcelsFilters(ParcelsFilterBatch() ..postalServiceFilter = const ParcelsFilter.postalService( serviceType: PostalServiceType.ups, - ), + )), ).called(1); cubit.setPostalServiceFilter(null); verify( - () => mockPref.parcelsFilters = ParcelsFilterBatch() - ..postalServiceFilter = const ParcelsFilter.postalService(), + () => mockPref.setParcelsFilters(ParcelsFilterBatch() + ..postalServiceFilter = const ParcelsFilter.postalService()), ).called(1); }, expect: () => [ @@ -238,15 +251,18 @@ void main() { 'Alphabetically sort', build: () => cubit, act: (ParcelsCubit cubit) { + when(() => mockPref.setParcelsSort(any())).thenAnswer((_) async => {}); + cubit.setAlphabeticallySort(); verify( - () => mockPref.parcelsSort = const ParcelsSort.alphabetically(), + () => mockPref.setParcelsSort(const ParcelsSort.alphabetically()), ).called(1); cubit.setAlphabeticallySort(isDesc: true); verify( - () => mockPref.parcelsSort = - const ParcelsSort.alphabetically(isDesc: true), + () => mockPref.setParcelsSort( + const ParcelsSort.alphabetically(isDesc: true), + ), ).called(1); }, expect: () => [ @@ -265,15 +281,17 @@ void main() { 'Activity date sort', build: () => cubit, act: (ParcelsCubit cubit) { + when(() => mockPref.setParcelsSort(any())).thenAnswer((_) async => {}); + cubit.setActivityDateSort(); verify( - () => mockPref.parcelsSort = const ParcelsSort.activityDate(), + () => mockPref.setParcelsSort(const ParcelsSort.activityDate()), ).called(1); cubit.setActivityDateSort(oldestFirst: true); verify( - () => mockPref.parcelsSort = - const ParcelsSort.activityDate(oldestFirst: true), + () => mockPref.setParcelsSort( + const ParcelsSort.activityDate(oldestFirst: true)), ).called(1); }, expect: () => [ @@ -292,15 +310,18 @@ void main() { 'Date added sort', build: () => cubit, act: (ParcelsCubit cubit) { + when(() => mockPref.setParcelsSort(any())).thenAnswer((_) async => {}); + cubit.setDateAddedSort(); verify( - () => mockPref.parcelsSort = const ParcelsSort.dateAdded(), + () => mockPref.setParcelsSort(const ParcelsSort.dateAdded()), ).called(1); cubit.setDateAddedSort(oldestFirst: true); verify( - () => mockPref.parcelsSort = - const ParcelsSort.dateAdded(oldestFirst: true), + () => mockPref.setParcelsSort( + const ParcelsSort.dateAdded(oldestFirst: true), + ), ).called(1); }, expect: () => [ diff --git a/test/ui/settings/appearance_cubit_test.dart b/test/ui/settings/appearance_cubit_test.dart index a8a2082..ddc1496 100644 --- a/test/ui/settings/appearance_cubit_test.dart +++ b/test/ui/settings/appearance_cubit_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2021 Yaroslav Pronin +// Copyright (C) 2021-2024 Yaroslav Pronin // Copyright (C) 2021 Insurgo Inc. // // This file is part of LibreTrack. @@ -35,31 +35,45 @@ void main() { late SystemTray mockSystemTray; setUpAll(() { + registerFallbackValue(const AppThemeType.system()); + registerFallbackValue(const AppLocaleType.system()); + mockPref = MockAppSettings(); - when(() => mockPref.theme).thenReturn( - const AppThemeType.system(), + when(() => mockPref.theme).thenAnswer( + (_) async => const AppThemeType.system(), ); - when(() => mockPref.trackingNotifications).thenReturn(false); - when(() => mockPref.locale).thenReturn( - const AppLocaleType.system(), + when(() => mockPref.setTheme(any())).thenAnswer((_) async {}); + when(() => mockPref.trackingNotifications).thenAnswer((_) async => false); + when(() => mockPref.setTrackingNotifications(any())) + .thenAnswer((_) async {}); + when(() => mockPref.locale).thenAnswer( + (_) async => const AppLocaleType.system(), ); - when(() => mockPref.trackingErrorNotifications).thenReturn(false); + when(() => mockPref.setLocale(any())).thenAnswer((_) async {}); + when(() => mockPref.trackingErrorNotifications) + .thenAnswer((_) async => false); + when(() => mockPref.setTrackingErrorNotifications(any())) + .thenAnswer((_) async {}); mockSystemTray = MockSystemTray(); when( () => mockSystemTray.switchTrayIcon( enable: any(named: 'enable'), ), ).thenAnswer((_) async => {}); - when(() => mockPref.trayIcon).thenReturn(true); + when(() => mockPref.trayIcon).thenAnswer((_) async => true); + when(() => mockPref.setTrayIcon(any())).thenAnswer((_) async {}); mockAppCubit = MockAppCubit(); + when(() => mockAppCubit.setTheme(any())).thenAnswer((_) {}); + when(() => mockAppCubit.setLocale(any())).thenAnswer((_) {}); }); - setUp(() { + setUp(() async { cubit = AppearanceSettingsCubit( mockPref, mockAppCubit, mockSystemTray, ); + await cubit.load(); }); blocTest( @@ -71,29 +85,29 @@ void main() { blocTest( 'Change theme', build: () => cubit, - act: (AppearanceSettingsCubit cubit) { - cubit.setTheme(const AppThemeType.dark()); + act: (AppearanceSettingsCubit cubit) async { + await cubit.setTheme(const AppThemeType.dark()); verify( () => mockAppCubit.setTheme(const AppThemeType.dark()), ).called(1); verify( - () => mockPref.theme = const AppThemeType.dark(), + () => mockPref.setTheme(const AppThemeType.dark()), ).called(1); - cubit.setTheme(const AppThemeType.light()); + await cubit.setTheme(const AppThemeType.light()); verify( () => mockAppCubit.setTheme(const AppThemeType.light()), ).called(1); verify( - () => mockPref.theme = const AppThemeType.light(), + () => mockPref.setTheme(const AppThemeType.light()), ).called(1); - cubit.setTheme(const AppThemeType.system()); + await cubit.setTheme(const AppThemeType.system()); verify( () => mockAppCubit.setTheme(const AppThemeType.system()), ).called(1); verify( - () => mockPref.theme = const AppThemeType.system(), + () => mockPref.setTheme(const AppThemeType.system()), ).called(1); }, expect: () => [ @@ -130,15 +144,15 @@ void main() { blocTest( 'Tracking notifications', build: () => cubit, - act: (AppearanceSettingsCubit cubit) { - cubit.trackingNotify(enable: true); + act: (AppearanceSettingsCubit cubit) async { + await cubit.trackingNotify(enable: true); verify( - () => mockPref.trackingNotifications = true, + () => mockPref.setTrackingNotifications(true), ).called(1); - cubit.trackingNotify(enable: false); + await cubit.trackingNotify(enable: false); verify( - () => mockPref.trackingNotifications = false, + () => mockPref.setTrackingNotifications(false), ).called(1); }, expect: () => [ @@ -166,8 +180,8 @@ void main() { blocTest( 'Change locale', build: () => cubit, - act: (AppearanceSettingsCubit cubit) { - cubit.setLocale( + act: (AppearanceSettingsCubit cubit) async { + await cubit.setLocale( const AppLocaleType.inner( locale: Locale('ru', 'RU'), ), @@ -180,19 +194,21 @@ void main() { ), ).called(1); verify( - () => mockPref.locale = const AppLocaleType.inner( - locale: Locale('ru', 'RU'), + () => mockPref.setLocale( + const AppLocaleType.inner( + locale: Locale('ru', 'RU'), + ), ), ).called(1); - cubit.setLocale(const AppLocaleType.system()); + await cubit.setLocale(const AppLocaleType.system()); verify( () => mockAppCubit.setLocale( const AppLocaleType.system(), ), ).called(1); verify( - () => mockPref.locale = const AppLocaleType.system(), + () => mockPref.setLocale(const AppLocaleType.system()), ).called(1); }, expect: () => [ @@ -222,15 +238,15 @@ void main() { blocTest( 'Tracking error notifications', build: () => cubit, - act: (AppearanceSettingsCubit cubit) { - cubit.trackingErrorNotify(enable: true); + act: (AppearanceSettingsCubit cubit) async { + await cubit.trackingErrorNotify(enable: true); verify( - () => mockPref.trackingErrorNotifications = true, + () => mockPref.setTrackingErrorNotifications(true), ).called(1); - cubit.trackingErrorNotify(enable: false); + await cubit.trackingErrorNotify(enable: false); verify( - () => mockPref.trackingErrorNotifications = false, + () => mockPref.setTrackingErrorNotifications(false), ).called(1); }, expect: () => [ @@ -258,18 +274,18 @@ void main() { blocTest( 'Tray icon', build: () => cubit, - act: (AppearanceSettingsCubit cubit) { - cubit.trayIcon(enable: true); + act: (AppearanceSettingsCubit cubit) async { + await cubit.trayIcon(enable: true); verify( - () => mockPref.trayIcon = true, + () => mockPref.setTrayIcon(true), ).called(1); verify( () => mockSystemTray.switchTrayIcon(enable: true), ).called(1); - cubit.trayIcon(enable: false); + await cubit.trayIcon(enable: false); verify( - () => mockPref.trayIcon = false, + () => mockPref.setTrayIcon(false), ).called(1); verify( () => mockSystemTray.switchTrayIcon(enable: false), diff --git a/test/ui/settings/behavior_cubit_test.dart b/test/ui/settings/behavior_cubit_test.dart index 0fda68d..ec793ea 100644 --- a/test/ui/settings/behavior_cubit_test.dart +++ b/test/ui/settings/behavior_cubit_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2021 Yaroslav Pronin +// Copyright (C) 2021-2024 Yaroslav Pronin // Copyright (C) 2021 Insurgo Inc. // // This file is part of LibreTrack. @@ -32,21 +32,31 @@ void main() { late TrackingScheduler mockTrackingScheduler; setUpAll(() { + registerFallbackValue(const TrackingFreqLimit.unlimited()); + registerFallbackValue(const AutoTrackingFreq.twelveHours()); + mockPref = MockAppSettings(); mockTrackingScheduler = MockTrackingScheduler(); - when(() => mockPref.trackingFrequencyLimit).thenReturn( - const TrackingFreqLimit.fifteenMin(), + when(() => mockPref.trackingFrequencyLimit).thenAnswer( + (_) async => const TrackingFreqLimit.fifteenMin(), ); - when(() => mockPref.autoTracking).thenReturn(true); - when(() => mockPref.autoTrackingFreq).thenReturn( - const AutoTrackingFreq.twelveHours(), + when(() => mockPref.setTrackingFrequencyLimit(any())) + .thenAnswer((_) async {}); + when(() => mockPref.autoTracking).thenAnswer((_) async => true); + when(() => mockPref.setAutoTracking(any())).thenAnswer((_) async {}); + when(() => mockPref.autoTrackingFreq).thenAnswer( + (_) async => const AutoTrackingFreq.twelveHours(), ); - when(() => mockPref.trackingHistorySize).thenReturn(5); + when(() => mockPref.setAutoTrackingFreq(any())).thenAnswer((_) async {}); + when(() => mockPref.trackingHistorySize).thenAnswer((_) async => 5); + when(() => mockPref.setTrackingHistorySize(any())) + .thenAnswer((_) async {}); when(() => mockTrackingScheduler.reenqueueAll()).thenAnswer((_) async {}); }); - setUp(() { + setUp(() async { cubit = BehaviorSettingsCubit(mockPref, mockTrackingScheduler); + await cubit.load(); }); blocTest( @@ -58,17 +68,17 @@ void main() { blocTest( 'Change tracking frequency limit', build: () => cubit, - act: (BehaviorSettingsCubit cubit) { - cubit.setTrackingLimit(const TrackingFreqLimit.oneHour()); + act: (BehaviorSettingsCubit cubit) async { + await cubit.setTrackingLimit(const TrackingFreqLimit.oneHour()); verify( - () => mockPref.trackingFrequencyLimit = - const TrackingFreqLimit.oneHour(), + () => mockPref + .setTrackingFrequencyLimit(const TrackingFreqLimit.oneHour()), ).called(1); - cubit.setTrackingLimit(const TrackingFreqLimit.fifteenMin()); + await cubit.setTrackingLimit(const TrackingFreqLimit.fifteenMin()); verify( - () => mockPref.trackingFrequencyLimit = - const TrackingFreqLimit.fifteenMin(), + () => mockPref + .setTrackingFrequencyLimit(const TrackingFreqLimit.fifteenMin()), ).called(1); }, expect: () => [ @@ -94,24 +104,12 @@ void main() { blocTest( 'Auto tracking', build: () => cubit, - act: (BehaviorSettingsCubit cubit) { - cubit.autoTracking(enable: true); - verify(() => mockPref.autoTracking = true).called(1); - verify(() => mockTrackingScheduler.reenqueueAll()).called(1); - - cubit.autoTracking(enable: false); - verify(() => mockPref.autoTracking = false).called(1); + act: (BehaviorSettingsCubit cubit) async { + await cubit.autoTracking(enable: false); + verify(() => mockPref.setAutoTracking(false)).called(1); verify(() => mockTrackingScheduler.reenqueueAll()).called(1); }, expect: () => [ - const BehaviorState.autoTrackingChanged( - BehaviorInfo( - trackingLimit: TrackingFreqLimit.fifteenMin(), - autoTracking: true, - autoTrackingFreq: AutoTrackingFreq.twelveHours(), - trackingHistorySize: 5, - ), - ), const BehaviorState.autoTrackingChanged( BehaviorInfo( trackingLimit: TrackingFreqLimit.fifteenMin(), @@ -126,17 +124,17 @@ void main() { blocTest( 'Auto tracking frequency', build: () => cubit, - act: (BehaviorSettingsCubit cubit) { - cubit.setAutoTrackingFreq(const AutoTrackingFreq.sixHours()); + act: (BehaviorSettingsCubit cubit) async { + await cubit.setAutoTrackingFreq(const AutoTrackingFreq.sixHours()); verify( - () => mockPref.autoTrackingFreq = const AutoTrackingFreq.sixHours(), + () => mockPref.setAutoTrackingFreq(const AutoTrackingFreq.sixHours()), ).called(1); verify(() => mockTrackingScheduler.reenqueueAll()).called(1); - cubit.setAutoTrackingFreq(const AutoTrackingFreq.twelveHours()); + await cubit.setAutoTrackingFreq(const AutoTrackingFreq.twelveHours()); verify( - () => - mockPref.autoTrackingFreq = const AutoTrackingFreq.twelveHours(), + () => mockPref + .setAutoTrackingFreq(const AutoTrackingFreq.twelveHours()), ).called(1); verify(() => mockTrackingScheduler.reenqueueAll()).called(1); }, @@ -163,12 +161,12 @@ void main() { blocTest( 'Change tracking history size', build: () => cubit, - act: (BehaviorSettingsCubit cubit) { - cubit.setTrackingHistorySize(10); - verify(() => mockPref.trackingHistorySize = 10).called(1); + act: (BehaviorSettingsCubit cubit) async { + await cubit.setTrackingHistorySize(10); + verify(() => mockPref.setTrackingHistorySize(10)).called(1); - cubit.setTrackingHistorySize(5); - verify(() => mockPref.trackingHistorySize = 5).called(1); + await cubit.setTrackingHistorySize(5); + verify(() => mockPref.setTrackingHistorySize(5)).called(1); }, expect: () => [ const BehaviorState.trackingHistorySizeChanged( diff --git a/test/worker/tracking_all_worker_test.dart b/test/worker/tracking_all_worker_test.dart index 6846266..496708d 100644 --- a/test/worker/tracking_all_worker_test.dart +++ b/test/worker/tracking_all_worker_test.dart @@ -98,7 +98,8 @@ void main() { )).thenAnswer( (_) async => const WorkResult.success(), ); - when(() => mockPref.locale).thenReturn(const AppLocaleType.system()); + when(() => mockPref.locale) + .thenAnswer((_) async => const AppLocaleType.system()); await trackingAllWorker.doWork(const WorkData.empty()); verify(() => mockTrackingWorker.doTrack( @@ -130,8 +131,8 @@ void main() { )).thenAnswer( (_) async => const WorkResult.success(), ); - when(() => mockPref.locale).thenReturn( - const AppLocaleType.inner( + when(() => mockPref.locale).thenAnswer( + (_) async => const AppLocaleType.inner( locale: locale, ), ); diff --git a/test/worker/tracking_notify_task_test.dart b/test/worker/tracking_notify_task_test.dart index 15e326c..ee15b2c 100644 --- a/test/worker/tracking_notify_task_test.dart +++ b/test/worker/tracking_notify_task_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2021 Yaroslav Pronin +// Copyright (C) 2021-2024 Yaroslav Pronin // Copyright (C) 2021 Insurgo Inc. // // This file is part of LibreTrack. @@ -184,8 +184,9 @@ void main() { ).thenAnswer( (_) async => {}, ); - when(() => mockPref.trackingNotifications).thenReturn(true); - when(() => mockPref.trackingErrorNotifications).thenReturn(true); + when(() => mockPref.trackingNotifications).thenAnswer((_) async => true); + when(() => mockPref.trackingErrorNotifications) + .thenAnswer((_) async => true); await notifyTask.show( trackingInfo: trackingInfo, @@ -202,8 +203,9 @@ void main() { }); test('Disabled notifications', () async { - when(() => mockPref.trackingNotifications).thenReturn(false); - when(() => mockPref.trackingErrorNotifications).thenReturn(false); + when(() => mockPref.trackingNotifications).thenAnswer((_) async => false); + when(() => mockPref.trackingErrorNotifications) + .thenAnswer((_) async => false); await notifyTask.show(trackingInfo: [], trackingResult: []); verifyNever( () => mockNotifyManager.newActivitiesNotify(any()), @@ -225,7 +227,8 @@ void main() { ).thenAnswer( (_) async => {}, ); - when(() => mockPref.trackingErrorNotifications).thenReturn(true); + when(() => mockPref.trackingErrorNotifications) + .thenAnswer((_) async => true); await notifyTask.trackingFailedNotify(['1']); verify( diff --git a/test/worker/tracking_periodic_worker_test.dart b/test/worker/tracking_periodic_worker_test.dart index 3812a6b..7d9c5b1 100644 --- a/test/worker/tracking_periodic_worker_test.dart +++ b/test/worker/tracking_periodic_worker_test.dart @@ -67,12 +67,13 @@ void main() { const locale = Locale('en', 'US'); when(() => mockPlatformInfo.currentAsLocale) .thenAnswer((_) async => locale); - when(() => mockPref.locale).thenReturn(const AppLocaleType.system()); - when(() => mockPref.autoTracking).thenReturn(true); + when(() => mockPref.locale) + .thenAnswer((_) async => const AppLocaleType.system()); + when(() => mockPref.autoTracking).thenAnswer((_) async => true); when(() => mockPref.autoTrackingFreq) - .thenReturn(const AutoTrackingFreq.sixHours()); + .thenAnswer((_) async => const AutoTrackingFreq.sixHours()); when(() => mockPref.trackingFrequencyLimit) - .thenReturn(const TrackingFreqLimit.fifteenMin()); + .thenAnswer((_) async => const TrackingFreqLimit.fifteenMin()); when( () => mockTrackNumberRepo.getAllUnarchivedTracks(), ).thenAnswer( @@ -258,7 +259,7 @@ void main() { }); test('Auto tracking is disabled', () async { - when(() => mockPref.autoTracking).thenReturn(false); + when(() => mockPref.autoTracking).thenAnswer((_) async => false); expect(await worker.doWork(null), const WorkResult.success()); }); @@ -266,12 +267,13 @@ void main() { const locale = Locale('en', 'US'); when(() => mockPlatformInfo.currentAsLocale) .thenAnswer((_) async => locale); - when(() => mockPref.locale).thenReturn(const AppLocaleType.system()); - when(() => mockPref.autoTracking).thenReturn(true); + when(() => mockPref.locale) + .thenAnswer((_) async => const AppLocaleType.system()); + when(() => mockPref.autoTracking).thenAnswer((_) async => true); when(() => mockPref.autoTrackingFreq) - .thenReturn(const AutoTrackingFreq.sixHours()); + .thenAnswer((_) async => const AutoTrackingFreq.sixHours()); when(() => mockPref.trackingFrequencyLimit) - .thenReturn(const TrackingFreqLimit.fifteenMin()); + .thenAnswer((_) async => const TrackingFreqLimit.fifteenMin()); when( () => mockTrackNumberRepo.getAllUnarchivedTracks(), ).thenAnswer( @@ -392,19 +394,19 @@ void main() { }); test('Auto tracking is disabled', () async { - when(() => mockPref.autoTracking).thenReturn(false); + when(() => mockPref.autoTracking).thenAnswer((_) async => false); expect(await worker.doWork(null), const WorkResult.success()); }); test('Using app locale', () async { const locale = Locale('ru', 'RU'); when(() => mockPref.locale) - .thenReturn(const AppLocaleType.inner(locale: locale)); - when(() => mockPref.autoTracking).thenReturn(true); + .thenAnswer((_) async => const AppLocaleType.inner(locale: locale)); + when(() => mockPref.autoTracking).thenAnswer((_) async => true); when(() => mockPref.autoTrackingFreq) - .thenReturn(const AutoTrackingFreq.sixHours()); + .thenAnswer((_) async => const AutoTrackingFreq.sixHours()); when(() => mockPref.trackingFrequencyLimit) - .thenReturn(const TrackingFreqLimit.fifteenMin()); + .thenAnswer((_) async => const TrackingFreqLimit.fifteenMin()); when( () => mockTrackNumberRepo.getAllUnarchivedTracks(), ).thenAnswer( @@ -473,12 +475,13 @@ void main() { const locale = Locale('en', 'US'); when(() => mockPlatformInfo.currentAsLocale) .thenAnswer((_) async => locale); - when(() => mockPref.locale).thenReturn(const AppLocaleType.system()); - when(() => mockPref.autoTracking).thenReturn(true); + when(() => mockPref.locale) + .thenAnswer((_) async => const AppLocaleType.system()); + when(() => mockPref.autoTracking).thenAnswer((_) async => true); when(() => mockPref.autoTrackingFreq) - .thenReturn(const AutoTrackingFreq.sixHours()); + .thenAnswer((_) async => const AutoTrackingFreq.sixHours()); when(() => mockPref.trackingFrequencyLimit) - .thenReturn(const TrackingFreqLimit.fifteenMin()); + .thenAnswer((_) async => const TrackingFreqLimit.fifteenMin()); when( () => mockTrackNumberRepo.getAllUnarchivedTracks(), ).thenAnswer( @@ -619,7 +622,7 @@ void main() { }); test('No track numbers', () async { - when(() => mockPref.autoTracking).thenReturn(true); + when(() => mockPref.autoTracking).thenAnswer((_) async => true); when( () => mockTrackNumberRepo.getAllUnarchivedTracks(), ).thenAnswer( @@ -631,12 +634,12 @@ void main() { test('No latest tracking info', () async { const locale = Locale('en', 'US'); when(() => mockPref.locale) - .thenReturn(const AppLocaleType.inner(locale: locale)); - when(() => mockPref.autoTracking).thenReturn(true); + .thenAnswer((_) async => const AppLocaleType.inner(locale: locale)); + when(() => mockPref.autoTracking).thenAnswer((_) async => true); when(() => mockPref.autoTrackingFreq) - .thenReturn(const AutoTrackingFreq.sixHours()); + .thenAnswer((_) async => const AutoTrackingFreq.sixHours()); when(() => mockPref.trackingFrequencyLimit) - .thenReturn(const TrackingFreqLimit.fifteenMin()); + .thenAnswer((_) async => const TrackingFreqLimit.fifteenMin()); when( () => mockTrackNumberRepo.getAllUnarchivedTracks(), ).thenAnswer( diff --git a/test/worker/tracking_worker_test.dart b/test/worker/tracking_worker_test.dart index 3f766c9..0460579 100644 --- a/test/worker/tracking_worker_test.dart +++ b/test/worker/tracking_worker_test.dart @@ -1,4 +1,4 @@ -// Copyright (C) 2021 Yaroslav Pronin +// Copyright (C) 2021-2024 Yaroslav Pronin // Copyright (C) 2021 Insurgo Inc. // // This file is part of LibreTrack. @@ -184,7 +184,7 @@ void main() { ), ).thenAnswer((_) async => StorageResult.empty); - when(() => mockPref.trackingHistorySize).thenReturn(5); + when(() => mockPref.trackingHistorySize).thenAnswer((_) async => 5); when(() => mockPlatformInfo.currentAsLocale) .thenAnswer((_) async => const Locale('en', 'US')); @@ -206,7 +206,8 @@ void main() { ), ).thenAnswer((_) async => {}); - when(() => mockPref.locale).thenReturn(const AppLocaleType.system()); + when(() => mockPref.locale) + .thenAnswer((_) async => const AppLocaleType.system()); for (final trackInfo in trackList) { when( @@ -362,7 +363,7 @@ void main() { ), ).thenAnswer((_) async => StorageResult.empty); - when(() => mockPref.trackingHistorySize).thenReturn(5); + when(() => mockPref.trackingHistorySize).thenAnswer((_) async => 5); when(() => mockPlatformInfo.currentAsLocale) .thenAnswer((_) async => null); @@ -386,8 +387,8 @@ void main() { ), ).thenAnswer((_) async => {}); - when(() => mockPref.locale).thenReturn( - const AppLocaleType.inner( + when(() => mockPref.locale).thenAnswer( + (_) async => const AppLocaleType.inner( locale: locale, ), ); @@ -512,7 +513,7 @@ void main() { ), ).thenAnswer((_) async => StorageResult.empty); - when(() => mockPref.trackingHistorySize).thenReturn(5); + when(() => mockPref.trackingHistorySize).thenAnswer((_) async => 5); when(() => mockPlatformInfo.currentAsLocale) .thenAnswer((_) async => const Locale('en', 'US')); @@ -535,7 +536,8 @@ void main() { ), ).thenAnswer((_) async => {}); - when(() => mockPref.locale).thenReturn(const AppLocaleType.system()); + when(() => mockPref.locale) + .thenAnswer((_) async => const AppLocaleType.system()); when( () => mockLimiter.check(trackList[0].trackNumber), @@ -650,7 +652,8 @@ void main() { } }, ); - when(() => mockPref.locale).thenReturn(const AppLocaleType.system()); + when(() => mockPref.locale) + .thenAnswer((_) async => const AppLocaleType.system()); when( () => mockLimiter.check(trackNumberInfo.trackNumber), ).thenAnswer( @@ -669,7 +672,7 @@ void main() { test('Normal case', () async { when( () => mockPref.trackingHistorySize, - ).thenReturn(allTrackingInfo.length - 1); + ).thenAnswer((_) async => allTrackingInfo.length - 1); when( () => mockTrackingRepo.deleteTrackingInfoByList( [allTrackingInfo[0]], @@ -685,7 +688,7 @@ void main() { }); test('History size == 0', () async { - when(() => mockPref.trackingHistorySize).thenReturn(0); + when(() => mockPref.trackingHistorySize).thenAnswer((_) async => 0); when( () => mockTrackingRepo.deleteTrackingInfoByList( allTrackingInfo, @@ -701,7 +704,7 @@ void main() { }); test('History size is negative', () async { - when(() => mockPref.trackingHistorySize).thenReturn(-1); + when(() => mockPref.trackingHistorySize).thenAnswer((_) async => -1); expect( () => worker.doWork(inputData), throwsA(isA()), @@ -746,7 +749,7 @@ void main() { ), ).thenAnswer((_) async => {}); - when(() => mockPref.trackingHistorySize).thenReturn(0); + when(() => mockPref.trackingHistorySize).thenAnswer((_) async => 0); when( () => mockTrackingRepo.deleteTrackingInfoByList([]), ).thenAnswer((_) async => StorageResult.empty); @@ -760,7 +763,7 @@ void main() { test('History size less than the maximum value', () async { when( () => mockPref.trackingHistorySize, - ).thenReturn(allTrackingInfo.length + 1); + ).thenAnswer((_) async => allTrackingInfo.length + 1); when( () => mockTrackingRepo.deleteTrackingInfoByList([]), ).thenAnswer((_) async => StorageResult.empty);