improved support for disable animations
This commit is contained in:
@@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+11
-5
@@ -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<void>? 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)
|
||||
|
||||
+4
-1
@@ -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<Offset?>(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,
|
||||
),
|
||||
|
||||
+30
-17
@@ -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(
|
||||
|
||||
+30
-17
@@ -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(
|
||||
|
||||
+14
-7
@@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-2
@@ -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(
|
||||
|
||||
+4
-1
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
+10
-5
@@ -67,6 +67,7 @@ class _MaterialPickerState extends State<MaterialPicker> {
|
||||
|
||||
@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<MaterialPicker> {
|
||||
: 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<MaterialPicker> {
|
||||
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<MaterialPicker> {
|
||||
],
|
||||
)
|
||||
: AnimatedOpacity(
|
||||
duration: const Duration(
|
||||
milliseconds: 300,
|
||||
),
|
||||
duration: disableAnimations
|
||||
? Duration.zero
|
||||
: const Duration(milliseconds: 300),
|
||||
opacity: _currentShading == color
|
||||
? 1
|
||||
: 0,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -44,43 +44,59 @@ ValueNotifier<bool> 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;
|
||||
}
|
||||
|
||||
@@ -24,14 +24,19 @@ void useSyncPageWithTab(
|
||||
TabController tabController,
|
||||
PageController pageController, {
|
||||
void Function(int index)? onIndexChanged,
|
||||
bool disableAnimations = false,
|
||||
}) {
|
||||
useEffect(() {
|
||||
Future<void> 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]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user