diff --git a/app/lib/features/geckoview/features/browser/presentation/screens/browser.dart b/app/lib/features/geckoview/features/browser/presentation/screens/browser.dart index 12c6140a..a7c77ec9 100644 --- a/app/lib/features/geckoview/features/browser/presentation/screens/browser.dart +++ b/app/lib/features/geckoview/features/browser/presentation/screens/browser.dart @@ -683,6 +683,9 @@ class BrowserScreen extends HookConsumerWidget { // Layer 5: Page load progress indicator (animates with toolbar visibility) Consumer( builder: (context, ref, child) { + final disableAnimations = MediaQuery.disableAnimationsOf( + context, + ); final toolbarState = ref.watch( toolbarVisibilityControllerProvider(selectedTabId), ); @@ -691,7 +694,9 @@ class BrowserScreen extends HookConsumerWidget { (!tabInFullScreen && toolbarState == ToolbarVisibility.visible); return AnimatedPositioned( - duration: _AnimatedToolbar._kAnimationDuration, + duration: disableAnimations + ? Duration.zero + : _AnimatedToolbar._kAnimationDuration, curve: Curves.easeInOutQuart, left: 0, right: 0, @@ -730,6 +735,9 @@ class BrowserScreen extends HookConsumerWidget { // Layer 6: Find in Page widget (above toolbar or keyboard, whichever is higher) Consumer( builder: (context, ref, child) { + final disableAnimations = MediaQuery.disableAnimationsOf( + context, + ); final toolbarState = ref.watch( toolbarVisibilityControllerProvider(selectedTabId), ); @@ -738,7 +746,9 @@ class BrowserScreen extends HookConsumerWidget { (!tabInFullScreen && toolbarState == ToolbarVisibility.visible); return AnimatedPositioned( - duration: _AnimatedToolbar._kAnimationDuration, + duration: disableAnimations + ? Duration.zero + : _AnimatedToolbar._kAnimationDuration, curve: Curves.easeInOutQuart, left: 0, right: 0, @@ -1111,17 +1121,22 @@ class _SiteSettingsSheet extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final disableAnimations = MediaQuery.disableAnimationsOf(context); final draggableScrollableController = useDraggableScrollableController(); void handleClearSiteDataExpansion(bool isExpanded) { if (isExpanded) { - unawaited( - draggableScrollableController.animateTo( - 1.0, - duration: const Duration(milliseconds: 300), - curve: Curves.decelerate, - ), - ); + if (disableAnimations) { + draggableScrollableController.jumpTo(1.0); + } else { + unawaited( + draggableScrollableController.animateTo( + 1.0, + duration: const Duration(milliseconds: 300), + curve: Curves.decelerate, + ), + ); + } } } diff --git a/app/lib/features/geckoview/features/browser/presentation/screens/tab_view.dart b/app/lib/features/geckoview/features/browser/presentation/screens/tab_view.dart index 6eb10a26..8e916f18 100644 --- a/app/lib/features/geckoview/features/browser/presentation/screens/tab_view.dart +++ b/app/lib/features/geckoview/features/browser/presentation/screens/tab_view.dart @@ -35,6 +35,7 @@ class TabViewScreen extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final disableAnimations = MediaQuery.disableAnimationsOf(context); final tabsViewMode = ref.watch(tabsViewModeControllerProvider); final tabsReorderable = ref.watch(tabsReorderableControllerProvider); @@ -51,7 +52,10 @@ class TabViewScreen extends HookConsumerWidget { final scrollController = useScrollController(keys: [tabsReorderable]); // Track FAB visibility based on scroll direction - final isFabVisible = useScrollVisibility(scrollController); + final isFabVisible = useScrollVisibility( + scrollController, + disableAnimations: disableAnimations, + ); return Dialog.fullscreen( child: Scaffold( @@ -87,11 +91,15 @@ class TabViewScreen extends HookConsumerWidget { floatingActionButton: isSyncedScope ? null : AnimatedSlide( - duration: const Duration(milliseconds: 200), + duration: disableAnimations + ? Duration.zero + : const Duration(milliseconds: 200), offset: isFabVisible.value ? Offset.zero : const Offset(0, 2), curve: Curves.easeInOut, child: AnimatedOpacity( - duration: const Duration(milliseconds: 200), + duration: disableAnimations + ? Duration.zero + : const Duration(milliseconds: 200), opacity: isFabVisible.value ? 1.0 : 0.0, child: FloatingActionButton( onPressed: () async { diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart index d54c9851..459044a1 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/bottom_app_bar.dart @@ -462,6 +462,7 @@ class QuickTabSwitcher extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final disableAnimations = MediaQuery.disableAnimationsOf(context); final showIsolatedTabUi = ref.watch( generalSettingsWithDefaultsProvider.select((s) => s.showIsolatedTabUi), ); @@ -523,11 +524,16 @@ class QuickTabSwitcher extends HookConsumerWidget { showTitles: showTitles, showIsolatedTabUi: showIsolatedTabUi, onSelected: (item) async { - final animation = chipScrollController.animateTo( - 0, - duration: const Duration(milliseconds: 200), - curve: Curves.easeOutBack, - ); + Future? animation; + if (disableAnimations) { + chipScrollController.jumpTo(0); + } else { + animation = chipScrollController.animateTo( + 0, + duration: const Duration(milliseconds: 200), + curve: Curves.easeOutBack, + ); + } if (item.isHistory) { await ref .read(tabRepositoryProvider.notifier) diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/draggable_fab.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/draggable_fab.dart index af579c5a..cff3021e 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/draggable_fab.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_modules/draggable_fab.dart @@ -47,6 +47,7 @@ class DraggableFab extends HookConsumerWidget { final mediaQuery = MediaQuery.of(context); final screenSize = mediaQuery.size; final padding = mediaQuery.padding; + final disableAnimations = mediaQuery.disableAnimations; final isDragging = useState(false); final customOffset = useState(null); @@ -98,7 +99,9 @@ class DraggableFab extends HookConsumerWidget { dragStartPosition.value = null; }, child: AnimatedScale( - duration: const Duration(milliseconds: 150), + duration: disableAnimations + ? Duration.zero + : const Duration(milliseconds: 150), scale: isDragging.value ? 1.1 : 1.0, child: child, ), diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_grid_view.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_grid_view.dart index 92d5cd3f..e482509e 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_grid_view.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_grid_view.dart @@ -142,6 +142,7 @@ class _TabGridView extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final screenWidth = MediaQuery.of(context).size.width; + final disableAnimations = MediaQuery.disableAnimationsOf(context); final canManualReorder = ref.watch(canManualTabReorderProvider); final reorderEnabled = tabsReorderable && canManualReorder; @@ -220,13 +221,17 @@ class _TabGridView extends HookConsumerWidget { tabStart >= viewportStart && tabEnd <= viewportEnd; if (!isVisible) { - unawaited( - scrollController.animateTo( - tabStart, - duration: const Duration(milliseconds: 200), - curve: Curves.easeInOut, - ), - ); + if (disableAnimations) { + scrollController.jumpTo(tabStart); + } else { + unawaited( + scrollController.animateTo( + tabStart, + duration: const Duration(milliseconds: 200), + curve: Curves.easeInOut, + ), + ); + } didInitialScroll.value = true; } else { @@ -470,21 +475,25 @@ class ViewTabGridWidget extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final disableAnimations = MediaQuery.disableAnimationsOf(context); final isFabVisible = useState(true); final lastSheetSize = useRef(0.0); final isInitialized = useRef(false); // Delay initialization to ignore initial animations useEffect(() { - final timer = Timer(const Duration(milliseconds: 500), () { - isInitialized.value = true; - // Initialize lastSheetSize with current size - if (draggableScrollableController?.isAttached == true) { - lastSheetSize.value = draggableScrollableController!.size; - } - }); + final timer = Timer( + disableAnimations ? Duration.zero : const Duration(milliseconds: 500), + () { + isInitialized.value = true; + // Initialize lastSheetSize with current size + if (draggableScrollableController?.isAttached == true) { + lastSheetSize.value = draggableScrollableController!.size; + } + }, + ); return timer.cancel; - }, []); + }, [disableAnimations]); // Listen to DraggableScrollableController for sheet size changes useEffect(() { @@ -571,11 +580,15 @@ class ViewTabGridWidget extends HookConsumerWidget { ), if (showNewTabFab) AnimatedSlide( - duration: const Duration(milliseconds: 200), + duration: disableAnimations + ? Duration.zero + : const Duration(milliseconds: 200), offset: isFabVisible.value ? Offset.zero : const Offset(0, 2), curve: Curves.easeInOut, child: AnimatedOpacity( - duration: const Duration(milliseconds: 200), + duration: disableAnimations + ? Duration.zero + : const Duration(milliseconds: 200), opacity: isFabVisible.value ? 1.0 : 0.0, child: Padding( padding: const EdgeInsets.only( diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_list_view.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_list_view.dart index 1de17c56..debc9abd 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_list_view.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_list_view.dart @@ -204,6 +204,7 @@ class _TabListView extends HookConsumerWidget { } Widget _buildLocalTabsView(BuildContext context, WidgetRef ref) { + final disableAnimations = MediaQuery.disableAnimationsOf(context); final containerId = ref.watch(selectedContainerProvider); final canManualReorder = ref.watch(canManualTabReorderProvider); final reorderEnabled = tabsReorderable && canManualReorder; @@ -263,13 +264,17 @@ class _TabListView extends HookConsumerWidget { scrollController.position.maxScrollExtent, ); - unawaited( - scrollController.animateTo( - targetOffset, - duration: const Duration(milliseconds: 200), - curve: Curves.easeInOut, - ), - ); + if (disableAnimations) { + scrollController.jumpTo(targetOffset); + } else { + unawaited( + scrollController.animateTo( + targetOffset, + duration: const Duration(milliseconds: 200), + curve: Curves.easeInOut, + ), + ); + } didInitialScroll.value = true; } else { @@ -465,6 +470,7 @@ class ViewTabListWidget extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final disableAnimations = MediaQuery.disableAnimationsOf(context); final isSyncedScope = ref.watch( effectiveTabsTrayScopeProvider.select( (scope) => scope == TabsTrayScope.synced, @@ -477,15 +483,18 @@ class ViewTabListWidget extends HookConsumerWidget { // Delay initialization to ignore initial animations useEffect(() { - final timer = Timer(const Duration(milliseconds: 500), () { - isInitialized.value = true; - // Initialize lastSheetSize with current size - if (draggableScrollableController?.isAttached == true) { - lastSheetSize.value = draggableScrollableController!.size; - } - }); + final timer = Timer( + disableAnimations ? Duration.zero : const Duration(milliseconds: 500), + () { + isInitialized.value = true; + // Initialize lastSheetSize with current size + if (draggableScrollableController?.isAttached == true) { + lastSheetSize.value = draggableScrollableController!.size; + } + }, + ); return timer.cancel; - }, []); + }, [disableAnimations]); // Listen to DraggableScrollableController for sheet size changes useEffect(() { @@ -572,11 +581,15 @@ class ViewTabListWidget extends HookConsumerWidget { ), if (showNewTabFab && !isSyncedScope) AnimatedSlide( - duration: const Duration(milliseconds: 200), + duration: disableAnimations + ? Duration.zero + : const Duration(milliseconds: 200), offset: isFabVisible.value ? Offset.zero : const Offset(0, 2), curve: Curves.easeInOut, child: AnimatedOpacity( - duration: const Duration(milliseconds: 200), + duration: disableAnimations + ? Duration.zero + : const Duration(milliseconds: 200), opacity: isFabVisible.value ? 1.0 : 0.0, child: Padding( padding: const EdgeInsets.only( diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_tree_view.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_tree_view.dart index 23913b5c..dfad59e4 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_tree_view.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/tab_view/tab_tree_view.dart @@ -211,6 +211,9 @@ class ViewTabTreesWidget extends HookConsumerWidget { child: HookConsumer( builder: (context, ref, child) { final screenWidth = MediaQuery.of(context).size.width; + final disableAnimations = MediaQuery.disableAnimationsOf( + context, + ); final containerId = ref.watch(selectedContainerProvider); @@ -265,13 +268,17 @@ class ViewTabTreesWidget extends HookConsumerWidget { final offset = (index ~/ 2) * itemSize.height; if (offset != scrollController.offset) { - unawaited( - scrollController.animateTo( - offset, - duration: const Duration(milliseconds: 200), - curve: Curves.easeInOut, - ), - ); + if (disableAnimations) { + scrollController.jumpTo(offset); + } else { + unawaited( + scrollController.animateTo( + offset, + duration: const Duration(milliseconds: 200), + curve: Curves.easeInOut, + ), + ); + } } } diff --git a/app/lib/features/geckoview/features/search/presentation/widgets/animated_tab_type_switcher.dart b/app/lib/features/geckoview/features/search/presentation/widgets/animated_tab_type_switcher.dart index 23182da7..856e3d30 100644 --- a/app/lib/features/geckoview/features/search/presentation/widgets/animated_tab_type_switcher.dart +++ b/app/lib/features/geckoview/features/search/presentation/widgets/animated_tab_type_switcher.dart @@ -126,6 +126,7 @@ class _Segment extends StatelessWidget { @override Widget build(BuildContext context) { + final disableAnimations = MediaQuery.disableAnimationsOf(context); final theme = Theme.of(context); final colorScheme = theme.colorScheme; @@ -140,7 +141,9 @@ class _Segment extends StatelessWidget { onTap: onTap, behavior: HitTestBehavior.opaque, child: AnimatedContainer( - duration: const Duration(milliseconds: 300), + duration: disableAnimations + ? Duration.zero + : const Duration(milliseconds: 300), curve: Curves.easeOutCubic, color: bgColor, padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), @@ -149,7 +152,9 @@ class _Segment extends StatelessWidget { children: [ Icon(icon, color: fgColor, size: 18), AnimatedSize( - duration: const Duration(milliseconds: 300), + duration: disableAnimations + ? Duration.zero + : const Duration(milliseconds: 300), curve: Curves.easeOutCubic, child: isSelected ? Padding( diff --git a/app/lib/features/geckoview/features/search/presentation/widgets/search_modules/search_module_header.dart b/app/lib/features/geckoview/features/search/presentation/widgets/search_modules/search_module_header.dart index 1fbe73cc..1bded264 100644 --- a/app/lib/features/geckoview/features/search/presentation/widgets/search_modules/search_module_header.dart +++ b/app/lib/features/geckoview/features/search/presentation/widgets/search_modules/search_module_header.dart @@ -54,6 +54,7 @@ class SearchModuleHeader extends StatelessWidget { @override Widget build(BuildContext context) { + final disableAnimations = MediaQuery.disableAnimationsOf(context); final isCollapsed = displayState == SearchModuleDisplayState.collapsed; final isExpanded = displayState == SearchModuleDisplayState.expanded; final showTrailing = !isCollapsed && totalCount > previewLimit; @@ -75,7 +76,9 @@ class SearchModuleHeader extends StatelessWidget { const SizedBox(width: 8), AnimatedRotation( turns: isCollapsed ? -0.25 : 0, - duration: const Duration(milliseconds: 200), + duration: disableAnimations + ? Duration.zero + : const Duration(milliseconds: 200), child: Icon( Icons.expand_more, size: 20, diff --git a/app/lib/features/geckoview/features/tabs/presentation/screens/container_edit.dart b/app/lib/features/geckoview/features/tabs/presentation/screens/container_edit.dart index f7e501ac..a7c4a5e3 100644 --- a/app/lib/features/geckoview/features/tabs/presentation/screens/container_edit.dart +++ b/app/lib/features/geckoview/features/tabs/presentation/screens/container_edit.dart @@ -70,6 +70,7 @@ class ContainerEditScreen extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final disableAnimations = MediaQuery.disableAnimationsOf(context); final selectedColor = useState(initialContainer.color); final contextualIdentity = useState( initialContainer.metadata.contextualIdentity, @@ -136,7 +137,9 @@ class ContainerEditScreen extends HookConsumerWidget { prefixIcon: Padding( padding: const EdgeInsets.all(10.0), child: AnimatedContainer( - duration: const Duration(milliseconds: 300), + duration: disableAnimations + ? Duration.zero + : const Duration(milliseconds: 300), height: 24, width: 24, decoration: BoxDecoration( diff --git a/app/lib/features/geckoview/features/tabs/presentation/widgets/material_color_picker.dart b/app/lib/features/geckoview/features/tabs/presentation/widgets/material_color_picker.dart index 49d6edbb..02103c32 100644 --- a/app/lib/features/geckoview/features/tabs/presentation/widgets/material_color_picker.dart +++ b/app/lib/features/geckoview/features/tabs/presentation/widgets/material_color_picker.dart @@ -67,6 +67,7 @@ class _MaterialPickerState extends State { @override Widget build(BuildContext context) { + final disableAnimations = MediaQuery.disableAnimationsOf(context); final isPortrait = MediaQuery.of(context).orientation == Orientation.portrait || widget.portraitOnly; @@ -143,7 +144,9 @@ class _MaterialPickerState extends State { : const EdgeInsets.fromLTRB(7, 0, 7, 0), child: Align( child: AnimatedContainer( - duration: const Duration(milliseconds: 300), + duration: disableAnimations + ? Duration.zero + : const Duration(milliseconds: 300), width: 25, height: 25, decoration: BoxDecoration( @@ -235,7 +238,9 @@ class _MaterialPickerState extends State { child: Align( child: AnimatedContainer( curve: Curves.fastOutSlowIn, - duration: const Duration(milliseconds: 500), + duration: disableAnimations + ? Duration.zero + : const Duration(milliseconds: 500), width: isPortrait ? (_currentShading == color ? 250 : 230) : (_currentShading == color ? 50 : 30), @@ -308,9 +313,9 @@ class _MaterialPickerState extends State { ], ) : AnimatedOpacity( - duration: const Duration( - milliseconds: 300, - ), + duration: disableAnimations + ? Duration.zero + : const Duration(milliseconds: 300), opacity: _currentShading == color ? 1 : 0, diff --git a/app/lib/features/onboarding/presentation/onboarding.dart b/app/lib/features/onboarding/presentation/onboarding.dart index 2a7d1184..bfef2d6c 100644 --- a/app/lib/features/onboarding/presentation/onboarding.dart +++ b/app/lib/features/onboarding/presentation/onboarding.dart @@ -47,6 +47,7 @@ class OnboardingScreen extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final theme = Theme.of(context); + final disableAnimations = MediaQuery.disableAnimationsOf(context); final pageController = usePageController(); @@ -95,10 +96,14 @@ class OnboardingScreen extends HookConsumerWidget { visible: currentPage.value > 0, child: TextButton.icon( onPressed: () async { - await pageController.previousPage( - duration: const Duration(milliseconds: 250), - curve: Curves.easeInOut, - ); + if (disableAnimations) { + pageController.jumpToPage(currentPage.value - 1); + } else { + await pageController.previousPage( + duration: const Duration(milliseconds: 250), + curve: Curves.easeInOut, + ); + } }, icon: const Icon(Icons.chevron_left), label: const Text('Previous'), @@ -121,10 +126,14 @@ class OnboardingScreen extends HookConsumerWidget { Expanded( child: TextButton.icon( onPressed: () async { - await pageController.nextPage( - duration: const Duration(milliseconds: 250), - curve: Curves.easeInOut, - ); + if (disableAnimations) { + pageController.jumpToPage(currentPage.value + 1); + } else { + await pageController.nextPage( + duration: const Duration(milliseconds: 250), + curve: Curves.easeInOut, + ); + } }, iconAlignment: IconAlignment.end, icon: const Icon(Icons.chevron_right), diff --git a/app/lib/presentation/hooks/scroll_visibility.dart b/app/lib/presentation/hooks/scroll_visibility.dart index b4bbfc31..066fda67 100644 --- a/app/lib/presentation/hooks/scroll_visibility.dart +++ b/app/lib/presentation/hooks/scroll_visibility.dart @@ -44,43 +44,59 @@ ValueNotifier useScrollVisibility( double hideThreshold = 5.0, double showThreshold = 5.0, Duration initializationDelay = const Duration(milliseconds: 1000), + bool disableAnimations = false, }) { final isVisible = useState(true); final lastScrollOffset = useRef(0.0); final isInitialized = useRef(false); - useEffect(() { - // Start timer to enable scroll listener after initialization delay - final timer = Timer(initializationDelay, () { - isInitialized.value = true; - }); + useEffect( + () { + // Start timer to enable scroll listener after initialization delay + final timer = Timer( + disableAnimations ? Duration.zero : initializationDelay, + () { + if (scrollController.hasClients) { + lastScrollOffset.value = scrollController.offset; + } + isInitialized.value = true; + }, + ); - void scrollListener() { - // Ignore scroll events until initialization is complete - if (!isInitialized.value) return; + void scrollListener() { + // Ignore scroll events until initialization is complete + if (!isInitialized.value) return; - final currentOffset = scrollController.offset; - final difference = currentOffset - lastScrollOffset.value; + final currentOffset = scrollController.offset; + final difference = currentOffset - lastScrollOffset.value; - // Hide when scrolling down beyond threshold - if (difference > hideThreshold && isVisible.value) { - isVisible.value = false; - } - // Show when scrolling up beyond threshold or at top - else if ((difference < -showThreshold || currentOffset <= 0) && - !isVisible.value) { - isVisible.value = true; + // Hide when scrolling down beyond threshold + if (difference > hideThreshold && isVisible.value) { + isVisible.value = false; + } + // Show when scrolling up beyond threshold or at top + else if ((difference < -showThreshold || currentOffset <= 0) && + !isVisible.value) { + isVisible.value = true; + } + + lastScrollOffset.value = currentOffset; } - lastScrollOffset.value = currentOffset; - } - - scrollController.addListener(scrollListener); - return () { - timer.cancel(); - scrollController.removeListener(scrollListener); - }; - }, [scrollController, hideThreshold, showThreshold, initializationDelay]); + scrollController.addListener(scrollListener); + return () { + timer.cancel(); + scrollController.removeListener(scrollListener); + }; + }, + [ + scrollController, + hideThreshold, + showThreshold, + initializationDelay, + disableAnimations, + ], + ); return isVisible; } diff --git a/app/lib/presentation/hooks/sync_page_tab.dart b/app/lib/presentation/hooks/sync_page_tab.dart index 237e62d1..b9f19a81 100644 --- a/app/lib/presentation/hooks/sync_page_tab.dart +++ b/app/lib/presentation/hooks/sync_page_tab.dart @@ -24,14 +24,19 @@ void useSyncPageWithTab( TabController tabController, PageController pageController, { void Function(int index)? onIndexChanged, + bool disableAnimations = false, }) { useEffect(() { Future syncPage() async { - await pageController.animateToPage( - tabController.index, - curve: Curves.linear, - duration: const Duration(milliseconds: 300), - ); + if (disableAnimations) { + pageController.jumpToPage(tabController.index); + } else { + await pageController.animateToPage( + tabController.index, + curve: Curves.linear, + duration: const Duration(milliseconds: 300), + ); + } onIndexChanged?.call(tabController.index); } @@ -39,7 +44,10 @@ void useSyncPageWithTab( if (!tabController.indexIsChanging) { final index = pageController.page!.round(); - tabController.animateTo(index); + tabController.animateTo( + index, + duration: disableAnimations ? Duration.zero : kTabScrollDuration, + ); onIndexChanged?.call(index); } } @@ -51,5 +59,5 @@ void useSyncPageWithTab( tabController.removeListener(syncPage); pageController.removeListener(syncTab); }; - }, [tabController, pageController]); + }, [tabController, pageController, disableAnimations]); }