diff --git a/app/lib/controller/pref_controller.dart b/app/lib/controller/pref_controller.dart index 9db45bd3..e96da528 100644 --- a/app/lib/controller/pref_controller.dart +++ b/app/lib/controller/pref_controller.dart @@ -108,6 +108,15 @@ class PrefController { value: value, ); + ValueStream get isAlbumBrowserShowDate => + _isAlbumBrowserShowDateController.stream; + + Future setAlbumBrowserShowDate(bool value) => _set( + controller: _isAlbumBrowserShowDateController, + setter: (pref, value) => pref.setAlbumBrowserShowDate(value), + value: value, + ); + Future _set({ required BehaviorSubject controller, required Future Function(Pref pref, T value) setter, @@ -162,4 +171,6 @@ class PrefController { BehaviorSubject.seeded(_c.pref.isViewerForceRotationOr(false)); late final _gpsMapProviderController = BehaviorSubject.seeded( GpsMapProvider.fromValue(_c.pref.getGpsMapProviderOr(0))); + late final _isAlbumBrowserShowDateController = + BehaviorSubject.seeded(_c.pref.isAlbumBrowserShowDateOr(false)); } diff --git a/app/lib/widget/settings.dart b/app/lib/widget/settings.dart index 48d122de..3406cde2 100644 --- a/app/lib/widget/settings.dart +++ b/app/lib/widget/settings.dart @@ -18,6 +18,7 @@ import 'package:nc_photos/snack_bar_manager.dart'; import 'package:nc_photos/stream_util.dart'; import 'package:nc_photos/url_launcher_util.dart'; import 'package:nc_photos/widget/list_tile_center_leading.dart'; +import 'package:nc_photos/widget/settings/collection_settings.dart'; import 'package:nc_photos/widget/settings/developer_settings.dart'; import 'package:nc_photos/widget/settings/expert_settings.dart'; import 'package:nc_photos/widget/settings/language_settings.dart'; @@ -117,7 +118,7 @@ class _SettingsState extends State { _SubPageItem( leading: const Icon(Icons.grid_view_outlined), label: L10n.global().collectionsTooltip, - pageBuilder: () => _AlbumSettings(), + pageBuilder: () => const CollectionSettings(), ), _SubPageItem( leading: const Icon(Icons.view_carousel_outlined), @@ -290,72 +291,6 @@ class _SubPageItem extends StatelessWidget { final Widget Function() pageBuilder; } -class _AlbumSettings extends StatefulWidget { - @override - createState() => _AlbumSettingsState(); -} - -@npLog -class _AlbumSettingsState extends State<_AlbumSettings> { - @override - initState() { - super.initState(); - _isBrowserShowDate = Pref().isAlbumBrowserShowDateOr(); - } - - @override - build(BuildContext context) { - return Scaffold( - body: Builder( - builder: (context) => _buildContent(context), - ), - ); - } - - Widget _buildContent(BuildContext context) { - return CustomScrollView( - slivers: [ - SliverAppBar( - pinned: true, - title: Text(L10n.global().collectionsTooltip), - ), - SliverList( - delegate: SliverChildListDelegate( - [ - SwitchListTile( - title: Text(L10n.global().settingsShowDateInAlbumTitle), - subtitle: - Text(L10n.global().settingsShowDateInAlbumDescription), - value: _isBrowserShowDate, - onChanged: (value) => _onBrowserShowDateChanged(value), - ), - ], - ), - ), - ], - ); - } - - Future _onBrowserShowDateChanged(bool value) async { - final oldValue = _isBrowserShowDate; - setState(() { - _isBrowserShowDate = value; - }); - if (!await Pref().setAlbumBrowserShowDate(value)) { - _log.severe("[_onBrowserShowDateChanged] Failed writing pref"); - SnackBarManager().showSnackBar(SnackBar( - content: Text(L10n.global().writePreferenceFailureNotification), - duration: k.snackBarDurationNormal, - )); - setState(() { - _isBrowserShowDate = oldValue; - }); - } - } - - late bool _isBrowserShowDate; -} - class EnhancementSettings extends StatefulWidget { static const routeName = "/enhancement-settings"; diff --git a/app/lib/widget/settings.g.dart b/app/lib/widget/settings.g.dart index cb3beb01..cb5f1776 100644 --- a/app/lib/widget/settings.g.dart +++ b/app/lib/widget/settings.g.dart @@ -13,13 +13,6 @@ extension _$_SettingsStateNpLog on _SettingsState { static final log = Logger("widget.settings._SettingsState"); } -extension _$_AlbumSettingsStateNpLog on _AlbumSettingsState { - // ignore: unused_element - Logger get _log => log; - - static final log = Logger("widget.settings._AlbumSettingsState"); -} - extension _$_EnhancementSettingsStateNpLog on _EnhancementSettingsState { // ignore: unused_element Logger get _log => log; diff --git a/app/lib/widget/settings/collection/bloc.dart b/app/lib/widget/settings/collection/bloc.dart new file mode 100644 index 00000000..8f295c68 --- /dev/null +++ b/app/lib/widget/settings/collection/bloc.dart @@ -0,0 +1,35 @@ +part of '../collection_settings.dart'; + +@npLog +class _Bloc extends Bloc<_Event, _State> with BlocLogger { + _Bloc({ + required this.prefController, + }) : super(_State( + isBrowserShowDate: prefController.isAlbumBrowserShowDate.value, + )) { + on<_Init>(_onInit); + on<_SetBrowserShowDate>(_onSetBrowserShowDate); + } + + @override + String get tag => _log.fullName; + + Future _onInit(_Init ev, Emitter<_State> emit) async { + _log.info(ev); + return emit.forEach( + prefController.isAlbumBrowserShowDate, + onData: (data) => state.copyWith(isBrowserShowDate: data), + onError: (e, stackTrace) { + _log.severe("[_onInit] Uncaught exception", e, stackTrace); + return state.copyWith(error: ExceptionEvent(e, stackTrace)); + }, + ); + } + + void _onSetBrowserShowDate(_SetBrowserShowDate ev, Emitter<_State> emit) { + _log.info(ev); + prefController.setAlbumBrowserShowDate(ev.value); + } + + final PrefController prefController; +} diff --git a/app/lib/widget/settings/collection/state_event.dart b/app/lib/widget/settings/collection/state_event.dart new file mode 100644 index 00000000..7bfe8548 --- /dev/null +++ b/app/lib/widget/settings/collection/state_event.dart @@ -0,0 +1,39 @@ +part of '../collection_settings.dart'; + +@genCopyWith +@toString +class _State { + const _State({ + required this.isBrowserShowDate, + this.error, + }); + + @override + String toString() => _$toString(); + + final bool isBrowserShowDate; + + final ExceptionEvent? error; +} + +abstract class _Event { + const _Event(); +} + +@toString +class _Init implements _Event { + const _Init(); + + @override + String toString() => _$toString(); +} + +@toString +class _SetBrowserShowDate implements _Event { + const _SetBrowserShowDate(this.value); + + @override + String toString() => _$toString(); + + final bool value; +} diff --git a/app/lib/widget/settings/collection_settings.dart b/app/lib/widget/settings/collection_settings.dart new file mode 100644 index 00000000..c7b17667 --- /dev/null +++ b/app/lib/widget/settings/collection_settings.dart @@ -0,0 +1,104 @@ +import 'package:copy_with/copy_with.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:logging/logging.dart'; +import 'package:nc_photos/app_localizations.dart'; +import 'package:nc_photos/bloc_util.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/snack_bar_manager.dart'; +import 'package:nc_photos/widget/page_visibility_mixin.dart'; +import 'package:np_codegen/np_codegen.dart'; +import 'package:to_string/to_string.dart'; + +part 'collection/bloc.dart'; +part 'collection/state_event.dart'; +part 'collection_settings.g.dart'; + +typedef _BlocListener = BlocListener<_Bloc, _State>; +typedef _BlocSelector = BlocSelector<_Bloc, _State, T>; + +class CollectionSettings extends StatelessWidget { + const CollectionSettings({super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (_) => _Bloc( + prefController: context.read(), + ), + child: const _WrappedAlbumSettings(), + ); + } +} + +class _WrappedAlbumSettings extends StatefulWidget { + const _WrappedAlbumSettings(); + + @override + State createState() => _WrappedAlbumSettingsState(); +} + +@npLog +class _WrappedAlbumSettingsState extends State<_WrappedAlbumSettings> + with RouteAware, PageVisibilityMixin { + @override + void initState() { + super.initState(); + _bloc.add(const _Init()); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + 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().settingsViewerTitle), + ), + SliverList( + delegate: SliverChildListDelegate( + [ + _BlocSelector( + selector: (state) => state.isBrowserShowDate, + builder: (context, state) { + return SwitchListTile( + title: Text(L10n.global().settingsShowDateInAlbumTitle), + subtitle: Text( + L10n.global().settingsShowDateInAlbumDescription), + value: state, + onChanged: (value) { + _bloc.add(_SetBrowserShowDate(value)); + }, + ); + }, + ), + ], + ), + ), + ], + ), + ), + ); + } + + late final _bloc = context.read<_Bloc>(); +} diff --git a/app/lib/widget/settings/collection_settings.g.dart b/app/lib/widget/settings/collection_settings.g.dart new file mode 100644 index 00000000..283c2880 --- /dev/null +++ b/app/lib/widget/settings/collection_settings.g.dart @@ -0,0 +1,79 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'collection_settings.dart'; + +// ************************************************************************** +// CopyWithLintRuleGenerator +// ************************************************************************** + +// ignore_for_file: library_private_types_in_public_api, duplicate_ignore + +// ************************************************************************** +// CopyWithGenerator +// ************************************************************************** + +abstract class $_StateCopyWithWorker { + _State call({bool? isBrowserShowDate, ExceptionEvent? error}); +} + +class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker { + _$_StateCopyWithWorkerImpl(this.that); + + @override + _State call({dynamic isBrowserShowDate, dynamic error = copyWithNull}) { + return _State( + isBrowserShowDate: isBrowserShowDate as bool? ?? that.isBrowserShowDate, + error: error == copyWithNull ? that.error : error as ExceptionEvent?); + } + + final _State that; +} + +extension $_StateCopyWith on _State { + $_StateCopyWithWorker get copyWith => _$copyWith; + $_StateCopyWithWorker get _$copyWith => _$_StateCopyWithWorkerImpl(this); +} + +// ************************************************************************** +// NpLogGenerator +// ************************************************************************** + +extension _$_WrappedAlbumSettingsStateNpLog on _WrappedAlbumSettingsState { + // ignore: unused_element + Logger get _log => log; + + static final log = + Logger("widget.settings.collection_settings._WrappedAlbumSettingsState"); +} + +extension _$_BlocNpLog on _Bloc { + // ignore: unused_element + Logger get _log => log; + + static final log = Logger("widget.settings.collection_settings._Bloc"); +} + +// ************************************************************************** +// ToStringGenerator +// ************************************************************************** + +extension _$_StateToString on _State { + String _$toString() { + // ignore: unnecessary_string_interpolations + return "_State {isBrowserShowDate: $isBrowserShowDate, error: $error}"; + } +} + +extension _$_InitToString on _Init { + String _$toString() { + // ignore: unnecessary_string_interpolations + return "_Init {}"; + } +} + +extension _$_SetBrowserShowDateToString on _SetBrowserShowDate { + String _$toString() { + // ignore: unnecessary_string_interpolations + return "_SetBrowserShowDate {value: $value}"; + } +}