Refactoring: migrate ThemeSettings to use bloc

This commit is contained in:
Ming Ming 2022-12-31 15:51:54 +08:00
parent 232be4d267
commit 7b1a70d1d1
6 changed files with 331 additions and 95 deletions

View file

@ -0,0 +1,139 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:copy_with/copy_with.dart';
import 'package:event_bus/event_bus.dart';
import 'package:flutter/material.dart';
import 'package:kiwi/kiwi.dart';
import 'package:logging/logging.dart';
import 'package:nc_photos/di_container.dart';
import 'package:nc_photos/event/event.dart';
import 'package:nc_photos/theme.dart';
import 'package:np_codegen/np_codegen.dart';
import 'package:to_string/to_string.dart';
part 'theme.g.dart';
@autoCopyWith
@toString
class ThemeSettingsState {
const ThemeSettingsState({
required this.isFollowSystemTheme,
required this.isUseBlackInDarkTheme,
required this.seedColor,
});
@override
String toString() => _$toString();
final bool isFollowSystemTheme;
final bool isUseBlackInDarkTheme;
final Color seedColor;
}
abstract class ThemeSettingsEvent {
const ThemeSettingsEvent();
}
@toString
class ThemeSettingsSetFollowSystemTheme extends ThemeSettingsEvent {
const ThemeSettingsSetFollowSystemTheme(this.value);
@override
String toString() => _$toString();
final bool value;
}
@toString
class ThemeSettingsSetUseBlackInDarkTheme extends ThemeSettingsEvent {
const ThemeSettingsSetUseBlackInDarkTheme(this.value, this.theme);
@override
String toString() => _$toString();
final bool value;
final ThemeData theme;
}
@toString
class ThemeSettingsSetSeedColor extends ThemeSettingsEvent {
const ThemeSettingsSetSeedColor(this.value);
@override
String toString() => _$toString();
final Color value;
}
class ThemeSettingsError {
const ThemeSettingsError(this.ev, [this.error, this.stackTrace]);
final ThemeSettingsEvent ev;
final Object? error;
final StackTrace? stackTrace;
}
@npLog
class ThemeSettingsBloc extends Bloc<ThemeSettingsEvent, ThemeSettingsState> {
ThemeSettingsBloc(DiContainer c)
: assert(require(c)),
_c = c,
super(ThemeSettingsState(
isFollowSystemTheme: c.pref.isFollowSystemThemeOr(false),
isUseBlackInDarkTheme: c.pref.isUseBlackInDarkThemeOr(false),
seedColor: getSeedColor(),
)) {
on<ThemeSettingsSetFollowSystemTheme>(_onSetFollowSystemTheme);
on<ThemeSettingsSetUseBlackInDarkTheme>(_onSetUseBlackInDarkTheme);
on<ThemeSettingsSetSeedColor>(_onSetSeedColor);
}
static bool require(DiContainer c) => DiContainer.has(c, DiType.pref);
Stream<ThemeSettingsError> errorStream() => _errorStream.stream;
Future<void> _onSetFollowSystemTheme(ThemeSettingsSetFollowSystemTheme ev,
Emitter<ThemeSettingsState> emit) async {
final oldValue = state.isFollowSystemTheme;
emit(state.copyWith(isFollowSystemTheme: ev.value));
if (await _c.pref.setFollowSystemTheme(ev.value)) {
KiwiContainer().resolve<EventBus>().fire(ThemeChangedEvent());
} else {
_log.severe("[_onSetFollowSystemTheme] Failed writing pref");
_errorStream.add(ThemeSettingsError(ev));
emit(state.copyWith(isFollowSystemTheme: oldValue));
}
}
Future<void> _onSetUseBlackInDarkTheme(ThemeSettingsSetUseBlackInDarkTheme ev,
Emitter<ThemeSettingsState> emit) async {
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<EventBus>().fire(ThemeChangedEvent());
}
} else {
_log.severe("[_onSetUseBlackInDarkTheme] Failed writing pref");
_errorStream.add(ThemeSettingsError(ev));
emit(state.copyWith(isUseBlackInDarkTheme: oldValue));
}
}
Future<void> _onSetSeedColor(
ThemeSettingsSetSeedColor ev, Emitter<ThemeSettingsState> emit) async {
final oldValue = state.seedColor;
emit(state.copyWith(seedColor: ev.value));
if (await _c.pref.setSeedColor(ev.value.withAlpha(0xFF).value)) {
KiwiContainer().resolve<EventBus>().fire(ThemeChangedEvent());
} else {
_log.severe("[_onSetSeedColor] Failed writing pref");
_errorStream.add(ThemeSettingsError(ev));
emit(state.copyWith(seedColor: oldValue));
}
}
final DiContainer _c;
final _errorStream = StreamController<ThemeSettingsError>.broadcast();
}

View file

@ -0,0 +1,74 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'theme.dart';
// **************************************************************************
// CopyWithGenerator
// **************************************************************************
extension $ThemeSettingsStateCopyWith on ThemeSettingsState {
ThemeSettingsState copyWith(
{bool? isFollowSystemTheme,
bool? isUseBlackInDarkTheme,
Color? seedColor}) =>
_$copyWith(
isFollowSystemTheme: isFollowSystemTheme,
isUseBlackInDarkTheme: isUseBlackInDarkTheme,
seedColor: seedColor);
ThemeSettingsState _$copyWith(
{bool? isFollowSystemTheme,
bool? isUseBlackInDarkTheme,
Color? seedColor}) {
return ThemeSettingsState(
isFollowSystemTheme: isFollowSystemTheme ?? this.isFollowSystemTheme,
isUseBlackInDarkTheme:
isUseBlackInDarkTheme ?? this.isUseBlackInDarkTheme,
seedColor: seedColor ?? this.seedColor);
}
}
// **************************************************************************
// NpLogGenerator
// **************************************************************************
extension _$ThemeSettingsBlocNpLog on ThemeSettingsBloc {
// ignore: unused_element
Logger get _log => log;
static final log = Logger("bloc.settings.theme.ThemeSettingsBloc");
}
// **************************************************************************
// ToStringGenerator
// **************************************************************************
extension _$ThemeSettingsStateToString on ThemeSettingsState {
String _$toString() {
// ignore: unnecessary_string_interpolations
return "ThemeSettingsState {isFollowSystemTheme: $isFollowSystemTheme, isUseBlackInDarkTheme: $isUseBlackInDarkTheme, seedColor: $seedColor}";
}
}
extension _$ThemeSettingsSetFollowSystemThemeToString
on ThemeSettingsSetFollowSystemTheme {
String _$toString() {
// ignore: unnecessary_string_interpolations
return "ThemeSettingsSetFollowSystemTheme {value: $value}";
}
}
extension _$ThemeSettingsSetUseBlackInDarkThemeToString
on ThemeSettingsSetUseBlackInDarkTheme {
String _$toString() {
// ignore: unnecessary_string_interpolations
return "ThemeSettingsSetUseBlackInDarkTheme {value: $value, theme: $theme}";
}
}
extension _$ThemeSettingsSetSeedColorToString on ThemeSettingsSetSeedColor {
String _$toString() {
// ignore: unnecessary_string_interpolations
return "ThemeSettingsSetSeedColor {value: $value}";
}
}

View file

@ -1,39 +1,63 @@
import 'package:event_bus/event_bus.dart';
import 'dart:async';
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/event/event.dart';
import 'package:nc_photos/bloc/settings/theme.dart';
import 'package:nc_photos/di_container.dart';
import 'package:nc_photos/k.dart' as k;
import 'package:nc_photos/mobile/android/android_info.dart';
import 'package:nc_photos/platform/k.dart' as platform_k;
import 'package:nc_photos/pref.dart';
import 'package:nc_photos/snack_bar_manager.dart';
import 'package:nc_photos/theme.dart';
import 'package:np_codegen/np_codegen.dart';
part 'theme_settings.g.dart';
class ThemeSettings extends StatefulWidget {
class ThemeSettings extends StatelessWidget {
const ThemeSettings({super.key});
@override
createState() => _ThemeSettingsState();
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => ThemeSettingsBloc(KiwiContainer().resolve<DiContainer>()),
child: const _WrappedThemeSettings(),
);
}
}
class _WrappedThemeSettings extends StatefulWidget {
const _WrappedThemeSettings();
@override
State<StatefulWidget> createState() => _WrappedThemeSettingsState();
}
@npLog
class _ThemeSettingsState extends State<ThemeSettings> {
class _WrappedThemeSettingsState extends State<_WrappedThemeSettings> {
@override
initState() {
void initState() {
super.initState();
_isFollowSystemTheme = Pref().isFollowSystemThemeOr(false);
_isUseBlackInDarkTheme = Pref().isUseBlackInDarkThemeOr(false);
_seedColor = getSeedColor();
_errorSubscription =
context.read<ThemeSettingsBloc>().errorStream().listen((_) {
SnackBarManager().showSnackBar(SnackBar(
content: Text(L10n.global().writePreferenceFailureNotification),
duration: k.snackBarDurationNormal,
));
});
}
@override
build(BuildContext context) {
void dispose() {
_errorSubscription.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Builder(
builder: (context) => _buildContent(context),
@ -51,32 +75,60 @@ class _ThemeSettingsState extends State<ThemeSettings> {
SliverList(
delegate: SliverChildListDelegate(
[
ListTile(
title: Text(L10n.global().settingsSeedColorTitle),
subtitle: Text(L10n.global().settingsSeedColorDescription),
trailing: Icon(
Icons.circle,
size: 32,
color: _seedColor,
),
onTap: () => _onSeedColorPressed(context),
BlocBuilder<ThemeSettingsBloc, ThemeSettingsState>(
buildWhen: (previous, current) =>
previous.seedColor != current.seedColor,
builder: (context, state) {
return ListTile(
title: Text(L10n.global().settingsSeedColorTitle),
subtitle: Text(L10n.global().settingsSeedColorDescription),
trailing: Icon(
Icons.circle,
size: 32,
color: state.seedColor,
),
onTap: () => _onSeedColorPressed(context),
);
},
),
if (platform_k.isAndroid &&
AndroidInfo().sdkInt >= AndroidVersion.Q)
SwitchListTile(
title: Text(L10n.global().settingsFollowSystemThemeTitle),
value: _isFollowSystemTheme,
onChanged: (value) => _onFollowSystemThemeChanged(value),
BlocBuilder<ThemeSettingsBloc, ThemeSettingsState>(
buildWhen: (previous, current) =>
previous.isFollowSystemTheme !=
current.isFollowSystemTheme,
builder: (context, state) {
return SwitchListTile(
title: Text(L10n.global().settingsFollowSystemThemeTitle),
value: state.isFollowSystemTheme,
onChanged: (value) {
context
.read<ThemeSettingsBloc>()
.add(ThemeSettingsSetFollowSystemTheme(value));
},
);
},
),
SwitchListTile(
title: Text(L10n.global().settingsUseBlackInDarkThemeTitle),
subtitle: Text(_isUseBlackInDarkTheme
? L10n.global().settingsUseBlackInDarkThemeTrueDescription
: L10n.global()
.settingsUseBlackInDarkThemeFalseDescription),
value: _isUseBlackInDarkTheme,
onChanged: (value) =>
_onUseBlackInDarkThemeChanged(context, value),
BlocBuilder<ThemeSettingsBloc, ThemeSettingsState>(
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) {
context.read<ThemeSettingsBloc>().add(
ThemeSettingsSetUseBlackInDarkTheme(
value, Theme.of(context)));
},
);
},
),
],
),
@ -85,47 +137,6 @@ class _ThemeSettingsState extends State<ThemeSettings> {
);
}
Future<void> _onFollowSystemThemeChanged(bool value) async {
final oldValue = _isFollowSystemTheme;
setState(() {
_isFollowSystemTheme = value;
});
if (await Pref().setFollowSystemTheme(value)) {
KiwiContainer().resolve<EventBus>().fire(ThemeChangedEvent());
} else {
_log.severe("[_onFollowSystemThemeChanged] Failed writing pref");
SnackBarManager().showSnackBar(SnackBar(
content: Text(L10n.global().writePreferenceFailureNotification),
duration: k.snackBarDurationNormal,
));
setState(() {
_isFollowSystemTheme = oldValue;
});
}
}
Future<void> _onUseBlackInDarkThemeChanged(
BuildContext context, bool value) async {
final oldValue = _isUseBlackInDarkTheme;
setState(() {
_isUseBlackInDarkTheme = value;
});
if (await Pref().setUseBlackInDarkTheme(value)) {
if (Theme.of(context).brightness == Brightness.dark) {
KiwiContainer().resolve<EventBus>().fire(ThemeChangedEvent());
}
} else {
_log.severe("[_onUseBlackInDarkThemeChanged] Failed writing pref");
SnackBarManager().showSnackBar(SnackBar(
content: Text(L10n.global().writePreferenceFailureNotification),
duration: k.snackBarDurationNormal,
));
setState(() {
_isUseBlackInDarkTheme = oldValue;
});
}
}
Future<void> _onSeedColorPressed(BuildContext context) async {
final result = await showDialog<Color>(
context: context,
@ -134,28 +145,12 @@ class _ThemeSettingsState extends State<ThemeSettings> {
if (result == null) {
return;
}
final oldValue = _seedColor;
setState(() {
_seedColor = result;
});
if (await Pref().setSeedColor(result.withAlpha(0xFF).value)) {
KiwiContainer().resolve<EventBus>().fire(ThemeChangedEvent());
} else {
_log.severe("[_onSeedColorPressed] Failed writing pref");
SnackBarManager().showSnackBar(SnackBar(
content: Text(L10n.global().writePreferenceFailureNotification),
duration: k.snackBarDurationNormal,
));
setState(() {
_seedColor = oldValue;
});
if (mounted) {
context.read<ThemeSettingsBloc>().add(ThemeSettingsSetSeedColor(result));
}
}
late bool _isFollowSystemTheme;
late bool _isUseBlackInDarkTheme;
late Color _seedColor;
late final StreamSubscription _errorSubscription;
}
class _SeedColorPicker extends StatefulWidget {

View file

@ -6,10 +6,10 @@ part of 'theme_settings.dart';
// NpLogGenerator
// **************************************************************************
extension _$_ThemeSettingsStateNpLog on _ThemeSettingsState {
extension _$_WrappedThemeSettingsStateNpLog on _WrappedThemeSettingsState {
// ignore: unused_element
Logger get _log => log;
static final log =
Logger("widget.settings.theme_settings._ThemeSettingsState");
Logger("widget.settings.theme_settings._WrappedThemeSettingsState");
}

View file

@ -297,6 +297,24 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.2"
copy_with:
dependency: "direct main"
description:
path: copy_with
ref: "copy_with-1.0.0"
resolved-ref: c3ef6b3b5337f99ee7c0e1fb655bacf635d8b072
url: "https://gitlab.com/nkming2/dart-copy-with"
source: git
version: "1.0.0"
copy_with_build:
dependency: "direct dev"
description:
path: copy_with_build
ref: "copy_with_build-1.0.0"
resolved-ref: "8303676d56dc16bc2fb1e38ad95c5d97b493e613"
url: "https://gitlab.com/nkming2/dart-copy-with"
source: git
version: "1.0.0"
coverage:
dependency: transitive
description:

View file

@ -40,6 +40,11 @@ dependencies:
circular_reveal_animation: ^2.0.1
collection: ^1.15.0
connectivity_plus: ^2.0.2
copy_with:
git:
url: https://gitlab.com/nkming2/dart-copy-with
path: copy_with
ref: copy_with-1.0.0
devicelocale: ^0.5.0
device_info_plus: ^4.0.0
draggable_scrollbar:
@ -124,6 +129,11 @@ dev_dependencies:
test: any
bloc_test: any
build_runner: ^2.1.11
copy_with_build:
git:
url: https://gitlab.com/nkming2/dart-copy-with
path: copy_with_build
ref: copy_with_build-1.0.0
drift_dev: ^1.7.0
flutter_lints: ^2.0.1
flutter_test: