diff --git a/README.md b/README.md index 4f014f8..a50587e 100644 --- a/README.md +++ b/README.md @@ -360,8 +360,9 @@ A series of hooks with no particular theme. | [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` | +| [useOverlayPortalController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useOverlayPortalController.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://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useSnapshotController.html) | Creates and manages a `SnapshotController` | +| [useCupertinoController](https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useCupertinoController.html) | Creates and manages a `CupertinoController` | ## Contributions diff --git a/packages/flutter_hooks/lib/src/cupertino_tab_controller.dart b/packages/flutter_hooks/lib/src/cupertino_tab_controller.dart new file mode 100644 index 0000000..53e7114 --- /dev/null +++ b/packages/flutter_hooks/lib/src/cupertino_tab_controller.dart @@ -0,0 +1,46 @@ +part of 'hooks.dart'; + +/// Creates a [CupertinoTabController] that will be disposed automatically. +/// +/// See also: +/// - [CupertinoTabController] +CupertinoTabController useCupertinoTabController({ + int initialIndex = 0, + List? keys, +}) { + return use( + _CupertinoTabControllerHook( + initialIndex: initialIndex, + keys: keys, + ), + ); +} + +class _CupertinoTabControllerHook extends Hook { + const _CupertinoTabControllerHook({ + required this.initialIndex, + super.keys, + }); + + final int initialIndex; + + @override + HookState> + createState() => _CupertinoTabControllerHookState(); +} + +class _CupertinoTabControllerHookState + extends HookState { + late final controller = CupertinoTabController( + initialIndex: hook.initialIndex, + ); + + @override + CupertinoTabController build(BuildContext context) => controller; + + @override + void dispose() => controller.dispose(); + + @override + String get debugLabel => 'useCupertinoTabController'; +} diff --git a/packages/flutter_hooks/lib/src/hooks.dart b/packages/flutter_hooks/lib/src/hooks.dart index 1816175..f9b04a9 100644 --- a/packages/flutter_hooks/lib/src/hooks.dart +++ b/packages/flutter_hooks/lib/src/hooks.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:flutter/cupertino.dart' show CupertinoTabController; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart' show @@ -21,6 +22,7 @@ import 'framework.dart'; part 'animation.dart'; part 'async.dart'; part 'carousel_controller.dart'; +part 'cupertino_tab_controller.dart'; part 'debounced.dart'; part 'draggable_scrollable_controller.dart'; part 'expansion_tile_controller.dart'; diff --git a/packages/flutter_hooks/test/use_cupertino_tab_controller_test.dart b/packages/flutter_hooks/test/use_cupertino_tab_controller_test.dart new file mode 100644 index 0000000..a57748a --- /dev/null +++ b/packages/flutter_hooks/test/use_cupertino_tab_controller_test.dart @@ -0,0 +1,89 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.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) { + useCupertinoTabController(); + return const SizedBox(); + }), + ); + + await tester.pump(); + + final element = tester.element(find.byType(HookBuilder)); + + expect( + element + .toDiagnosticsNode(style: DiagnosticsTreeStyle.offstage) + .toStringDeep(), + equalsIgnoringHashCodes( + 'HookBuilder\n' + " │ useCupertinoTabController: Instance of 'CupertinoTabController'\n" + ' └SizedBox(renderObject: RenderConstrainedBox#00000)\n', + ), + ); + }); + + group('useCupertinoTabController', () { + testWidgets('initial values matches with real constructor', (tester) async { + late CupertinoTabController controller; + late CupertinoTabController controller2; + + await tester.pumpWidget( + HookBuilder(builder: (context) { + controller2 = CupertinoTabController(); + controller = useCupertinoTabController(); + return Container(); + }), + ); + + expect(controller.index, controller2.index); + }); + testWidgets("returns a CupertinoTabController that doesn't change", + (tester) async { + late CupertinoTabController controller; + late CupertinoTabController controller2; + + await tester.pumpWidget( + HookBuilder(builder: (context) { + controller = useCupertinoTabController(initialIndex: 1); + return Container(); + }), + ); + + expect(controller, isA()); + + await tester.pumpWidget( + HookBuilder(builder: (context) { + controller2 = useCupertinoTabController(initialIndex: 1); + return Container(); + }), + ); + + expect(identical(controller, controller2), isTrue); + }); + + testWidgets('passes hook parameters to the CupertinoTabController', + (tester) async { + late CupertinoTabController controller; + + await tester.pumpWidget( + HookBuilder( + builder: (context) { + controller = useCupertinoTabController(initialIndex: 2); + + return Container(); + }, + ), + ); + + expect(controller.index, 2); + }); + }); +}