respect disableAnimations flag

This commit is contained in:
Fabian Freund
2026-03-09 18:03:34 +01:00
parent a815a3529a
commit 0a8348b324
8 changed files with 102 additions and 19 deletions
@@ -76,8 +76,10 @@ class _AnimatedToolbar extends HookWidget {
@override
Widget build(BuildContext context) {
final disableAnimations = MediaQuery.disableAnimationsOf(context);
final controller = useAnimationController(
duration: _kAnimationDuration,
duration: disableAnimations ? Duration.zero : _kAnimationDuration,
initialValue: visible ? 1.0 : 0.0,
);
@@ -618,10 +618,7 @@ class _PinTopSiteTile extends HookConsumerWidget {
} else {
await ref
.read(topSiteRepositoryProvider.notifier)
.addPinnedSite(
title: tabState.titleOrAuthority,
url: url,
);
.addPinnedSite(title: tabState.titleOrAuthority, url: url);
if (context.mounted) {
ui_helper.showInfoMessage(context, 'Pinned to Top Sites');
}
@@ -1932,19 +1929,21 @@ class _SyncTile extends HookConsumerWidget {
);
final isSyncing = syncStarted || syncInfo?.syncing == true;
final disableAnimations = MediaQuery.disableAnimationsOf(context);
final controller = useAnimationController(
duration: const Duration(seconds: 2),
duration: disableAnimations ? Duration.zero : const Duration(seconds: 2),
);
useEffect(() {
if (isSyncing) {
if (isSyncing && !disableAnimations) {
unawaited(controller.repeat());
} else {
controller.stop();
controller.reset();
}
return null;
}, [isSyncing]);
}, [isSyncing, disableAnimations]);
return ListTile(
leading: RotationTransition(
@@ -74,6 +74,11 @@ class _DraggableScrollableHeaderState extends State<DraggableScrollableHeader>
double targetSize, {
Duration? customDuration,
}) async {
if (MediaQuery.disableAnimationsOf(context)) {
widget.controller.jumpTo(targetSize);
return;
}
if (customDuration != null) {
_animationController.duration = customDuration;
} else {
@@ -53,19 +53,21 @@ class SyncSettingsScreen extends HookConsumerWidget {
return 'Last synced: $formattedDate';
}, [syncInfo, isSyncing]);
final disableAnimations = MediaQuery.disableAnimationsOf(context);
final syncController = useAnimationController(
duration: const Duration(seconds: 2),
duration: disableAnimations ? Duration.zero : const Duration(seconds: 2),
);
useEffect(() {
if (isSyncing) {
if (isSyncing && !disableAnimations) {
unawaited(syncController.repeat());
} else {
syncController.stop();
syncController.reset();
}
return null;
}, [isSyncing]);
}, [isSyncing, disableAnimations]);
return Scaffold(
appBar: AppBar(title: const Text('Firefox Sync')),
+31
View File
@@ -81,6 +81,31 @@ bool _hasBrokenSurfaceContainerColors(ColorScheme scheme) {
scheme.surfaceContainerHighest == scheme.surface;
}
class _NoAnimationPageTransitionsBuilder extends PageTransitionsBuilder {
const _NoAnimationPageTransitionsBuilder();
@override
Widget buildTransitions<T>(
PageRoute<T> route,
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) {
return child;
}
}
const _noAnimationPageTransitionsTheme = PageTransitionsTheme(
builders: {
TargetPlatform.android: _NoAnimationPageTransitionsBuilder(),
TargetPlatform.iOS: _NoAnimationPageTransitionsBuilder(),
TargetPlatform.linux: _NoAnimationPageTransitionsBuilder(),
TargetPlatform.macOS: _NoAnimationPageTransitionsBuilder(),
TargetPlatform.windows: _NoAnimationPageTransitionsBuilder(),
},
);
class _MainWidget extends HookConsumerWidget {
const _MainWidget();
@@ -257,11 +282,17 @@ class _MainWidget extends HookConsumerWidget {
theme: ThemeData(
useMaterial3: true,
colorScheme: lightColorScheme,
pageTransitionsTheme: disableAnimations
? _noAnimationPageTransitionsTheme
: null,
extensions: const <ThemeExtension<dynamic>>[AppColors.light],
),
darkTheme: ThemeData(
useMaterial3: true,
colorScheme: darkColorScheme,
pageTransitionsTheme: disableAnimations
? _noAnimationPageTransitionsTheme
: null,
extensions: const <ThemeExtension<dynamic>>[AppColors.dark],
),
themeMode: themeMode,
+9
View File
@@ -55,6 +55,9 @@ class MainApp extends HookConsumerWidget {
theme: theme,
darkTheme: darkTheme,
themeMode: themeMode,
themeAnimationStyle: disableAnimations
? AnimationStyle.noAnimation
: null,
builder: (context, child) {
return _AppMediaQueryOverrides(
uiScaleFactor: uiScaleFactor,
@@ -85,6 +88,9 @@ class MainApp extends HookConsumerWidget {
theme: theme,
darkTheme: darkTheme,
themeMode: themeMode,
themeAnimationStyle: disableAnimations
? AnimationStyle.noAnimation
: null,
routerConfig: router.value,
builder: (context, child) {
return _AppMediaQueryOverrides(
@@ -103,6 +109,9 @@ class MainApp extends HookConsumerWidget {
theme: theme,
darkTheme: darkTheme,
themeMode: themeMode,
themeAnimationStyle: disableAnimations
? AnimationStyle.noAnimation
: null,
builder: (context, child) {
return _AppMediaQueryOverrides(
uiScaleFactor: uiScaleFactor,
@@ -120,12 +120,24 @@ class _AnimateGradientShaderState extends State<AnimateGradientShader>
List<Color> primaryColors = [];
List<Color> secondaryColors = [];
bool _disableAnimations = false;
@override
void initState() {
_initialize();
super.initState();
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
final disableAnimations = MediaQuery.disableAnimationsOf(context);
if (disableAnimations != _disableAnimations) {
_disableAnimations = disableAnimations;
_setAnimations();
}
}
@override
void didUpdateWidget(AnimateGradientShader oldWidget) {
_initialize();
@@ -222,10 +234,15 @@ class _AnimateGradientShaderState extends State<AnimateGradientShader>
void _setAnimations() {
_controller?.dispose();
_controller =
(widget.controller ??
AnimationController(vsync: this, duration: widget.duration))
// ignore: discarded_futures
..repeat(reverse: widget.reverse);
widget.controller ??
AnimationController(vsync: this, duration: widget.duration);
if (_disableAnimations) {
_controller!.value = 0;
} else {
// ignore: discarded_futures
_controller!.repeat(reverse: widget.reverse);
}
_animation = CurvedAnimation(parent: _controller!, curve: Curves.easeInOut);
}
@@ -129,12 +129,30 @@ class _AnimatedIndexedStackState extends State<AnimatedIndexedStack>
/// The entry which is currently at the top of the stack.
_ChildEntry? _currentEntry;
bool _disableAnimations = false;
Duration get _effectiveDuration =>
_disableAnimations ? Duration.zero : widget.duration;
@override
void initState() {
super.initState();
_updateEntriesList();
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
final disableAnimations = MediaQuery.disableAnimationsOf(context);
if (disableAnimations != _disableAnimations) {
_disableAnimations = disableAnimations;
for (final entry in _entries) {
entry.primaryController.duration = _effectiveDuration;
entry.secondaryController.duration = _effectiveDuration;
}
}
}
@override
void didUpdateWidget(AnimatedIndexedStack oldWidget) {
super.didUpdateWidget(oldWidget);
@@ -207,8 +225,8 @@ class _AnimatedIndexedStackState extends State<AnimatedIndexedStack>
// If we find an existing entry, we update its child widget and reuse it.
// This ensures it continues to use the same global key and animation controllers.
existingEntry.child = child;
existingEntry.primaryController.duration = widget.duration;
existingEntry.secondaryController.duration = widget.duration;
existingEntry.primaryController.duration = _effectiveDuration;
existingEntry.secondaryController.duration = _effectiveDuration;
entry = existingEntry;
} else {
entry = _newEntry(child);
@@ -299,11 +317,11 @@ class _AnimatedIndexedStackState extends State<AnimatedIndexedStack>
key: GlobalKey(),
child: child,
primaryController: AnimationController(
duration: widget.duration,
duration: _effectiveDuration,
vsync: this,
),
secondaryController: AnimationController(
duration: widget.duration,
duration: _effectiveDuration,
vsync: this,
),
);