diff --git a/README.md b/README.md index 984f33b..99aff23 100644 --- a/README.md +++ b/README.md @@ -346,6 +346,7 @@ A series of hooks with no particular theme. | [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`. | diff --git a/packages/flutter_hooks/CHANGELOG.md b/packages/flutter_hooks/CHANGELOG.md index 90c4b44..6835524 100644 --- a/packages/flutter_hooks/CHANGELOG.md +++ b/packages/flutter_hooks/CHANGELOG.md @@ -1,4 +1,9 @@ +## Unreleased build + +- Added `useFixedExtentScrollController` (thanks to @whynotmake-it) + ## 0.21.1-pre.3 - 2024-07-30 + - Added `useOnListenableChange` (thanks to @whynotmake-it) ## 0.21.1-pre.2 - 2024-07-22 diff --git a/packages/flutter_hooks/lib/src/fixed_extent_scroll_controller.dart b/packages/flutter_hooks/lib/src/fixed_extent_scroll_controller.dart new file mode 100644 index 0000000..a70a161 --- /dev/null +++ b/packages/flutter_hooks/lib/src/fixed_extent_scroll_controller.dart @@ -0,0 +1,57 @@ +part of 'hooks.dart'; + +/// Creates [FixedExtentScrollController] that will be disposed automatically. +/// +/// See also: +/// - [FixedExtentScrollController] +FixedExtentScrollController useFixedExtentScrollController({ + int initialItem = 0, + ScrollControllerCallback? onAttach, + ScrollControllerCallback? onDetach, + List? keys, +}) { + return use( + _FixedExtentScrollControllerHook( + initialItem: initialItem, + onAttach: onAttach, + onDetach: onDetach, + keys: keys, + ), + ); +} + +class _FixedExtentScrollControllerHook + extends Hook { + const _FixedExtentScrollControllerHook({ + required this.initialItem, + this.onAttach, + this.onDetach, + super.keys, + }); + + final int initialItem; + final ScrollControllerCallback? onAttach; + final ScrollControllerCallback? onDetach; + + @override + HookState> + createState() => _FixedExtentScrollControllerHookState(); +} + +class _FixedExtentScrollControllerHookState extends HookState< + FixedExtentScrollController, _FixedExtentScrollControllerHook> { + late final controller = FixedExtentScrollController( + initialItem: hook.initialItem, + onAttach: hook.onAttach, + onDetach: hook.onDetach, + ); + + @override + FixedExtentScrollController build(BuildContext context) => controller; + + @override + void dispose() => controller.dispose(); + + @override + String get debugLabel => 'useFixedExtentScrollController'; +} diff --git a/packages/flutter_hooks/lib/src/hooks.dart b/packages/flutter_hooks/lib/src/hooks.dart index 2eef50c..023868d 100644 --- a/packages/flutter_hooks/lib/src/hooks.dart +++ b/packages/flutter_hooks/lib/src/hooks.dart @@ -19,6 +19,7 @@ part 'animation.dart'; part 'async.dart'; part 'draggable_scrollable_controller.dart'; part 'expansion_tile_controller.dart'; +part 'fixed_extent_scroll_controller.dart'; part 'focus_node.dart'; part 'focus_scope_node.dart'; part 'keep_alive.dart'; diff --git a/packages/flutter_hooks/test/use_fixed_extent_scroll_controller_test.dart b/packages/flutter_hooks/test/use_fixed_extent_scroll_controller_test.dart new file mode 100644 index 0000000..00f9f6c --- /dev/null +++ b/packages/flutter_hooks/test/use_fixed_extent_scroll_controller_test.dart @@ -0,0 +1,102 @@ +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) { + useFixedExtentScrollController(); + return const SizedBox(); + }), + ); + + final element = tester.element(find.byType(HookBuilder)); + + expect( + element + .toDiagnosticsNode(style: DiagnosticsTreeStyle.offstage) + .toStringDeep(), + equalsIgnoringHashCodes( + 'HookBuilder\n' + ' │ useFixedExtentScrollController:\n' + ' │ FixedExtentScrollController#00000(no clients)\n' + ' └SizedBox(renderObject: RenderConstrainedBox#00000)\n', + ), + ); + }); + + group('useFixedExtentScrollController', () { + testWidgets('initial values matches with real constructor', (tester) async { + late FixedExtentScrollController controller; + late FixedExtentScrollController controller2; + + await tester.pumpWidget( + HookBuilder(builder: (context) { + controller2 = FixedExtentScrollController(); + controller = useFixedExtentScrollController(); + return Container(); + }), + ); + + expect(controller.debugLabel, controller2.debugLabel); + expect(controller.initialItem, controller2.initialItem); + expect(controller.onAttach, controller2.onAttach); + expect(controller.onDetach, controller2.onDetach); + }); + testWidgets("returns a FixedExtentScrollController that doesn't change", + (tester) async { + late FixedExtentScrollController controller; + late FixedExtentScrollController controller2; + + await tester.pumpWidget( + HookBuilder(builder: (context) { + controller2 = FixedExtentScrollController(); + controller = useFixedExtentScrollController(); + return Container(); + }), + ); + expect(controller, isA()); + + await tester.pumpWidget( + HookBuilder(builder: (context) { + controller2 = useFixedExtentScrollController(); + return Container(); + }), + ); + + expect(identical(controller, controller2), isTrue); + }); + + testWidgets('passes hook parameters to the FixedExtentScrollController', + (tester) async { + late FixedExtentScrollController controller; + + void onAttach(ScrollPosition position) {} + void onDetach(ScrollPosition position) {} + + await tester.pumpWidget( + HookBuilder( + builder: (context) { + controller = useFixedExtentScrollController( + initialItem: 42, + onAttach: onAttach, + onDetach: onDetach, + ); + + return Container(); + }, + ), + ); + + expect(controller.initialItem, 42); + expect(controller.onAttach, onAttach); + expect(controller.onDetach, onDetach); + }); + }); +} + +class TickerProviderMock extends Mock implements TickerProvider {} diff --git a/packages/flutter_hooks/test/use_page_controller_test.dart b/packages/flutter_hooks/test/use_page_controller_test.dart index 31f0cf9..7015090 100644 --- a/packages/flutter_hooks/test/use_page_controller_test.dart +++ b/packages/flutter_hooks/test/use_page_controller_test.dart @@ -45,6 +45,8 @@ void main() { expect(controller.initialPage, controller2.initialPage); expect(controller.keepPage, controller2.keepPage); expect(controller.viewportFraction, controller2.viewportFraction); + expect(controller.onAttach, controller2.onAttach); + expect(controller.onDetach, controller2.onDetach); }); testWidgets("returns a PageController that doesn't change", (tester) async { late PageController controller; @@ -98,23 +100,6 @@ void main() { expect(controller.onDetach, onDetach); }); - testWidgets('onAttach and onDetach are null by default', (tester) async { - late PageController controller; - - await tester.pumpWidget( - HookBuilder( - builder: (context) { - controller = usePageController(); - - return Container(); - }, - ), - ); - - expect(controller.onAttach, isNull); - expect(controller.onDetach, isNull); - }); - testWidgets('disposes the PageController on unmount', (tester) async { late PageController controller; diff --git a/packages/flutter_hooks/test/use_scroll_controller_test.dart b/packages/flutter_hooks/test/use_scroll_controller_test.dart index 3ab90b2..fee5fd5 100644 --- a/packages/flutter_hooks/test/use_scroll_controller_test.dart +++ b/packages/flutter_hooks/test/use_scroll_controller_test.dart @@ -44,6 +44,8 @@ void main() { expect(controller.debugLabel, controller2.debugLabel); expect(controller.initialScrollOffset, controller2.initialScrollOffset); expect(controller.keepScrollOffset, controller2.keepScrollOffset); + expect(controller.onAttach, controller2.onAttach); + expect(controller.onDetach, controller2.onDetach); }); testWidgets("returns a ScrollController that doesn't change", (tester) async { @@ -98,22 +100,6 @@ void main() { expect(controller.onAttach, onAttach); expect(controller.onDetach, onDetach); }); - - testWidgets('onAttach and onDetach are null by default', (tester) async { - late ScrollController controller; - - await tester.pumpWidget( - HookBuilder( - builder: (context) { - controller = useScrollController(); - - return Container(); - }, - ), - ); - expect(controller.onAttach, isNull); - expect(controller.onDetach, isNull); - }); }); }