From fa64ba9cac81b0bfb28245152355ee01ef3eb1ec Mon Sep 17 00:00:00 2001 From: James Aprosail Date: Sun, 30 Jun 2024 02:12:30 +0800 Subject: [PATCH 1/5] theme adapt brightness --- lib/src/theme.dart | 81 +++++++++++++++----------------------------- test/theme_test.dart | 35 ++++++++++++++++--- 2 files changed, 57 insertions(+), 59 deletions(-) diff --git a/lib/src/theme.dart b/lib/src/theme.dart index 5d8eb14..e082e7d 100644 --- a/lib/src/theme.dart +++ b/lib/src/theme.dart @@ -40,6 +40,10 @@ extension WrapTheme on Widget { ); } +/// Handle a [Theme] and the [ThemeMode], +/// and it will also provide the [Brightness] of current theme. +/// And you can also modify current [Theme] and [ThemeMode] from +/// its descendants in the widget tree via the context. class ThemeHandler extends StatefulWidget { const ThemeHandler({ super.key, @@ -63,19 +67,9 @@ class _ThemeHandlerState extends State> { late T _dark = widget.dark; late ThemeMode _mode = widget.mode; - late T _theme = theme; - late Brightness _brightness = brightness; - - /// Compute what theme ([T]) should be now according to [_brightness]. - /// It's not recommended to call it directly, consider using [_theme] - /// to reduce unnecessary computations. - T get theme => _brightness == Brightness.dark ? _dark : _light; - /// Compute what [Brightness] should be now according to [_mode] /// and [MediaQueryData.platformBrightness] of current platform. - /// It's not recommended to call it directly, consider using [_brightness] - /// to reduce unnecessary computations. - Brightness get brightness => _mode == ThemeMode.system + Brightness get adaptedBrightness => _mode == ThemeMode.system ? MediaQuery.of(context).platformBrightness : _mode == ThemeMode.dark ? Brightness.dark @@ -84,59 +78,38 @@ class _ThemeHandlerState extends State> { @override void didUpdateWidget(covariant ThemeHandler oldWidget) { super.didUpdateWidget(oldWidget); - - var needSetState = false; - if (widget.mode != _mode) needSetState = _preUpdateMode(widget.mode); - - if (widget.light != _light) { - _light = widget.light; - if (_brightness == Brightness.light) needSetState = true; - } - if (widget.dark != _dark) { - _dark = widget.dark; - if (_brightness == Brightness.dark) needSetState = true; - } - - if (needSetState) setState(() {}); + if (_mode != widget.mode) setState(() => _mode = widget.mode); + if (_dark != widget.dark) setState(() => _dark = widget.dark); + if (_light != widget.light) setState(() => _light = widget.light); } void updateMode(ThemeMode Function(ThemeMode raw) updater) { - if (_preUpdateMode(updater(_mode))) setState(() {}); - } - - bool _preUpdateMode(ThemeMode mode) { - if (mode == _mode) return false; - _mode = mode; - final brightness = this.brightness; - if (_brightness != brightness) { - _brightness = brightness; - _theme = theme; - } - return true; + final mode = updater(_mode); + if (_mode != mode) setState(() => _mode = mode); } void updateCurrentTheme(T Function(T raw) updater) { - if (_preUpdateCurrentTheme(updater(_theme))) setState(() {}); - } - - bool _preUpdateCurrentTheme(T theme) { - if (_theme == theme) return false; - switch (_brightness) { + switch (adaptedBrightness) { case Brightness.dark: - _dark = theme; + final theme = updater(_dark); + if (_dark != theme) setState(() => _dark = theme); case Brightness.light: - _light = theme; + final theme = updater(_light); + if (_light != theme) setState(() => _light = theme); } - return true; } @override - Widget build(BuildContext context) => widget.child - .foreground(context, _theme.foreground) - .background(_theme.background) - .inherit(_brightness) - .inherit(_theme) - .inherit(_mode) - .inherit(InheritHandlerAPI(updateMode)) - .inherit(InheritHandlerAPI(updateCurrentTheme)); + Widget build(BuildContext context) { + final brightness = adaptedBrightness; + final theme = adaptedBrightness == Brightness.dark ? _dark : _light; + return widget.child + .foreground(context, theme.foreground) + .background(theme.background) + .inherit(brightness) + .inherit(theme) + .inherit(_mode) + .inherit(InheritHandlerAPI(updateMode)) + .inherit(InheritHandlerAPI(updateCurrentTheme)); + } } diff --git a/test/theme_test.dart b/test/theme_test.dart index c1345a3..81b4ed1 100644 --- a/test/theme_test.dart +++ b/test/theme_test.dart @@ -6,6 +6,9 @@ import 'package:modifier/modifier.dart'; import 'package:modifier_test/modifier_test.dart'; void main() { + const light = CustomizedTheme.light(); + const dark = CustomizedTheme.dark(); + testWidgets('platform media', (t) async { await builder((context) => 'brightness: ${MediaQuery.of(context).platformBrightness.name}' @@ -27,11 +30,33 @@ void main() { expect(find.text('brightness: ${Brightness.dark.name}'), findsOneWidget); }); - testWidgets('theme adapt', (t) async { - const light = CustomizedTheme.light(); - expect(light, light); - const dark = CustomizedTheme.dark(); - expect(dark, dark); + testWidgets('brightness adapt', (t) async { + await builder((context) { + final platformBrightness = MediaQuery.of(context).platformBrightness; + return [ + 'brightness: ${context.findAndTrust().name}'.asText, + 'mode: ${context.findAndTrust().name}'.asText, + 'platform: ${platformBrightness.name}'.asText, + ].asColumn; + }) + .center + .builder((context, child) => child + .theme(light: light, dark: dark) + .ensureDirection(context) + .ensureMedia(context)) + .pump(t); + + t.binding.platformDispatcher.platformBrightnessTestValue = Brightness.light; + await t.pump(); + expect(find.text('brightness: ${Brightness.light.name}'), findsOneWidget); + expect(find.text('mode: ${ThemeMode.system.name}'), findsOneWidget); + expect(find.text('platform: ${Brightness.light.name}'), findsOneWidget); + + t.binding.platformDispatcher.platformBrightnessTestValue = Brightness.dark; + await t.pump(); + expect(find.text('brightness: ${Brightness.dark.name}'), findsOneWidget); + expect(find.text('mode: ${ThemeMode.system.name}'), findsOneWidget); + expect(find.text('platform: ${Brightness.dark.name}'), findsOneWidget); }); } From 7564dda0c07deea25342ac817b692aea8df9a583 Mon Sep 17 00:00:00 2001 From: James Aprosail Date: Sun, 30 Jun 2024 02:34:53 +0800 Subject: [PATCH 2/5] fix theme mode updater inherit --- lib/src/theme.dart | 7 ++--- test/theme_test.dart | 62 +++++++++++++++++++++++++++++++++++++++----- 2 files changed, 57 insertions(+), 12 deletions(-) diff --git a/lib/src/theme.dart b/lib/src/theme.dart index e082e7d..1c61fb6 100644 --- a/lib/src/theme.dart +++ b/lib/src/theme.dart @@ -83,18 +83,15 @@ class _ThemeHandlerState extends State> { if (_light != widget.light) setState(() => _light = widget.light); } - void updateMode(ThemeMode Function(ThemeMode raw) updater) { - final mode = updater(_mode); + void updateMode(ThemeMode mode) { if (_mode != mode) setState(() => _mode = mode); } - void updateCurrentTheme(T Function(T raw) updater) { + void updateCurrentTheme(T theme) { switch (adaptedBrightness) { case Brightness.dark: - final theme = updater(_dark); if (_dark != theme) setState(() => _dark = theme); case Brightness.light: - final theme = updater(_light); if (_light != theme) setState(() => _light = theme); } } diff --git a/test/theme_test.dart b/test/theme_test.dart index 81b4ed1..fd4f00b 100644 --- a/test/theme_test.dart +++ b/test/theme_test.dart @@ -34,29 +34,77 @@ void main() { await builder((context) { final platformBrightness = MediaQuery.of(context).platformBrightness; return [ + 'platform: ${platformBrightness.name}'.asText, 'brightness: ${context.findAndTrust().name}'.asText, 'mode: ${context.findAndTrust().name}'.asText, - 'platform: ${platformBrightness.name}'.asText, ].asColumn; }) .center - .builder((context, child) => child - .theme(light: light, dark: dark) - .ensureDirection(context) - .ensureMedia(context)) + .theme(light: light, dark: dark) + .builder((context, child) => child.ensureDirection(context)) + .builder((context, child) => child.ensureMedia(context)) .pump(t); t.binding.platformDispatcher.platformBrightnessTestValue = Brightness.light; await t.pump(); + expect(find.text('platform: ${Brightness.light.name}'), findsOneWidget); expect(find.text('brightness: ${Brightness.light.name}'), findsOneWidget); expect(find.text('mode: ${ThemeMode.system.name}'), findsOneWidget); - expect(find.text('platform: ${Brightness.light.name}'), findsOneWidget); t.binding.platformDispatcher.platformBrightnessTestValue = Brightness.dark; await t.pump(); + expect(find.text('platform: ${Brightness.dark.name}'), findsOneWidget); expect(find.text('brightness: ${Brightness.dark.name}'), findsOneWidget); expect(find.text('mode: ${ThemeMode.system.name}'), findsOneWidget); - expect(find.text('platform: ${Brightness.dark.name}'), findsOneWidget); + }); + + testWidgets('change theme mode', (t) async { + await builder((context) { + final theme = context.findAndTrust(); + final platformBrightness = MediaQuery.of(context).platformBrightness; + void updateThemeMode(ThemeMode mode) => + context.updateAndCheck((_) => mode); + + return [ + 'platform: ${platformBrightness.name}'.asText, + 'brightness: ${context.findAndTrust().name}'.asText, + 'mode: ${context.findAndTrust().name}'.asText, + 'background: ${theme.background.hex}'.asText, + 'foreground: ${theme.foreground.hex}'.asText, + 'to system'.asText.on(tap: () => updateThemeMode(ThemeMode.system)), + 'to light'.asText.on(tap: () => updateThemeMode(ThemeMode.light)), + 'to dark'.asText.on(tap: () => updateThemeMode(ThemeMode.dark)), + ].asColumn; + }) + .center + .theme(light: light, dark: dark) + .builder((context, child) => child.ensureDirection(context)) + .builder((context, child) => child.ensureMedia(context)) + .pump(t); + + t.binding.platformDispatcher.platformBrightnessTestValue = Brightness.light; + await t.pump(); + expect(find.text('platform: ${Brightness.light.name}'), findsOneWidget); + expect(find.text('brightness: ${Brightness.light.name}'), findsOneWidget); + expect(find.text('mode: ${ThemeMode.system.name}'), findsOneWidget); + expect(find.text('background: ${light.background.hex}'), findsOneWidget); + expect(find.text('foreground: ${light.foreground.hex}'), findsOneWidget); + + await t.tap(find.text('to dark')); + await t.pump(); + expect(find.text('platform: ${Brightness.light.name}'), findsOneWidget); + expect(find.text('brightness: ${Brightness.dark.name}'), findsOneWidget); + expect(find.text('mode: ${ThemeMode.dark.name}'), findsOneWidget); + expect(find.text('background: ${dark.background.hex}'), findsOneWidget); + expect(find.text('foreground: ${dark.foreground.hex}'), findsOneWidget); + + await t.tap(find.text('to light')); + await t.pump(); + expect(find.text('platform: ${Brightness.light.name}'), findsOneWidget); + expect(find.text('brightness: ${Brightness.light.name}'), findsOneWidget); + expect(find.text('mode: ${ThemeMode.light.name}'), findsOneWidget); + expect(find.text('background: ${light.background.hex}'), findsOneWidget); + expect(find.text('foreground: ${light.foreground.hex}'), findsOneWidget); }); } From 865b0df7949871c1377814f8f3b616b1d6c94da8 Mon Sep 17 00:00:00 2001 From: James Aprosail Date: Sun, 30 Jun 2024 02:37:03 +0800 Subject: [PATCH 3/5] changelog 0.4 --- CHANGELOG.md | 6 ++++++ pubspec.yaml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b18e4e..cab8406 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.4.0 + +- Theme template and theme handler. +- Support customized theme on mixin. +- Theme handler adapt system brightness change. + ## 0.3.0 - Wrap media with default value. diff --git a/pubspec.yaml b/pubspec.yaml index 0662e46..26e1a25 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: modifier description: Syntax sugar optimizations to avoid nesting hell in Flutter. -version: 0.3.0 +version: 0.4.0 homepage: https://github.com/treeinfra/modifier repository: https://github.com/treeinfra/modifier environment: {sdk: ">=3.4.3 <4.0.0", flutter: ">=3.22.2"} From 65c675938536e9f931087eeb3921ebb3aa02df42 Mon Sep 17 00:00:00 2001 From: James Aprosail Date: Sun, 30 Jun 2024 02:46:52 +0800 Subject: [PATCH 4/5] test rm unnecessary function encapsulation --- test/inherit_test.dart | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/test/inherit_test.dart b/test/inherit_test.dart index 1d0955a..5b61e1e 100644 --- a/test/inherit_test.dart +++ b/test/inherit_test.dart @@ -4,19 +4,6 @@ import 'package:modifier/modifier.dart'; import 'package:modifier_test/modifier_test.dart'; void main() { - testFindAndTrust(); - testInheritHandler(); -} - -/// Wrapping a single text message for demonstration. -/// See the code inside [testFindAndTrust]. -class MessageExample { - const MessageExample({required this.message}); - - final String message; -} - -void testFindAndTrust() { group('find and trust', () { // It's strongly not recommended to code like that, // because there might many inherited data with the String type @@ -44,9 +31,7 @@ void testFindAndTrust() { expect(find.text(message), findsOneWidget); }); }); -} -void testInheritHandler() { testWidgets('inherit handler', (t) async { await builder((context) { final message = context.findAndTrust(); @@ -109,3 +94,11 @@ void testInheritHandler() { expect(find.text('outer message: 123457'), findsOneWidget); }); } + +/// Wrapping a single text message for demonstration. +/// This is an encapsulation to avoid inherit the commonly used [String] type. +class MessageExample { + const MessageExample({required this.message}); + + final String message; +} From ced14246170945ac566adce0f8ce3445d76d00d4 Mon Sep 17 00:00:00 2001 From: James Aprosail Date: Sun, 30 Jun 2024 02:48:18 +0800 Subject: [PATCH 5/5] inherit on change trigger --- CHANGELOG.md | 2 ++ lib/src/inherit.dart | 26 ++++++++++++++++++++++---- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cab8406..928ebf7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ - Theme template and theme handler. - Support customized theme on mixin. - Theme handler adapt system brightness change. +- Remove unnecessary encapsulations and tidy comments. +- Inherit on change trigger (experimental). ## 0.3.0 diff --git a/lib/src/inherit.dart b/lib/src/inherit.dart index 19ee4a6..36964a7 100644 --- a/lib/src/inherit.dart +++ b/lib/src/inherit.dart @@ -75,8 +75,11 @@ extension FindInherit on BuildContext { } /// A stateful widget to handle [Inherit]ed data in widget tree. -/// You can change the handled data from the descendants in the widget tree -/// using the [BuildContext]'s [InheritHandlerAPI.update] extension method. +/// +/// 1. You can change the handled data from the descendants in the widget tree +/// using the [BuildContext]'s [InheritHandlerAPI.update] extension method. +/// 2. You can also customize [onUpdate] callback to resolve +/// actions when the value changed. /// /// It's strongly not recommended to use it directly, /// please consider using [WrapInheritHandler.handle] extended on [Widget] @@ -87,10 +90,12 @@ class InheritHandler extends StatefulWidget { /// before using such constructor directly. const InheritHandler({ super.key, + this.onUpdate, required this.data, required this.child, }); + final void Function(T value)? onUpdate; final T data; final Widget child; @@ -102,7 +107,9 @@ class _InheritHandlerState extends State> { late T _data = widget.data; void update(T value) { - if (_data != value) setState(() => _data = value); + if (_data == value) return; + setState(() => _data = value); + widget.onUpdate?.call(value); } @override @@ -127,7 +134,18 @@ class InheritHandlerAPI { } extension WrapInheritHandler on Widget { - Widget handle(T data) => InheritHandler(data: data, child: this); + /// Handle a data of type [T] into the widget tree. + /// + /// 1. This extension method is an encapsulation of [InheritHandler]. + /// 2. You can use [UpdateInheritHandler.update] extension method + /// to modify the handled value. + /// 3. You can also specify [onUpdate] to register trigger actions + /// when the value changed. + Widget handle(T data, {void Function(T)? onUpdate}) => InheritHandler( + onUpdate: onUpdate, + data: data, + child: this, + ); } extension UpdateInheritHandler on BuildContext {