diff --git a/README.md b/README.md index b2c276b..bf62334 100644 --- a/README.md +++ b/README.md @@ -337,29 +337,30 @@ They will take care of creating/updating/disposing an object. A series of hooks with no particular theme. -| Name | Description | -| -------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | -| [useReducer](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useReducer.html) | An alternative to `useState` for more complex states. | -| [usePrevious](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/usePrevious.html) | Returns the previous argument called to [usePrevious]. | -| [useTextEditingController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useTextEditingController-constant.html) | Creates a `TextEditingController`. | -| [useFocusNode](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useFocusNode.html) | Creates a `FocusNode`. | -| [useTabController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useTabController.html) | Creates and disposes a `TabController`. | -| [useScrollController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useScrollController.html) | Creates and disposes a `ScrollController`. | -| [usePageController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/usePageController.html) | Creates and disposes a `PageController`. | -| [useFixedExtentScrollController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useFixedExtentScrollController.html) | Creates and disposes a `FixedExtentScrollController`. | -| [useAppLifecycleState](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useAppLifecycleState.html) | Returns the current `AppLifecycleState` and rebuilds the widget on change. | -| [useOnAppLifecycleStateChange](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useOnAppLifecycleStateChange.html) | Listens to `AppLifecycleState` changes and triggers a callback on change. | -| [useTransformationController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useTransformationController.html) | Creates and disposes a `TransformationController`. | -| [useIsMounted](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useIsMounted.html) | An equivalent to `State.mounted` for hooks. | -| [useAutomaticKeepAlive](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useAutomaticKeepAlive.html) | An equivalent to the `AutomaticKeepAlive` widget for hooks. | -| [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`. | -| [useExpansionTileController](https://api.flutter.dev/flutter/material/ExpansionTileController-class.html) | Creates a `ExpansionTileController`. | -| [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`. | +| Name | Description | +| -------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| [useReducer](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useReducer.html) | An alternative to `useState` for more complex states. | +| [usePrevious](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/usePrevious.html) | Returns the previous argument called to [usePrevious]. | +| [useTextEditingController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useTextEditingController-constant.html) | Creates a `TextEditingController`. | +| [useFocusNode](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useFocusNode.html) | Creates a `FocusNode`. | +| [useTabController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useTabController.html) | Creates and disposes a `TabController`. | +| [useScrollController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useScrollController.html) | Creates and disposes a `ScrollController`. | +| [usePageController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/usePageController.html) | Creates and disposes a `PageController`. | +| [useFixedExtentScrollController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useFixedExtentScrollController.html) | Creates and disposes a `FixedExtentScrollController`. | +| [useAppLifecycleState](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useAppLifecycleState.html) | Returns the current `AppLifecycleState` and rebuilds the widget on change. | +| [useOnAppLifecycleStateChange](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useOnAppLifecycleStateChange.html) | Listens to `AppLifecycleState` changes and triggers a callback on change. | +| [useTransformationController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useTransformationController.html) | Creates and disposes a `TransformationController`. | +| [useIsMounted](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useIsMounted.html) | An equivalent to `State.mounted` for hooks. | +| [useAutomaticKeepAlive](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useAutomaticKeepAlive.html) | An equivalent to the `AutomaticKeepAlive` widget for hooks. | +| [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`. | +| [useExpansionTileController](https://api.flutter.dev/flutter/material/ExpansionTileController-class.html) | Creates a `ExpansionTileController`. | +| [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. | ## Contributions diff --git a/packages/flutter_hooks/CHANGELOG.md b/packages/flutter_hooks/CHANGELOG.md index f3fe9e1..84ef28f 100644 --- a/packages/flutter_hooks/CHANGELOG.md +++ b/packages/flutter_hooks/CHANGELOG.md @@ -2,6 +2,7 @@ - Add `useCarouselController` (thanks to @riscait) - Add `useTreeSliverController` (thanks to @benthillerkus) +- Added `useOverlayPortalController` (thanks to @offich) ## 0.21.1-pre.4 - 2024-08-01 diff --git a/packages/flutter_hooks/lib/src/misc.dart b/packages/flutter_hooks/lib/src/misc.dart index 147b765..57510ad 100644 --- a/packages/flutter_hooks/lib/src/misc.dart +++ b/packages/flutter_hooks/lib/src/misc.dart @@ -229,3 +229,32 @@ class _IsMountedHookState extends HookState { "Use BuildContext.mounted instead if you're on Flutter 3.7.0 or greater", ) typedef IsMounted = bool Function(); + +/// Creates a [OverlayPortalController] that will be disposed automatically. +/// +/// See also: +/// - [OverlayPortalController] +OverlayPortalController useOverlayPortalController({ + List? keys, +}) { + return use(_OverlayPortalControllerHook(keys: keys)); +} + +class _OverlayPortalControllerHook extends Hook { + const _OverlayPortalControllerHook({List? keys}) : super(keys: keys); + + @override + HookState> + createState() => _OverlayPortalControllerHookState(); +} + +class _OverlayPortalControllerHookState + extends HookState { + final controller = OverlayPortalController(); + + @override + OverlayPortalController build(BuildContext context) => controller; + + @override + String get debugLabel => 'useOverlayPortalController'; +} diff --git a/packages/flutter_hooks/test/use_overlay_portal_controller_test.dart b/packages/flutter_hooks/test/use_overlay_portal_controller_test.dart new file mode 100644 index 0000000..c922645 --- /dev/null +++ b/packages/flutter_hooks/test/use_overlay_portal_controller_test.dart @@ -0,0 +1,91 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.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) { + useOverlayPortalController(); + return const SizedBox(); + }), + ); + + await tester.pump(); + + final element = tester.element(find.byType(HookBuilder)); + + expect( + element + .toDiagnosticsNode(style: DiagnosticsTreeStyle.offstage) + .toStringDeep(), + equalsIgnoringHashCodes( + 'HookBuilder\n' + ' │ useOverlayPortalController: OverlayPortalController DETACHED\n' + ' └SizedBox(renderObject: RenderConstrainedBox#00000)\n', + ), + ); + }); + + group('useOverlayPortalController', () { + testWidgets('initial values matches with real constructor', (tester) async { + late OverlayPortalController controller; + final controller2 = OverlayPortalController(); + + await tester.pumpWidget(MaterialApp( + home: Scaffold( + body: HookBuilder(builder: (context) { + controller = useOverlayPortalController(); + return Column( + children: [ + OverlayPortal( + controller: controller, + overlayChildBuilder: (context) => + const Text('Overlay Portal'), + ), + OverlayPortal( + controller: controller2, + overlayChildBuilder: (context) => + const Text('Overlay Portal 2'), + ), + ], + ); + }), + ), + )); + expect(controller, isA()); + expect(controller.isShowing, controller2.isShowing); + }); + + testWidgets('check show/hide of overlay portal', (tester) async { + late OverlayPortalController controller; + await tester.pumpWidget(MaterialApp( + home: Scaffold( + body: HookBuilder(builder: (context) { + controller = useOverlayPortalController(); + return OverlayPortal( + controller: controller, + overlayChildBuilder: (context) => const Text('Overlay Content'), + ); + }), + ), + )); + + expect(controller.isShowing, false); + expect(find.text('Overlay Content'), findsNothing); + + controller.show(); + await tester.pump(); + expect(controller.isShowing, true); + expect(find.text('Overlay Content'), findsOneWidget); + + controller.hide(); + await tester.pump(); + expect(controller.isShowing, false); + expect(find.text('Overlay Content'), findsNothing); + }); + }); +}