diff --git a/CHANGELOG.md b/CHANGELOG.md index ab89083..8ef290f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.7.0 + +- **Incompatible**: API about `ReContext` changed. +- Use `builder` instead of `ReContext`, which is conciser. +- Optimize tests and tidy code structure. + ## 0.6.0 - Inherit data from widget tree ancestors. diff --git a/lib/src/context.dart b/lib/src/context.dart index 5361e33..53ae00b 100644 --- a/lib/src/context.dart +++ b/lib/src/context.dart @@ -1,16 +1,28 @@ import 'package:flutter/widgets.dart'; extension WrapContext on Widget { + /// Same as wrapping current widget with a [Builder], which keeps chain style. Widget wrap(Widget Function(BuildContext context, Widget child) builder) => - ReContext((context) => builder(context, this)); + Builder(builder: (context) => builder(context, this)); } -/// Refresh context, same as [Builder] but with conciser API. -class ReContext extends StatelessWidget { - const ReContext(this.builder, {super.key}); +/// An encapsulation of [Builder], which makes it conciser. +Widget builder(Widget Function(BuildContext context) builder) => + Builder(builder: builder); - final Widget Function(BuildContext context) builder; +extension WrapMedia on Widget { + Widget wrapMedia(MediaQueryData data) => MediaQuery(data: data, child: this); - @override - Widget build(BuildContext context) => builder(context); + /// Ensure the widget is wrapped by a [MediaQuery] ancestor. + /// + /// Provide environment for [MediaQuery.of] that many widgets need, + /// including the [Text] widget. Without such ancestor, + /// once displaying a [Text], + /// it will throw exceptions. + Widget ensureMedia(BuildContext context) { + final media = MediaQuery.maybeOf(context); + return media == null + ? wrapMedia(MediaQueryData.fromView(View.of(context))) + : this; + } } diff --git a/lib/src/handler.dart b/lib/src/handler.dart new file mode 100644 index 0000000..4cda8c3 --- /dev/null +++ b/lib/src/handler.dart @@ -0,0 +1,58 @@ +import 'package:flutter/widgets.dart'; + +import 'inherit.dart'; + +class InheritHandler extends StatefulWidget { + const InheritHandler({ + super.key, + required this.data, + required this.child, + }); + + final T data; + final Widget child; + + @override + State> createState() => _InheritHandlerState(); +} + +class _InheritHandlerState extends State> { + late var _data = widget.data; + T get data => _data; + set data(T value) { + if (_data != value) setState(() => _data = value); + } + + @override + void didUpdateWidget(covariant InheritHandler oldWidget) { + super.didUpdateWidget(oldWidget); + data = widget.data; + } + + @override + Widget build(BuildContext context) => widget.child + .wrapInherit(_data) + .wrapInheritUnchanged(InheritHandlerCaller( + updater: (data) => this.data = data, + )); +} + +class InheritHandlerCaller { + const InheritHandlerCaller({required this.updater}); + + final void Function(T data) updater; +} + +extension WrapHandler on Widget { + Widget wrapHandler(T data) => InheritHandler(data: data, child: this); +} + +extension UpdateHandlerData on BuildContext { + void maybeUpdate(T data) => find>()?.updater(data); + + void updateAndTrust(T data) => + findAndTrust>().updater(data); + + void updateAndCheck(T data) => + findAndCheck>().updater(data); +} diff --git a/lib/src/inherit.dart b/lib/src/inherit.dart index f79c05f..ecb6f22 100644 --- a/lib/src/inherit.dart +++ b/lib/src/inherit.dart @@ -58,4 +58,10 @@ extension FindInherit on BuildContext { assert(data != null, 'cannot find $T in context'); return data!; } + + T findAndCheck() { + final data = find(); + if (data == null) throw Exception('cannot find $T in context'); + return data; + } } diff --git a/lib/src/media.dart b/lib/src/media.dart deleted file mode 100644 index 479a33b..0000000 --- a/lib/src/media.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:flutter/widgets.dart'; - -extension WrapMedia on Widget { - Widget wrapMedia(MediaQueryData data) => MediaQuery(data: data, child: this); - - /// Ensure the widget is wrapped by a [MediaQuery] ancestor. - /// - /// Provide environment for [MediaQuery.of] that many widgets need, - /// including the [Text] widget. Without such ancestor, - /// once displaying a [Text], - /// it will throw exceptions. - Widget ensureMedia(BuildContext context) { - final media = MediaQuery.maybeOf(context); - return media == null - ? wrapMedia(MediaQueryData.fromView(View.of(context))) - : this; - } -} diff --git a/lib/src/text.dart b/lib/src/text.dart index faa679e..03116c1 100644 --- a/lib/src/text.dart +++ b/lib/src/text.dart @@ -1,6 +1,6 @@ import 'package:flutter/widgets.dart'; -import 'media.dart'; +import 'context.dart'; extension WrapDirectionality on Widget { Widget wrapDirectionality(TextDirection direction) => Directionality( diff --git a/lib/wrap.dart b/lib/wrap.dart index e1b9085..9128b19 100644 --- a/lib/wrap.dart +++ b/lib/wrap.dart @@ -2,8 +2,8 @@ export 'src/align.dart'; export 'src/color.dart'; export 'src/context.dart'; export 'src/decorate.dart'; +export 'src/handler.dart'; export 'src/inherit.dart'; export 'src/list.dart'; -export 'src/media.dart'; export 'src/size.dart'; export 'src/text.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index 2b2427f..0a47a60 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: wrap description: Chain style programming syntax sugar utilities for flutter widgets. -version: 0.6.0 +version: 0.7.0 repository: https://github.com/aprosail/wrap environment: {sdk: ">=3.3.4 <4.0.0", flutter: ">=3.19.6"} topics: diff --git a/test/context_test.dart b/test/context_test.dart index 460c61a..5a0b593 100644 --- a/test/context_test.dart +++ b/test/context_test.dart @@ -5,7 +5,7 @@ import 'package:wrap/wrap.dart'; void main() { testWidgets('re context root', (t) async { await t.pumpWidget( - ReContext((context) => const ColorProbe() + builder((context) => const ColorProbe() .wrapForeground(context, foreground) .ensureTextEnvironment(context)), ); diff --git a/test/inherit_test.dart b/test/inherit_test.dart index 5083b4e..d92c40c 100644 --- a/test/inherit_test.dart +++ b/test/inherit_test.dart @@ -6,7 +6,7 @@ void main() { testWidgets('inherit and find', (t) async { const message = 'it works'; await t.pumpWidget( - ReContext( + builder( (context) => Text(context.findAndTrust()) .wrapCenter .ensureTextEnvironment(context), diff --git a/test/text_test.dart b/test/text_test.dart index 948dae1..1c23122 100644 --- a/test/text_test.dart +++ b/test/text_test.dart @@ -5,7 +5,7 @@ import 'package:wrap/wrap.dart'; void main() { testWidgets('ensure text environment', (t) async { const message = 'it works'; - await t.pumpWidget(ReContext( + await t.pumpWidget(builder( (context) => const Text(message).ensureTextEnvironment(context), )); expect(find.text(message), findsOneWidget); @@ -16,7 +16,7 @@ void main() { const fontSize = 23.4; const fontFamily = 'iosevka'; await t.pumpWidget( - ReContext((context) { + builder((context) { final font = DefaultTextStyle.of(context).style; return [ 'font color: ${font.color}'.textWidget,