From f50d7fbf260a44116251a27df3c2293003b864f5 Mon Sep 17 00:00:00 2001 From: Ming Ming Date: Sun, 20 Aug 2023 00:47:56 +0800 Subject: [PATCH] Refactor: move theme prefs to PrefController --- app/lib/bloc_util.dart | 14 ++ app/lib/controller/pref_controller.dart | 75 ++++++++ app/lib/entity/pref/extension.dart | 12 ++ app/lib/event/event.dart | 2 - app/lib/theme.dart | 33 ++-- app/lib/widget/account_picker_dialog.dart | 39 ++-- app/lib/widget/account_picker_dialog.g.dart | 7 + .../widget/account_picker_dialog/bloc.dart | 8 + .../account_picker_dialog/state_event.dart | 10 + app/lib/widget/connect.dart | 2 +- app/lib/widget/image_editor.dart | 2 +- app/lib/widget/image_enhancer.dart | 2 +- app/lib/widget/local_file_viewer.dart | 2 +- app/lib/widget/my_app.dart | 72 ++++---- app/lib/widget/my_app.g.dart | 73 ++++++++ app/lib/widget/my_app/bloc.dart | 47 +++++ app/lib/widget/my_app/state_event.dart | 32 ++++ app/lib/widget/result_viewer.dart | 2 +- app/lib/widget/settings/theme/bloc.dart | 98 +++++----- .../widget/settings/theme/state_event.dart | 11 ++ app/lib/widget/settings/theme_settings.dart | 171 +++++++++--------- app/lib/widget/settings/theme_settings.g.dart | 20 +- app/lib/widget/sign_in.dart | 2 +- app/lib/widget/slideshow_viewer.dart | 2 +- app/lib/widget/trashbin_viewer.dart | 2 +- app/lib/widget/viewer.dart | 4 +- 26 files changed, 528 insertions(+), 216 deletions(-) create mode 100644 app/lib/widget/my_app/bloc.dart create mode 100644 app/lib/widget/my_app/state_event.dart diff --git a/app/lib/bloc_util.dart b/app/lib/bloc_util.dart index 75d9481b..4ba5142e 100644 --- a/app/lib/bloc_util.dart +++ b/app/lib/bloc_util.dart @@ -1,3 +1,5 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; + mixin BlocLogger { String? get tag => null; @@ -11,3 +13,15 @@ class StateMessage { final String value; } + +extension EmitterExtension on Emitter { + Future forEachIgnoreError( + Stream stream, { + required State Function(T data) onData, + }) => + onEach( + stream, + onData: (data) => call(onData(data)), + onError: (_, __) {}, + ); +} diff --git a/app/lib/controller/pref_controller.dart b/app/lib/controller/pref_controller.dart index ee095b3f..43e16048 100644 --- a/app/lib/controller/pref_controller.dart +++ b/app/lib/controller/pref_controller.dart @@ -1,3 +1,6 @@ +// ignore_for_file: deprecated_member_use_from_same_package + +import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; import 'package:nc_photos/di_container.dart'; import 'package:nc_photos/entity/pref.dart'; @@ -141,6 +144,41 @@ class PrefController { value: value, ); + ValueStream get isDarkTheme => _isDarkThemeController.stream; + + Future setDarkTheme(bool value) => _set( + controller: _isDarkThemeController, + setter: (pref, value) => pref.setDarkTheme(value), + value: value, + ); + + ValueStream get isFollowSystemTheme => + _isFollowSystemThemeController.stream; + + Future setFollowSystemTheme(bool value) => _set( + controller: _isFollowSystemThemeController, + setter: (pref, value) => pref.setFollowSystemTheme(value), + value: value, + ); + + ValueStream get isUseBlackInDarkTheme => + _isUseBlackInDarkThemeController.stream; + + Future setUseBlackInDarkTheme(bool value) => _set( + controller: _isUseBlackInDarkThemeController, + setter: (pref, value) => pref.setUseBlackInDarkTheme(value), + value: value, + ); + + ValueStream get seedColor => _seedColorController.stream; + + Future setSeedColor(Color? value) => _setOrRemove( + controller: _seedColorController, + setter: (pref, value) => pref.setSeedColor(value.withAlpha(0xFF).value), + remover: (pref) => pref.setSeedColor(null), + value: value, + ); + Future _set({ required BehaviorSubject controller, required Future Function(Pref pref, T value) setter, @@ -160,6 +198,33 @@ class PrefController { } } + Future _setOrRemove({ + required BehaviorSubject controller, + required Future Function(Pref pref, T value) setter, + required Future Function(Pref pref) remover, + required T? value, + T? defaultValue, + }) async { + final backup = controller.value; + controller.add(value ?? defaultValue); + try { + if (value == null) { + if (!await remover(_c.pref)) { + throw StateError("Unknown error"); + } + } else { + if (!await setter(_c.pref, value)) { + throw StateError("Unknown error"); + } + } + } catch (e, stackTrace) { + _log.severe("[_set] Failed setting preference", e, stackTrace); + controller + ..addError(e, stackTrace) + ..add(backup); + } + } + static language_util.AppLanguage _langIdToAppLanguage(int langId) { try { return language_util.supportedLanguages[langId]!; @@ -168,6 +233,8 @@ class PrefController { } } + static const _seedColorDef = 0xFF2196F3; + final DiContainer _c; late final _languageController = BehaviorSubject.seeded(_langIdToAppLanguage(_c.pref.getLanguageOr(0))); @@ -197,4 +264,12 @@ class PrefController { BehaviorSubject.seeded(_c.pref.isSaveEditResultToServerOr(true)); late final _enhanceMaxSizeController = BehaviorSubject.seeded( SizeInt(_c.pref.getEnhanceMaxWidthOr(), _c.pref.getEnhanceMaxHeightOr())); + late final _isDarkThemeController = + BehaviorSubject.seeded(_c.pref.isDarkThemeOr(false)); + late final _isFollowSystemThemeController = + BehaviorSubject.seeded(_c.pref.isFollowSystemThemeOr(false)); + late final _isUseBlackInDarkThemeController = + BehaviorSubject.seeded(_c.pref.isUseBlackInDarkThemeOr(false)); + late final _seedColorController = BehaviorSubject.seeded( + Color(_c.pref.getSeedColorOr(_seedColorDef))); } diff --git a/app/lib/entity/pref/extension.dart b/app/lib/entity/pref/extension.dart index 36c13a3d..f68c901a 100644 --- a/app/lib/entity/pref/extension.dart +++ b/app/lib/entity/pref/extension.dart @@ -101,21 +101,30 @@ extension PrefExtension on Pref { Future setLastVersion(int value) => _set( PrefKey.lastVersion, value, (key, value) => provider.setInt(key, value)); + @Deprecated("Use PrefController") bool? isDarkTheme() => provider.getBool(PrefKey.darkTheme); + @Deprecated("Use PrefController") bool isDarkThemeOr(bool def) => isDarkTheme() ?? def; + @Deprecated("Use PrefController") Future setDarkTheme(bool value) => _set( PrefKey.darkTheme, value, (key, value) => provider.setBool(key, value)); + @Deprecated("Use PrefController") bool? isFollowSystemTheme() => provider.getBool(PrefKey.followSystemTheme); + @Deprecated("Use PrefController") bool isFollowSystemThemeOr(bool def) => isFollowSystemTheme() ?? def; + @Deprecated("Use PrefController") Future setFollowSystemTheme(bool value) => _set( PrefKey.followSystemTheme, value, (key, value) => provider.setBool(key, value)); + @Deprecated("Use PrefController") bool? isUseBlackInDarkTheme() => provider.getBool(PrefKey.useBlackInDarkTheme); + @Deprecated("Use PrefController") bool isUseBlackInDarkThemeOr(bool def) => isUseBlackInDarkTheme() ?? def; + @Deprecated("Use PrefController") Future setUseBlackInDarkTheme(bool value) => _set( PrefKey.useBlackInDarkTheme, value, @@ -249,8 +258,11 @@ extension PrefExtension on Pref { value, (key, value) => provider.setBool(key, value)); + @Deprecated("Use PrefController") int? getSeedColor() => provider.getInt(PrefKey.seedColor); + @Deprecated("Use PrefController") int getSeedColorOr(int def) => getSeedColor() ?? def; + @Deprecated("Use PrefController") Future setSeedColor(int? value) { if (value == null) { return _remove(PrefKey.seedColor); diff --git a/app/lib/event/event.dart b/app/lib/event/event.dart index 563ba58e..c91d0d20 100644 --- a/app/lib/event/event.dart +++ b/app/lib/event/event.dart @@ -117,8 +117,6 @@ class FavoriteResyncedEvent { final Account account; } -class ThemeChangedEvent {} - enum MetadataTaskState { /// No work is being done idle, diff --git a/app/lib/theme.dart b/app/lib/theme.dart index 9a1b00a5..ef116643 100644 --- a/app/lib/theme.dart +++ b/app/lib/theme.dart @@ -1,12 +1,12 @@ import 'dart:ui'; import 'package:flutter/material.dart'; -import 'package:nc_photos/entity/pref.dart'; -import 'package:nc_photos/object_extension.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:nc_photos/controller/pref_controller.dart'; import 'package:nc_photos/theme/dimension.dart'; import 'package:nc_photos/theme/material3.dart'; -const defaultSeedColor = 0xFF2196F3; +const defaultSeedColor = Color(0xFF2196F3); extension ThemeExtension on ThemeData { double get widthLimitedContentMaxWidth => 550.0; @@ -88,20 +88,20 @@ class DarkModeSwitchTheme extends StatelessWidget { final Widget child; } -ThemeData buildTheme(Brightness brightness) { +ThemeData buildTheme(BuildContext context, Brightness brightness) { return (brightness == Brightness.light) - ? buildLightTheme() - : buildDarkTheme(); + ? buildLightTheme(context) + : buildDarkTheme(context); } -ThemeData buildLightTheme([ColorScheme? dynamicScheme]) { - final colorScheme = _getColorScheme(dynamicScheme, Brightness.light); +ThemeData buildLightTheme(BuildContext context, [ColorScheme? dynamicScheme]) { + final colorScheme = _getColorScheme(context, dynamicScheme, Brightness.light); return _applyColorScheme(colorScheme); } -ThemeData buildDarkTheme([ColorScheme? dynamicScheme]) { - final colorScheme = _getColorScheme(dynamicScheme, Brightness.dark); - if (Pref().isUseBlackInDarkThemeOr(false)) { +ThemeData buildDarkTheme(BuildContext context, [ColorScheme? dynamicScheme]) { + final colorScheme = _getColorScheme(context, dynamicScheme, Brightness.dark); + if (context.read().isUseBlackInDarkTheme.value) { return _applyColorScheme(colorScheme.copyWith( background: Colors.black, surface: Colors.grey[900], @@ -111,12 +111,13 @@ ThemeData buildDarkTheme([ColorScheme? dynamicScheme]) { } } -Color? getSeedColor() { - return Pref().getSeedColor()?.run((c) => Color(c).withAlpha(0xFF)); +Color? getSeedColor(BuildContext context) { + return context.read().seedColor.value; } -ColorScheme _getColorScheme(ColorScheme? dynamicScheme, Brightness brightness) { - var seedColor = Pref().getSeedColor(); +ColorScheme _getColorScheme( + BuildContext context, ColorScheme? dynamicScheme, Brightness brightness) { + var seedColor = getSeedColor(context); if (seedColor == null) { if (dynamicScheme != null) { return dynamicScheme; @@ -125,7 +126,7 @@ ColorScheme _getColorScheme(ColorScheme? dynamicScheme, Brightness brightness) { } } return ColorScheme.fromSeed( - seedColor: Color(seedColor), + seedColor: seedColor, brightness: brightness, ); } diff --git a/app/lib/widget/account_picker_dialog.dart b/app/lib/widget/account_picker_dialog.dart index 380687ec..84cecc26 100644 --- a/app/lib/widget/account_picker_dialog.dart +++ b/app/lib/widget/account_picker_dialog.dart @@ -4,7 +4,6 @@ import 'dart:math'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:clock/clock.dart'; import 'package:copy_with/copy_with.dart'; -import 'package:event_bus/event_bus.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:kiwi/kiwi.dart'; @@ -15,15 +14,16 @@ import 'package:nc_photos/api/api_util.dart' as api_util; import 'package:nc_photos/app_localizations.dart'; import 'package:nc_photos/bloc_util.dart'; import 'package:nc_photos/controller/account_controller.dart'; +import 'package:nc_photos/controller/pref_controller.dart'; import 'package:nc_photos/di_container.dart'; import 'package:nc_photos/entity/pref.dart'; import 'package:nc_photos/entity/server_status.dart'; import 'package:nc_photos/entity/sqlite/database.dart' as sql; -import 'package:nc_photos/event/event.dart'; import 'package:nc_photos/exception_event.dart'; import 'package:nc_photos/exception_util.dart' as exception_util; import 'package:nc_photos/help_utils.dart' as help_util; import 'package:nc_photos/k.dart' as k; +import 'package:nc_photos/stream_util.dart'; import 'package:nc_photos/theme.dart'; import 'package:nc_photos/toast.dart'; import 'package:nc_photos/url_launcher_util.dart'; @@ -50,6 +50,7 @@ class AccountPickerDialog extends StatelessWidget { create: (context) => _Bloc( container: KiwiContainer().resolve(), accountController: context.read(), + prefController: context.read(), ), child: const _WrappedAccountPickerDialog(), ); @@ -112,13 +113,27 @@ class _WrappedAccountPickerDialog extends StatelessWidget { style: Theme.of(context).textTheme.titleLarge, ), ), - if (!Pref().isFollowSystemThemeOr(false)) - Align( - alignment: AlignmentDirectional.centerEnd, - child: _DarkModeSwitch( - onChanged: _onDarkModeChanged, - ), - ), + ValueStreamBuilder( + stream: context + .read() + .isFollowSystemTheme, + builder: (_, isFollowSystemTheme) { + if (!isFollowSystemTheme.requireData) { + return Align( + alignment: AlignmentDirectional.centerEnd, + child: _DarkModeSwitch( + onChanged: (value) { + context + .read<_Bloc>() + .add(_SetDarkTheme(value)); + }, + ), + ); + } else { + return const SizedBox.shrink(); + } + }, + ), ], ), const SizedBox(height: 8), @@ -192,12 +207,6 @@ class _WrappedAccountPickerDialog extends StatelessWidget { ), ); } - - void _onDarkModeChanged(bool value) { - Pref().setDarkTheme(value).then((_) { - KiwiContainer().resolve().fire(ThemeChangedEvent()); - }); - } } class _DarkModeSwitch extends StatelessWidget { diff --git a/app/lib/widget/account_picker_dialog.g.dart b/app/lib/widget/account_picker_dialog.g.dart index d2bbcb18..a56a6cdf 100644 --- a/app/lib/widget/account_picker_dialog.g.dart +++ b/app/lib/widget/account_picker_dialog.g.dart @@ -89,6 +89,13 @@ extension _$_DeleteAccountToString on _DeleteAccount { } } +extension _$_SetDarkThemeToString on _SetDarkTheme { + String _$toString() { + // ignore: unnecessary_string_interpolations + return "_SetDarkTheme {value: $value}"; + } +} + extension _$_SetErrorToString on _SetError { String _$toString() { // ignore: unnecessary_string_interpolations diff --git a/app/lib/widget/account_picker_dialog/bloc.dart b/app/lib/widget/account_picker_dialog/bloc.dart index bfa439c3..7df9b775 100644 --- a/app/lib/widget/account_picker_dialog/bloc.dart +++ b/app/lib/widget/account_picker_dialog/bloc.dart @@ -5,6 +5,7 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger { _Bloc({ required DiContainer container, required this.accountController, + required this.prefController, }) : _c = container, super(_State.init( accounts: container.pref.getAccounts3Or([]), @@ -12,6 +13,7 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger { on<_ToggleDropdown>(_onToggleDropdown); on<_SwitchAccount>(_onSwitchAccount); on<_DeleteAccount>(_onDeleteAccount); + on<_SetDarkTheme>(_onSetDarkTheme); on<_SetError>(_onSetError); } @@ -86,6 +88,11 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger { } } + void _onSetDarkTheme(_SetDarkTheme ev, Emitter<_State> emit) { + _log.info(ev); + prefController.setDarkTheme(ev.value); + } + void _onSetError(_SetError ev, Emitter<_State> emit) { _log.info(ev); emit(state.copyWith(error: ExceptionEvent(ev.error, ev.stackTrace))); @@ -104,6 +111,7 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger { final DiContainer _c; final AccountController accountController; + final PrefController prefController; late final Account activeAccount = accountController.account; final _prefLock = Mutex(); diff --git a/app/lib/widget/account_picker_dialog/state_event.dart b/app/lib/widget/account_picker_dialog/state_event.dart index 0b0689bc..03df25bd 100644 --- a/app/lib/widget/account_picker_dialog/state_event.dart +++ b/app/lib/widget/account_picker_dialog/state_event.dart @@ -60,6 +60,16 @@ class _DeleteAccount implements _Event { final Account account; } +@toString +class _SetDarkTheme implements _Event { + const _SetDarkTheme(this.value); + + @override + String toString() => _$toString(); + + final bool value; +} + @toString class _SetError implements _Event { const _SetError(this.error, [this.stackTrace]); diff --git a/app/lib/widget/connect.dart b/app/lib/widget/connect.dart index 4f72085a..32e1669e 100644 --- a/app/lib/widget/connect.dart +++ b/app/lib/widget/connect.dart @@ -73,7 +73,7 @@ class _ConnectState extends State { @override build(BuildContext context) { return Theme( - data: buildDarkTheme().copyWith( + data: buildDarkTheme(context).copyWith( scaffoldBackgroundColor: Theme.of(context).nextcloudBlue, progressIndicatorTheme: const ProgressIndicatorThemeData( color: Colors.white, diff --git a/app/lib/widget/image_editor.dart b/app/lib/widget/image_editor.dart index 0c31a87b..6bcb4b8a 100644 --- a/app/lib/widget/image_editor.dart +++ b/app/lib/widget/image_editor.dart @@ -87,7 +87,7 @@ class _ImageEditorState extends State { @override build(BuildContext context) => Theme( - data: buildDarkTheme(), + data: buildDarkTheme(context), child: Scaffold( body: Builder( builder: _buildContent, diff --git a/app/lib/widget/image_enhancer.dart b/app/lib/widget/image_enhancer.dart index eda803c5..d234c80d 100644 --- a/app/lib/widget/image_enhancer.dart +++ b/app/lib/widget/image_enhancer.dart @@ -88,7 +88,7 @@ class _ImageEnhancerState extends State { @override build(BuildContext context) => Theme( - data: buildDarkTheme(), + data: buildDarkTheme(context), child: Scaffold( body: Builder( builder: _buildContent, diff --git a/app/lib/widget/local_file_viewer.dart b/app/lib/widget/local_file_viewer.dart index 5b8b6ff9..4e48a42e 100644 --- a/app/lib/widget/local_file_viewer.dart +++ b/app/lib/widget/local_file_viewer.dart @@ -53,7 +53,7 @@ class _LocalFileViewerState extends State { @override build(BuildContext context) { return Theme( - data: buildDarkTheme(), + data: buildDarkTheme(context), child: Scaffold( body: Builder( builder: _buildContent, diff --git a/app/lib/widget/my_app.dart b/app/lib/widget/my_app.dart index 64395dff..ceeb4a96 100644 --- a/app/lib/widget/my_app.dart +++ b/app/lib/widget/my_app.dart @@ -1,3 +1,4 @@ +import 'package:copy_with/copy_with.dart'; import 'package:dynamic_color/dynamic_color.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; @@ -6,18 +7,16 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:kiwi/kiwi.dart'; import 'package:logging/logging.dart'; +import 'package:nc_photos/bloc_util.dart'; import 'package:nc_photos/controller/account_controller.dart'; import 'package:nc_photos/controller/pref_controller.dart'; import 'package:nc_photos/di_container.dart'; -import 'package:nc_photos/entity/pref.dart'; -import 'package:nc_photos/event/event.dart'; import 'package:nc_photos/language_util.dart' as language_util; import 'package:nc_photos/legacy/connect.dart' as legacy; import 'package:nc_photos/legacy/sign_in.dart' as legacy; import 'package:nc_photos/navigation_manager.dart'; import 'package:nc_photos/session_storage.dart'; import 'package:nc_photos/snack_bar_manager.dart'; -import 'package:nc_photos/stream_util.dart'; import 'package:nc_photos/theme.dart'; import 'package:nc_photos/widget/album_dir_picker.dart'; import 'package:nc_photos/widget/album_importer.dart'; @@ -51,8 +50,15 @@ import 'package:nc_photos/widget/trashbin_browser.dart'; import 'package:nc_photos/widget/trashbin_viewer.dart'; import 'package:nc_photos/widget/viewer.dart'; import 'package:np_codegen/np_codegen.dart'; +import 'package:to_string/to_string.dart'; part 'my_app.g.dart'; +part 'my_app/bloc.dart'; +part 'my_app/state_event.dart'; + +typedef _BlocBuilder = BlocBuilder<_Bloc, _State>; +// typedef _BlocListener = BlocListener<_Bloc, _State>; +// typedef _BlocSelector = BlocSelector<_Bloc, _State, T>; class MyApp extends StatelessWidget { const MyApp({super.key}); @@ -69,7 +75,12 @@ class MyApp extends StatelessWidget { create: (_) => PrefController(_c), ), ], - child: const _WrappedApp(), + child: BlocProvider( + create: (context) => _Bloc( + prefController: context.read(), + ), + child: const _WrappedApp(), + ), ); } @@ -96,43 +107,48 @@ class _WrappedAppState extends State<_WrappedApp> super.initState(); SnackBarManager().registerHandler(this); NavigationManager().setHandler(this); - _themeChangedListener = - AppEventListener(_onThemeChangedEvent)..begin(); + + _bloc.add(const _Init()); } @override Widget build(BuildContext context) { - final ThemeMode themeMode; - if (Pref().isFollowSystemThemeOr(false)) { - themeMode = ThemeMode.system; - } else { - themeMode = - Pref().isDarkThemeOr(false) ? ThemeMode.dark : ThemeMode.light; - } - final prefController = context.read(); - return ValueStreamBuilder( - stream: prefController.language, - builder: (context, snapshot) => DynamicColorBuilder( + return _BlocBuilder( + buildWhen: (previous, current) => + previous.language != current.language || + previous.isDarkTheme != current.isDarkTheme || + previous.isFollowSystemTheme != current.isFollowSystemTheme || + previous.isUseBlackInDarkTheme != current.isUseBlackInDarkTheme || + previous.seedColor != current.seedColor, + builder: (context, state) => DynamicColorBuilder( builder: (lightDynamic, darkDynamic) { if (lightDynamic != null) { SessionStorage().isSupportDynamicColor = true; } + final ThemeMode themeMode; + if (state.isFollowSystemTheme) { + themeMode = ThemeMode.system; + } else { + themeMode = state.isDarkTheme ? ThemeMode.dark : ThemeMode.light; + } return MaterialApp( onGenerateTitle: (context) => AppLocalizations.of(context)!.appTitle, - theme: buildLightTheme(lightDynamic), - darkTheme: buildDarkTheme(darkDynamic), + theme: buildLightTheme(context, lightDynamic), + darkTheme: buildDarkTheme(context, darkDynamic), themeMode: themeMode, initialRoute: Splash.routeName, onGenerateRoute: _onGenerateRoute, - navigatorObservers: [MyApp.routeObserver], + navigatorObservers: [ + MyApp.routeObserver, + ], navigatorKey: _navigatorKey, scaffoldMessengerKey: _scaffoldMessengerKey, - locale: snapshot.requireData.locale, + locale: state.language.locale, localizationsDelegates: AppLocalizations.localizationsDelegates, - supportedLocales: const [ - // the order here doesn't matter, except for the first one, which must - // be en + supportedLocales: const [ + // the order here doesn't matter, except for the first one, which + // must be en Locale("en"), Locale("el"), Locale("es"), @@ -166,7 +182,6 @@ class _WrappedAppState extends State<_WrappedApp> super.dispose(); SnackBarManager().unregisterHandler(this); NavigationManager().unsetHandler(this); - _themeChangedListener.end(); } @override @@ -227,10 +242,6 @@ class _WrappedAppState extends State<_WrappedApp> return route; } - void _onThemeChangedEvent(ThemeChangedEvent ev) { - setState(() {}); - } - Route? _handleBasicRoute(RouteSettings settings) { for (final e in _getRouter().entries) { if (e.key == settings.name) { @@ -578,10 +589,9 @@ class _WrappedAppState extends State<_WrappedApp> return null; } + late final _bloc = context.read<_Bloc>(); final _scaffoldMessengerKey = GlobalKey(); final _navigatorKey = GlobalKey(); - - late AppEventListener _themeChangedListener; } class _ThemedMyApp extends StatelessWidget { diff --git a/app/lib/widget/my_app.g.dart b/app/lib/widget/my_app.g.dart index 3a7d740c..a51476b4 100644 --- a/app/lib/widget/my_app.g.dart +++ b/app/lib/widget/my_app.g.dart @@ -2,6 +2,54 @@ part of 'my_app.dart'; +// ************************************************************************** +// CopyWithLintRuleGenerator +// ************************************************************************** + +// ignore_for_file: library_private_types_in_public_api, duplicate_ignore + +// ************************************************************************** +// CopyWithGenerator +// ************************************************************************** + +abstract class $_StateCopyWithWorker { + _State call( + {language_util.AppLanguage? language, + bool? isDarkTheme, + bool? isFollowSystemTheme, + bool? isUseBlackInDarkTheme, + int? seedColor}); +} + +class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker { + _$_StateCopyWithWorkerImpl(this.that); + + @override + _State call( + {dynamic language, + dynamic isDarkTheme, + dynamic isFollowSystemTheme, + dynamic isUseBlackInDarkTheme, + dynamic seedColor = copyWithNull}) { + return _State( + language: language as language_util.AppLanguage? ?? that.language, + isDarkTheme: isDarkTheme as bool? ?? that.isDarkTheme, + isFollowSystemTheme: + isFollowSystemTheme as bool? ?? that.isFollowSystemTheme, + isUseBlackInDarkTheme: + isUseBlackInDarkTheme as bool? ?? that.isUseBlackInDarkTheme, + seedColor: + seedColor == copyWithNull ? that.seedColor : seedColor as int?); + } + + final _State that; +} + +extension $_StateCopyWith on _State { + $_StateCopyWithWorker get copyWith => _$copyWith; + $_StateCopyWithWorker get _$copyWith => _$_StateCopyWithWorkerImpl(this); +} + // ************************************************************************** // NpLogGenerator // ************************************************************************** @@ -12,3 +60,28 @@ extension _$_WrappedAppStateNpLog on _WrappedAppState { static final log = Logger("widget.my_app._WrappedAppState"); } + +extension _$_BlocNpLog on _Bloc { + // ignore: unused_element + Logger get _log => log; + + static final log = Logger("widget.my_app._Bloc"); +} + +// ************************************************************************** +// ToStringGenerator +// ************************************************************************** + +extension _$_StateToString on _State { + String _$toString() { + // ignore: unnecessary_string_interpolations + return "_State {language: $language, isDarkTheme: $isDarkTheme, isFollowSystemTheme: $isFollowSystemTheme, isUseBlackInDarkTheme: $isUseBlackInDarkTheme, seedColor: $seedColor}"; + } +} + +extension _$_InitToString on _Init { + String _$toString() { + // ignore: unnecessary_string_interpolations + return "_Init {}"; + } +} diff --git a/app/lib/widget/my_app/bloc.dart b/app/lib/widget/my_app/bloc.dart new file mode 100644 index 00000000..01fd5a2f --- /dev/null +++ b/app/lib/widget/my_app/bloc.dart @@ -0,0 +1,47 @@ +part of '../my_app.dart'; + +@npLog +class _Bloc extends Bloc<_Event, _State> with BlocLogger { + _Bloc({ + required this.prefController, + }) : super(_State( + language: prefController.language.value, + isDarkTheme: prefController.isDarkTheme.value, + isFollowSystemTheme: prefController.isFollowSystemTheme.value, + isUseBlackInDarkTheme: prefController.isUseBlackInDarkTheme.value, + seedColor: prefController.seedColor.value?.value, + )) { + on<_Init>(_onInit); + } + + @override + String get tag => _log.fullName; + + Future _onInit(_Init ev, Emitter<_State> emit) async { + _log.info(ev); + await Future.wait([ + emit.forEachIgnoreError( + prefController.language, + onData: (data) => state.copyWith(language: data), + ), + emit.forEachIgnoreError( + prefController.isDarkTheme, + onData: (data) => state.copyWith(isDarkTheme: data), + ), + emit.forEachIgnoreError( + prefController.isFollowSystemTheme, + onData: (data) => state.copyWith(isFollowSystemTheme: data), + ), + emit.forEachIgnoreError( + prefController.isUseBlackInDarkTheme, + onData: (data) => state.copyWith(isUseBlackInDarkTheme: data), + ), + emit.forEachIgnoreError( + prefController.seedColor, + onData: (data) => state.copyWith(seedColor: data?.value), + ), + ]); + } + + final PrefController prefController; +} diff --git a/app/lib/widget/my_app/state_event.dart b/app/lib/widget/my_app/state_event.dart new file mode 100644 index 00000000..d920879c --- /dev/null +++ b/app/lib/widget/my_app/state_event.dart @@ -0,0 +1,32 @@ +part of '../my_app.dart'; + +@genCopyWith +@toString +class _State { + const _State({ + required this.language, + required this.isDarkTheme, + required this.isFollowSystemTheme, + required this.isUseBlackInDarkTheme, + required this.seedColor, + }); + + @override + String toString() => _$toString(); + + final language_util.AppLanguage language; + final bool isDarkTheme; + final bool isFollowSystemTheme; + final bool isUseBlackInDarkTheme; + final int? seedColor; +} + +abstract class _Event {} + +@toString +class _Init implements _Event { + const _Init(); + + @override + String toString() => _$toString(); +} diff --git a/app/lib/widget/result_viewer.dart b/app/lib/widget/result_viewer.dart index e3096041..65264664 100644 --- a/app/lib/widget/result_viewer.dart +++ b/app/lib/widget/result_viewer.dart @@ -61,7 +61,7 @@ class _ResultViewerState extends State { build(BuildContext context) { if (_file == null) { return Theme( - data: buildDarkTheme(), + data: buildDarkTheme(context), child: Scaffold( appBar: AppBar( backgroundColor: Colors.black, diff --git a/app/lib/widget/settings/theme/bloc.dart b/app/lib/widget/settings/theme/bloc.dart index 64f63dde..1ba9a765 100644 --- a/app/lib/widget/settings/theme/bloc.dart +++ b/app/lib/widget/settings/theme/bloc.dart @@ -1,76 +1,68 @@ part of '../theme_settings.dart'; -class _Error { - const _Error(this.ev); - - final _Event ev; -} - @npLog class _Bloc extends Bloc<_Event, _State> with BlocLogger { - _Bloc(DiContainer c) - : assert(require(c)), - _c = c, - super(_State( - isFollowSystemTheme: c.pref.isFollowSystemThemeOr(false), - isUseBlackInDarkTheme: c.pref.isUseBlackInDarkThemeOr(false), - seedColor: getSeedColor()?.value, + _Bloc({ + required this.prefController, + }) : super(_State( + isFollowSystemTheme: prefController.isFollowSystemTheme.value, + isUseBlackInDarkTheme: prefController.isUseBlackInDarkTheme.value, + seedColor: prefController.seedColor.value?.value, )) { + on<_Init>(_onInit); on<_SetFollowSystemTheme>(_onSetFollowSystemTheme); on<_SetUseBlackInDarkTheme>(_onSetUseBlackInDarkTheme); on<_SetSeedColor>(_onSetSeedColor); } - static bool require(DiContainer c) => DiContainer.has(c, DiType.pref); - @override String get tag => _log.fullName; - Stream<_Error> errorStream() => _errorStream.stream; - - Future _onSetFollowSystemTheme( - _SetFollowSystemTheme ev, Emitter<_State> emit) async { + Future _onInit(_Init ev, Emitter<_State> emit) async { _log.info(ev); - final oldValue = state.isFollowSystemTheme; - emit(state.copyWith(isFollowSystemTheme: ev.value)); - if (await _c.pref.setFollowSystemTheme(ev.value)) { - KiwiContainer().resolve().fire(ThemeChangedEvent()); - } else { - _log.severe("[_onSetFollowSystemTheme] Failed writing pref"); - _errorStream.add(_Error(ev)); - emit(state.copyWith(isFollowSystemTheme: oldValue)); - } + await Future.wait([ + emit.forEach( + prefController.isFollowSystemTheme, + onData: (data) => state.copyWith(isFollowSystemTheme: data), + onError: (e, stackTrace) { + _log.severe("[_onInit] Uncaught exception", e, stackTrace); + return state.copyWith(error: ExceptionEvent(e, stackTrace)); + }, + ), + emit.forEach( + prefController.isUseBlackInDarkTheme, + onData: (data) => state.copyWith(isUseBlackInDarkTheme: data), + onError: (e, stackTrace) { + _log.severe("[_onInit] Uncaught exception", e, stackTrace); + return state.copyWith(error: ExceptionEvent(e, stackTrace)); + }, + ), + emit.forEach( + prefController.seedColor, + onData: (data) => state.copyWith(seedColor: data?.value), + onError: (e, stackTrace) { + _log.severe("[_onInit] Uncaught exception", e, stackTrace); + return state.copyWith(error: ExceptionEvent(e, stackTrace)); + }, + ), + ]); } - Future _onSetUseBlackInDarkTheme( - _SetUseBlackInDarkTheme ev, Emitter<_State> emit) async { + void _onSetFollowSystemTheme(_SetFollowSystemTheme ev, Emitter<_State> emit) { _log.info(ev); - final oldValue = state.isUseBlackInDarkTheme; - emit(state.copyWith(isUseBlackInDarkTheme: ev.value)); - if (await _c.pref.setUseBlackInDarkTheme(ev.value)) { - if (ev.theme.brightness == Brightness.dark) { - KiwiContainer().resolve().fire(ThemeChangedEvent()); - } - } else { - _log.severe("[_onSetUseBlackInDarkTheme] Failed writing pref"); - _errorStream.add(_Error(ev)); - emit(state.copyWith(isUseBlackInDarkTheme: oldValue)); - } + prefController.setFollowSystemTheme(ev.value); } - Future _onSetSeedColor(_SetSeedColor ev, Emitter<_State> emit) async { + void _onSetUseBlackInDarkTheme( + _SetUseBlackInDarkTheme ev, Emitter<_State> emit) { _log.info(ev); - final oldValue = state.seedColor; - emit(state.copyWith(seedColor: ev.value?.value)); - if (await _c.pref.setSeedColor(ev.value?.withAlpha(0xFF).value)) { - KiwiContainer().resolve().fire(ThemeChangedEvent()); - } else { - _log.severe("[_onSetSeedColor] Failed writing pref"); - _errorStream.add(_Error(ev)); - emit(state.copyWith(seedColor: oldValue)); - } + prefController.setUseBlackInDarkTheme(ev.value); } - final DiContainer _c; - final _errorStream = StreamController<_Error>.broadcast(); + void _onSetSeedColor(_SetSeedColor ev, Emitter<_State> emit) { + _log.info(ev); + prefController.setSeedColor(ev.value); + } + + final PrefController prefController; } diff --git a/app/lib/widget/settings/theme/state_event.dart b/app/lib/widget/settings/theme/state_event.dart index e4d962a1..9b82d0e8 100644 --- a/app/lib/widget/settings/theme/state_event.dart +++ b/app/lib/widget/settings/theme/state_event.dart @@ -7,6 +7,7 @@ class _State { required this.isFollowSystemTheme, required this.isUseBlackInDarkTheme, required this.seedColor, + this.error, }); @override @@ -16,12 +17,22 @@ class _State { final bool isUseBlackInDarkTheme; // workaround analyzer bug where Color type can't be recognized final int? seedColor; + + final ExceptionEvent? error; } abstract class _Event { const _Event(); } +@toString +class _Init implements _Event { + const _Init(); + + @override + String toString() => _$toString(); +} + @toString class _SetFollowSystemTheme extends _Event { const _SetFollowSystemTheme(this.value); diff --git a/app/lib/widget/settings/theme_settings.dart b/app/lib/widget/settings/theme_settings.dart index 0d866f10..60b95d1f 100644 --- a/app/lib/widget/settings/theme_settings.dart +++ b/app/lib/widget/settings/theme_settings.dart @@ -1,23 +1,23 @@ import 'dart:async'; import 'package:copy_with/copy_with.dart'; -import 'package:event_bus/event_bus.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_colorpicker/flutter_colorpicker.dart'; -import 'package:kiwi/kiwi.dart'; import 'package:logging/logging.dart'; import 'package:nc_photos/app_localizations.dart'; import 'package:nc_photos/bloc_util.dart'; -import 'package:nc_photos/di_container.dart'; -import 'package:nc_photos/entity/pref.dart'; -import 'package:nc_photos/event/event.dart'; +import 'package:nc_photos/controller/pref_controller.dart'; +import 'package:nc_photos/exception_event.dart'; +import 'package:nc_photos/exception_util.dart' as exception_util; import 'package:nc_photos/k.dart' as k; import 'package:nc_photos/mobile/android/android_info.dart'; +import 'package:nc_photos/object_extension.dart'; import 'package:nc_photos/platform/k.dart' as platform_k; import 'package:nc_photos/session_storage.dart'; import 'package:nc_photos/snack_bar_manager.dart'; import 'package:nc_photos/theme.dart'; +import 'package:nc_photos/widget/page_visibility_mixin.dart'; import 'package:np_codegen/np_codegen.dart'; import 'package:to_string/to_string.dart'; @@ -25,7 +25,9 @@ part 'theme/bloc.dart'; part 'theme/state_event.dart'; part 'theme_settings.g.dart'; -typedef _BlocBuilder = BlocBuilder<_Bloc, _State>; +// typedef _BlocBuilder = BlocBuilder<_Bloc, _State>; +typedef _BlocListener = BlocListener<_Bloc, _State>; +typedef _BlocSelector = BlocSelector<_Bloc, _State, T>; class ThemeSettings extends StatelessWidget { const ThemeSettings({super.key}); @@ -33,7 +35,9 @@ class ThemeSettings extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (_) => _Bloc(KiwiContainer().resolve()), + create: (_) => _Bloc( + prefController: context.read(), + ), child: const _WrappedThemeSettings(), ); } @@ -47,89 +51,86 @@ class _WrappedThemeSettings extends StatefulWidget { } @npLog -class _WrappedThemeSettingsState extends State<_WrappedThemeSettings> { +class _WrappedThemeSettingsState extends State<_WrappedThemeSettings> + with RouteAware, PageVisibilityMixin { @override void initState() { super.initState(); - _errorSubscription = _bloc.errorStream().listen((_) { - SnackBarManager().showSnackBar(SnackBar( - content: Text(L10n.global().writePreferenceFailureNotification), - duration: k.snackBarDurationNormal, - )); - }); - } - - @override - void dispose() { - _errorSubscription.cancel(); - super.dispose(); + _bloc.add(const _Init()); } @override Widget build(BuildContext context) { return Scaffold( - body: Builder( - builder: (context) => _buildContent(context), + body: MultiBlocListener( + listeners: [ + _BlocListener( + listenWhen: (previous, current) => previous.error != current.error, + listener: (context, state) { + if (state.error != null && isPageVisible()) { + SnackBarManager().showSnackBar(SnackBar( + content: + Text(exception_util.toUserString(state.error!.error)), + duration: k.snackBarDurationNormal, + )); + } + }, + ), + ], + child: CustomScrollView( + slivers: [ + SliverAppBar( + pinned: true, + title: Text(L10n.global().settingsThemeTitle), + ), + SliverList( + delegate: SliverChildListDelegate( + [ + const _SeedColorOption(), + if (platform_k.isAndroid && + AndroidInfo().sdkInt >= AndroidVersion.Q) + _BlocSelector( + selector: (state) => state.isFollowSystemTheme, + builder: (_, isFollowSystemTheme) { + return SwitchListTile( + title: Text( + L10n.global().settingsFollowSystemThemeTitle), + value: isFollowSystemTheme, + onChanged: (value) { + _bloc.add(_SetFollowSystemTheme(value)); + }, + ); + }, + ), + _BlocSelector( + selector: (state) => state.isUseBlackInDarkTheme, + builder: (context, isUseBlackInDarkTheme) { + return SwitchListTile( + title: Text( + L10n.global().settingsUseBlackInDarkThemeTitle), + subtitle: Text(isUseBlackInDarkTheme + ? L10n.global() + .settingsUseBlackInDarkThemeTrueDescription + : L10n.global() + .settingsUseBlackInDarkThemeFalseDescription), + value: isUseBlackInDarkTheme, + onChanged: (value) { + _bloc.add(_SetUseBlackInDarkTheme( + value, Theme.of(context))); + }, + ); + }, + ), + ], + ), + ), + ], + ), ), ); } - Widget _buildContent(BuildContext context) { - return CustomScrollView( - slivers: [ - SliverAppBar( - pinned: true, - title: Text(L10n.global().settingsThemeTitle), - ), - SliverList( - delegate: SliverChildListDelegate( - [ - const _SeedColorOption(), - if (platform_k.isAndroid && - AndroidInfo().sdkInt >= AndroidVersion.Q) - _BlocBuilder( - buildWhen: (previous, current) => - previous.isFollowSystemTheme != - current.isFollowSystemTheme, - builder: (context, state) { - return SwitchListTile( - title: Text(L10n.global().settingsFollowSystemThemeTitle), - value: state.isFollowSystemTheme, - onChanged: (value) { - _bloc.add(_SetFollowSystemTheme(value)); - }, - ); - }, - ), - _BlocBuilder( - buildWhen: (previous, current) => - previous.isUseBlackInDarkTheme != - current.isUseBlackInDarkTheme, - builder: (context, state) { - return SwitchListTile( - title: Text(L10n.global().settingsUseBlackInDarkThemeTitle), - subtitle: Text(state.isUseBlackInDarkTheme - ? L10n.global() - .settingsUseBlackInDarkThemeTrueDescription - : L10n.global() - .settingsUseBlackInDarkThemeFalseDescription), - value: state.isUseBlackInDarkTheme, - onChanged: (value) { - _bloc.add( - _SetUseBlackInDarkTheme(value, Theme.of(context))); - }, - ); - }, - ), - ], - ), - ), - ], - ); - } - late final _bloc = context.read<_Bloc>(); - late final StreamSubscription _errorSubscription; } class _SeedColorOption extends StatelessWidget { @@ -137,21 +138,21 @@ class _SeedColorOption extends StatelessWidget { @override Widget build(BuildContext context) { - return _BlocBuilder( - buildWhen: (previous, current) => previous.seedColor != current.seedColor, - builder: (context, state) { + return _BlocSelector( + selector: (state) => state.seedColor, + builder: (context, seedColor) { if (SessionStorage().isSupportDynamicColor) { return ListTile( title: Text(L10n.global().settingsSeedColorTitle), - subtitle: Text(state.seedColor == null + subtitle: Text(seedColor == null ? L10n.global().settingsSeedColorSystemColorDescription : L10n.global().settingsSeedColorDescription), - trailing: state.seedColor == null + trailing: seedColor == null ? null : Icon( Icons.circle, size: 32, - color: Color(state.seedColor!), + color: Color(seedColor), ), onTap: () => _onSeedColorPressed(context), ); @@ -162,7 +163,7 @@ class _SeedColorOption extends StatelessWidget { trailing: Icon( Icons.circle, size: 32, - color: Color(state.seedColor ?? defaultSeedColor), + color: seedColor?.run(Color.new) ?? defaultSeedColor, ), onTap: () => _onSeedColorPressed(context), ); @@ -281,7 +282,7 @@ class _SeedColorCustomPickerState extends State<_SeedColorCustomPicker> { ); } - late Color _customColor = getSeedColor() ?? const Color(defaultSeedColor); + late var _customColor = getSeedColor(context) ?? defaultSeedColor; } class _SeedColorPickerItem extends StatelessWidget { diff --git a/app/lib/widget/settings/theme_settings.g.dart b/app/lib/widget/settings/theme_settings.g.dart index a4a8a42f..e2b0e24f 100644 --- a/app/lib/widget/settings/theme_settings.g.dart +++ b/app/lib/widget/settings/theme_settings.g.dart @@ -14,7 +14,10 @@ part of 'theme_settings.dart'; abstract class $_StateCopyWithWorker { _State call( - {bool? isFollowSystemTheme, bool? isUseBlackInDarkTheme, int? seedColor}); + {bool? isFollowSystemTheme, + bool? isUseBlackInDarkTheme, + int? seedColor, + ExceptionEvent? error}); } class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker { @@ -24,14 +27,16 @@ class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker { _State call( {dynamic isFollowSystemTheme, dynamic isUseBlackInDarkTheme, - dynamic seedColor = copyWithNull}) { + dynamic seedColor = copyWithNull, + dynamic error = copyWithNull}) { return _State( isFollowSystemTheme: isFollowSystemTheme as bool? ?? that.isFollowSystemTheme, isUseBlackInDarkTheme: isUseBlackInDarkTheme as bool? ?? that.isUseBlackInDarkTheme, seedColor: - seedColor == copyWithNull ? that.seedColor : seedColor as int?); + seedColor == copyWithNull ? that.seedColor : seedColor as int?, + error: error == copyWithNull ? that.error : error as ExceptionEvent?); } final _State that; @@ -68,7 +73,14 @@ extension _$_BlocNpLog on _Bloc { extension _$_StateToString on _State { String _$toString() { // ignore: unnecessary_string_interpolations - return "_State {isFollowSystemTheme: $isFollowSystemTheme, isUseBlackInDarkTheme: $isUseBlackInDarkTheme, seedColor: $seedColor}"; + return "_State {isFollowSystemTheme: $isFollowSystemTheme, isUseBlackInDarkTheme: $isUseBlackInDarkTheme, seedColor: $seedColor, error: $error}"; + } +} + +extension _$_InitToString on _Init { + String _$toString() { + // ignore: unnecessary_string_interpolations + return "_Init {}"; } } diff --git a/app/lib/widget/sign_in.dart b/app/lib/widget/sign_in.dart index 1c96f460..afccd2ee 100644 --- a/app/lib/widget/sign_in.dart +++ b/app/lib/widget/sign_in.dart @@ -37,7 +37,7 @@ class _SignInState extends State { @override build(BuildContext context) { return Theme( - data: buildDarkTheme().copyWith( + data: buildDarkTheme(context).copyWith( scaffoldBackgroundColor: Colors.transparent, progressIndicatorTheme: const ProgressIndicatorThemeData( color: Colors.white, diff --git a/app/lib/widget/slideshow_viewer.dart b/app/lib/widget/slideshow_viewer.dart index 5deb221b..34a69d8e 100644 --- a/app/lib/widget/slideshow_viewer.dart +++ b/app/lib/widget/slideshow_viewer.dart @@ -100,7 +100,7 @@ class _SlideshowViewerState extends State @override build(BuildContext context) { return Theme( - data: buildDarkTheme(), + data: buildDarkTheme(context), child: Scaffold( body: Builder( builder: _buildContent, diff --git a/app/lib/widget/trashbin_viewer.dart b/app/lib/widget/trashbin_viewer.dart index 781a437f..242216ee 100644 --- a/app/lib/widget/trashbin_viewer.dart +++ b/app/lib/widget/trashbin_viewer.dart @@ -65,7 +65,7 @@ class _TrashbinViewerState extends State { @override build(BuildContext context) { return Theme( - data: buildDarkTheme(), + data: buildDarkTheme(context), child: Scaffold( body: Builder( builder: _buildContent, diff --git a/app/lib/widget/viewer.dart b/app/lib/widget/viewer.dart index c22f19d5..1c60efa0 100644 --- a/app/lib/widget/viewer.dart +++ b/app/lib/widget/viewer.dart @@ -120,7 +120,7 @@ class _ViewerState extends State build(BuildContext context) { final originalBrightness = Theme.of(context).brightness; return Theme( - data: buildDarkTheme(), + data: buildDarkTheme(context), child: AnnotatedRegion( value: const SystemUiOverlayStyle( systemNavigationBarColor: Colors.black, @@ -299,7 +299,7 @@ class _ViewerState extends State } }, child: Theme( - data: buildTheme(originalBrightness), + data: buildTheme(context, originalBrightness), child: Builder( builder: (context) { return Container(