#1102 accessibility: enable/disable more animations

This commit is contained in:
Thibault Deckers 2024-07-27 21:20:35 +02:00
parent fcde32d555
commit e3f6644366
5 changed files with 61 additions and 41 deletions

View file

@ -32,6 +32,7 @@ class ADurations {
// search animations
static const filterRowExpandAnimation = Duration(milliseconds: 300);
static const searchBodyTransition = Duration(milliseconds: 300);
// viewer animations
static const thumbnailScrollerScrollAnimation = Duration(milliseconds: 200);

View file

@ -665,6 +665,9 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
}
}
// Flutter has various overscroll indicator implementations for Android:
// - `StretchingOverscrollIndicator`, default when using Material 3
// - `GlowingOverscrollIndicator`, default when not using Material 3
class AvesScrollBehavior extends MaterialScrollBehavior {
@override
Widget buildOverscrollIndicator(BuildContext context, Widget child, ScrollableDetails details) {
@ -674,11 +677,7 @@ class AvesScrollBehavior extends MaterialScrollBehavior {
axisDirection: details.direction,
child: child,
)
: GlowingOverscrollIndicator(
axisDirection: details.direction,
color: Colors.white,
child: child,
);
: child;
}
}

View file

@ -5,6 +5,7 @@ import 'package:aves/widgets/common/search/route.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
abstract class AvesSearchDelegate extends SearchDelegate {
final String routeName;
@ -38,12 +39,13 @@ abstract class AvesSearchDelegate extends SearchDelegate {
// use a property instead of checking `Navigator.canPop(context)`
// because the navigator state changes as soon as we press back
// so the leading may mistakenly switch to the close button
final animate = context.read<Settings>().animate;
return canPop
? IconButton(
icon: AnimatedIcon(
icon: animate ? AnimatedIcon(
icon: AnimatedIcons.menu_arrow,
progress: transitionAnimation,
),
): const Icon(Icons.arrow_back),
onPressed: () => goBack(context),
tooltip: MaterialLocalizations.of(context).backButtonTooltip,
)

View file

@ -1,3 +1,4 @@
import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/theme/themes.dart';
import 'package:aves/utils/debouncer.dart';
@ -10,6 +11,7 @@ import 'package:aves/widgets/common/search/delegate.dart';
import 'package:aves/widgets/common/search/route.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:provider/provider.dart';
class SearchPage extends StatefulWidget {
static const routeName = '/search';
@ -103,32 +105,9 @@ class _SearchPageState extends State<SearchPage> {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
Widget? body;
switch (widget.delegate.currentBody) {
case SearchBody.suggestions:
body = KeyedSubtree(
key: const ValueKey<SearchBody>(SearchBody.suggestions),
child: widget.delegate.buildSuggestions(context),
);
case SearchBody.results:
body = KeyedSubtree(
key: const ValueKey<SearchBody>(SearchBody.results),
child: widget.delegate.buildResults(context),
);
case null:
break;
}
return AvesScaffold(
appBar: AppBar(
leading: Hero(
tag: AvesAppBar.leadingHeroTag,
transitionOnUserGestures: true,
child: Center(child: widget.delegate.buildLeading(context)),
),
title: Hero(
tag: AvesAppBar.titleHeroTag,
transitionOnUserGestures: true,
child: DefaultTextStyle.merge(
Widget leading = Center(child: widget.delegate.buildLeading(context));
Widget title = DefaultTextStyle.merge(
style: const TextStyle(fontFeatures: [FontFeature.disable('smcp')]),
child: TextField(
controller: widget.delegate.queryTextController,
@ -142,8 +121,45 @@ class _SearchPageState extends State<SearchPage> {
style: Themes.searchFieldStyle(context),
onSubmitted: (_) => widget.delegate.showResults(context),
),
),
),
);
Widget body;
switch (widget.delegate.currentBody) {
case SearchBody.suggestions:
body = KeyedSubtree(
key: const ValueKey<SearchBody>(SearchBody.suggestions),
child: widget.delegate.buildSuggestions(context),
);
case SearchBody.results:
body = KeyedSubtree(
key: const ValueKey<SearchBody>(SearchBody.results),
child: widget.delegate.buildResults(context),
);
case null:
body = const SizedBox();
}
final animate = context.select<Settings, bool>((v) => v.animate);
if (animate) {
leading = Hero(
tag: AvesAppBar.leadingHeroTag,
transitionOnUserGestures: true,
child: leading,
);
title = Hero(
tag: AvesAppBar.titleHeroTag,
transitionOnUserGestures: true,
child: title,
);
body = AnimatedSwitcher(
duration: ADurations.searchBodyTransition,
child: body,
);
}
return AvesScaffold(
appBar: AppBar(
leading: leading,
title: title,
actions: widget.delegate.buildActions(context),
),
body: AvesPopScope(
@ -151,11 +167,8 @@ class _SearchPageState extends State<SearchPage> {
tvNavigationPopHandler,
doubleBackPopHandler,
],
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: body,
),
),
);
}
}

View file

@ -1,6 +1,8 @@
import 'package:aves/model/settings/settings.dart';
import 'package:aves/widgets/common/search/delegate.dart';
import 'package:aves/widgets/common/search/page.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
// adapted from Flutter `_SearchBody` in `/material/search.dart`
enum SearchBody { suggestions, results }
@ -42,10 +44,13 @@ class SearchPageRoute<T> extends PageRoute<T> {
) {
// a simple fade is usually more fitting for a search page,
// instead of the `pageTransitionsTheme` used by the rest of the app
return FadeTransition(
final animate = context.read<Settings>().animate;
return animate
? FadeTransition(
opacity: animation,
child: child,
);
)
: child;
}
@override