#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 // search animations
static const filterRowExpandAnimation = Duration(milliseconds: 300); static const filterRowExpandAnimation = Duration(milliseconds: 300);
static const searchBodyTransition = Duration(milliseconds: 300);
// viewer animations // viewer animations
static const thumbnailScrollerScrollAnimation = Duration(milliseconds: 200); 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 { class AvesScrollBehavior extends MaterialScrollBehavior {
@override @override
Widget buildOverscrollIndicator(BuildContext context, Widget child, ScrollableDetails details) { Widget buildOverscrollIndicator(BuildContext context, Widget child, ScrollableDetails details) {
@ -674,11 +677,7 @@ class AvesScrollBehavior extends MaterialScrollBehavior {
axisDirection: details.direction, axisDirection: details.direction,
child: child, child: child,
) )
: GlowingOverscrollIndicator( : child;
axisDirection: details.direction,
color: Colors.white,
child: child,
);
} }
} }

View file

@ -5,6 +5,7 @@ import 'package:aves/widgets/common/search/route.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
abstract class AvesSearchDelegate extends SearchDelegate { abstract class AvesSearchDelegate extends SearchDelegate {
final String routeName; final String routeName;
@ -38,12 +39,13 @@ abstract class AvesSearchDelegate extends SearchDelegate {
// use a property instead of checking `Navigator.canPop(context)` // use a property instead of checking `Navigator.canPop(context)`
// because the navigator state changes as soon as we press back // because the navigator state changes as soon as we press back
// so the leading may mistakenly switch to the close button // so the leading may mistakenly switch to the close button
final animate = context.read<Settings>().animate;
return canPop return canPop
? IconButton( ? IconButton(
icon: AnimatedIcon( icon: animate ? AnimatedIcon(
icon: AnimatedIcons.menu_arrow, icon: AnimatedIcons.menu_arrow,
progress: transitionAnimation, progress: transitionAnimation,
), ): const Icon(Icons.arrow_back),
onPressed: () => goBack(context), onPressed: () => goBack(context),
tooltip: MaterialLocalizations.of(context).backButtonTooltip, 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/durations.dart';
import 'package:aves/theme/themes.dart'; import 'package:aves/theme/themes.dart';
import 'package:aves/utils/debouncer.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:aves/widgets/common/search/route.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
import 'package:provider/provider.dart';
class SearchPage extends StatefulWidget { class SearchPage extends StatefulWidget {
static const routeName = '/search'; static const routeName = '/search';
@ -103,7 +105,24 @@ class _SearchPageState extends State<SearchPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context); final theme = Theme.of(context);
Widget? body;
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,
focusNode: _searchFieldFocusNode,
decoration: InputDecoration(
border: InputBorder.none,
hintText: widget.delegate.searchFieldLabel,
hintStyle: theme.inputDecorationTheme.hintStyle,
),
textInputAction: TextInputAction.search,
style: Themes.searchFieldStyle(context),
onSubmitted: (_) => widget.delegate.showResults(context),
),
);
Widget body;
switch (widget.delegate.currentBody) { switch (widget.delegate.currentBody) {
case SearchBody.suggestions: case SearchBody.suggestions:
body = KeyedSubtree( body = KeyedSubtree(
@ -116,34 +135,31 @@ class _SearchPageState extends State<SearchPage> {
child: widget.delegate.buildResults(context), child: widget.delegate.buildResults(context),
); );
case null: case null:
break; 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( return AvesScaffold(
appBar: AppBar( appBar: AppBar(
leading: Hero( leading: leading,
tag: AvesAppBar.leadingHeroTag, title: title,
transitionOnUserGestures: true,
child: Center(child: widget.delegate.buildLeading(context)),
),
title: Hero(
tag: AvesAppBar.titleHeroTag,
transitionOnUserGestures: true,
child: DefaultTextStyle.merge(
style: const TextStyle(fontFeatures: [FontFeature.disable('smcp')]),
child: TextField(
controller: widget.delegate.queryTextController,
focusNode: _searchFieldFocusNode,
decoration: InputDecoration(
border: InputBorder.none,
hintText: widget.delegate.searchFieldLabel,
hintStyle: theme.inputDecorationTheme.hintStyle,
),
textInputAction: TextInputAction.search,
style: Themes.searchFieldStyle(context),
onSubmitted: (_) => widget.delegate.showResults(context),
),
),
),
actions: widget.delegate.buildActions(context), actions: widget.delegate.buildActions(context),
), ),
body: AvesPopScope( body: AvesPopScope(
@ -151,10 +167,7 @@ class _SearchPageState extends State<SearchPage> {
tvNavigationPopHandler, tvNavigationPopHandler,
doubleBackPopHandler, doubleBackPopHandler,
], ],
child: AnimatedSwitcher( child: body,
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/delegate.dart';
import 'package:aves/widgets/common/search/page.dart'; import 'package:aves/widgets/common/search/page.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
// adapted from Flutter `_SearchBody` in `/material/search.dart` // adapted from Flutter `_SearchBody` in `/material/search.dart`
enum SearchBody { suggestions, results } 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, // a simple fade is usually more fitting for a search page,
// instead of the `pageTransitionsTheme` used by the rest of the app // instead of the `pageTransitionsTheme` used by the rest of the app
return FadeTransition( final animate = context.read<Settings>().animate;
opacity: animation, return animate
child: child, ? FadeTransition(
); opacity: animation,
child: child,
)
: child;
} }
@override @override