diff --git a/README.md b/README.md index fcb293c..4f014f8 100644 --- a/README.md +++ b/README.md @@ -355,12 +355,13 @@ A series of hooks with no particular theme. | [useOnPlatformBrightnessChange](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useOnPlatformBrightnessChange.html) | Listens to platform `Brightness` changes and triggers a callback on change. | | [useSearchController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useSearchController.html) | Creates and disposes a `SearchController`. | | [useWidgetStatesController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useWidgetStatesController.html) | Creates and disposes a `WidgetStatesController`. | -| [useExpansibleController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useExpansibleController.html) | Creates a `ExpansibleController`. | +| [useExpansibleController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useExpansibleController.html) | Creates a `ExpansibleController`. | | [useDebounced](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useDebounced.html) | Returns a debounced version of the provided value, triggering widget updates accordingly after a specified timeout duration | | [useDraggableScrollableController](https://api.flutter.dev/flutter/widgets/DraggableScrollableController-class.html) | Creates a `DraggableScrollableController`. | | [useCarouselController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useCarouselController.html) | Creates and disposes a **`CarouselController`**. | | [useTreeSliverController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useTreeSliverController.html) | Creates a `TreeSliverController`. | | [useOverlayPortalController](https://api.flutter.dev/flutter/widgets/OverlayPortalController-class.html) | Creates and manages an `OverlayPortalController` for controlling the visibility of overlay content. The controller will be automatically disposed when no longer needed. | +| [useSnapshotController](https://api.flutter.dev/flutter/widgets/SnapshotController-class.html) | Creates and manages a `SnapshotController` | ## Contributions diff --git a/packages/flutter_hooks/lib/src/hooks.dart b/packages/flutter_hooks/lib/src/hooks.dart index 7fb94cd..1816175 100644 --- a/packages/flutter_hooks/lib/src/hooks.dart +++ b/packages/flutter_hooks/lib/src/hooks.dart @@ -42,3 +42,4 @@ part 'transformation_controller.dart'; part 'tree_sliver_controller.dart'; part 'widget_states_controller.dart'; part 'widgets_binding_observer.dart'; +part 'snapshot_controller.dart'; diff --git a/packages/flutter_hooks/lib/src/snapshot_controller.dart b/packages/flutter_hooks/lib/src/snapshot_controller.dart new file mode 100644 index 0000000..d5b69ec --- /dev/null +++ b/packages/flutter_hooks/lib/src/snapshot_controller.dart @@ -0,0 +1,62 @@ +part of 'hooks.dart'; + +/// Creates and disposes a [SnapshotController]. +/// +/// Note that [allowSnapshotting] must be set to `true` +/// in order for this controller to actually do anything. +/// This is consistent with [SnapshotController.new]. +/// +/// If [allowSnapshotting] changes on subsequent calls to [useSnapshotController], +/// [SnapshotController.allowSnapshotting] will be called to update accordingly. +/// +/// ```dart +/// final controller = useSnapshotController(allowSnapshotting: true); +/// // is equivalent to +/// final controller = useSnapshotController(); +/// controller.allowSnapshotting = true; +/// ``` +/// +/// See also: +/// - [SnapshotController] +SnapshotController useSnapshotController({ + bool allowSnapshotting = false, +}) { + return use( + _SnapshotControllerHook( + allowSnapshotting: allowSnapshotting, + ), + ); +} + +class _SnapshotControllerHook extends Hook { + const _SnapshotControllerHook({ + required this.allowSnapshotting, + }); + + final bool allowSnapshotting; + + @override + HookState> createState() => + _SnapshotControllerHookState(); +} + +class _SnapshotControllerHookState + extends HookState { + late final controller = + SnapshotController(allowSnapshotting: hook.allowSnapshotting); + + @override + void didUpdateHook(_SnapshotControllerHook oldHook) { + super.didUpdateHook(oldHook); + controller.allowSnapshotting = hook.allowSnapshotting; + } + + @override + SnapshotController build(BuildContext context) => controller; + + @override + void dispose() => controller.dispose(); + + @override + String get debugLabel => 'useSnapshotController'; +} diff --git a/packages/flutter_hooks/test/use_snapshot_controller_test.dart b/packages/flutter_hooks/test/use_snapshot_controller_test.dart new file mode 100644 index 0000000..842691d --- /dev/null +++ b/packages/flutter_hooks/test/use_snapshot_controller_test.dart @@ -0,0 +1,75 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_hooks/src/framework.dart'; +import 'package:flutter_hooks/src/hooks.dart'; + +import 'mock.dart'; + +void main() { + testWidgets('debugFillProperties', (tester) async { + await tester.pumpWidget( + HookBuilder(builder: (context) { + useSnapshotController(); + return const SizedBox(); + }), + ); + + await tester.pump(); + + final element = tester.element(find.byType(HookBuilder)); + + expect( + element + .toDiagnosticsNode(style: DiagnosticsTreeStyle.offstage) + .toStringDeep(), + equalsIgnoringHashCodes( + 'HookBuilder\n' + " │ useSnapshotController: Instance of 'SnapshotController'\n" + ' └SizedBox(renderObject: RenderConstrainedBox#00000)\n', + ), + ); + }); + + group('useSnapshotController', () { + testWidgets('initial values matches with real constructor', (tester) async { + late SnapshotController controller; + late SnapshotController controller2; + + await tester.pumpWidget( + HookBuilder(builder: (context) { + controller2 = SnapshotController(); + controller = useSnapshotController(); + return Container(); + }), + ); + + expect(controller.allowSnapshotting, controller2.allowSnapshotting); + }); + + testWidgets('passes hook parameters to the SnapshotController', + (tester) async { + late SnapshotController controller; + + await tester.pumpWidget( + HookBuilder(builder: (context) { + controller = useSnapshotController(allowSnapshotting: true); + return const SizedBox(); + }), + ); + + expect(controller.allowSnapshotting, true); + + late SnapshotController retrievedController; + await tester.pumpWidget( + HookBuilder(builder: (context) { + // ignore: avoid_redundant_argument_values + retrievedController = useSnapshotController(allowSnapshotting: false); + return const SizedBox(); + }), + ); + + expect(retrievedController, same(controller)); + expect(retrievedController.allowSnapshotting, false); + }); + }); +}