accessibility: remove animations (WIP)

This commit is contained in:
Thibault Deckers 2021-09-28 11:17:55 +09:00
parent c5942ba344
commit 8d096e5e9b
27 changed files with 237 additions and 53 deletions

View file

@ -59,6 +59,6 @@ class AccessibilityHandler(private val context: Activity) : MethodCallHandler {
}
companion object {
const val CHANNEL = "deckers.thibault/aves/a11y"
const val CHANNEL = "deckers.thibault/aves/accessibility"
}
}

View file

@ -200,6 +200,11 @@
"keepScreenOnAlways": "Always",
"@keepScreenOnAlways": {},
"accessibilityAnimationsRemove": "Prevent screen effects",
"@accessibilityAnimationsRemove": {},
"accessibilityAnimationsKeep": "Keep screen effects",
"@accessibilityAnimationsKeep": {},
"albumTierNew": "New",
"@albumTierNew": {},
"albumTierPinned": "Pinned",
@ -799,6 +804,10 @@
"settingsSectionAccessibility": "Accessibility",
"@settingsSectionAccessibility": {},
"settingsRemoveAnimationsTile": "Remove animations",
"@settingsRemoveAnimationsTile": {},
"settingsRemoveAnimationsTitle": "Remove Animations",
"@settingsRemoveAnimationsTitle": {},
"settingsTimeToTakeActionTile": "Time to take action",
"@settingsTimeToTakeActionTile": {},
"settingsTimeToTakeActionTitle": "Time to Take Action",

View file

@ -101,6 +101,9 @@
"keepScreenOnViewerOnly": "뷰어 이용 시 작동",
"keepScreenOnAlways": "항상 켜짐",
"accessibilityAnimationsRemove": "화면 효과 제한",
"accessibilityAnimationsKeep": "화면 효과 유지",
"albumTierNew": "신규",
"albumTierPinned": "고정",
"albumTierSpecial": "기본",
@ -389,6 +392,8 @@
"settingsStorageAccessRevokeTooltip": "취소",
"settingsSectionAccessibility": "접근성",
"settingsRemoveAnimationsTile": "애니메이션 삭제",
"settingsRemoveAnimationsTitle": "애니메이션 삭제",
"settingsTimeToTakeActionTile": "액션 취하기 전 대기 시간",
"settingsTimeToTakeActionTitle": "액션 취하기 전 대기 시간",

View file

@ -0,0 +1,17 @@
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:flutter/widgets.dart';
import 'enums.dart';
extension ExtraAccessibilityAnimations on AccessibilityAnimations {
String getName(BuildContext context) {
switch (this) {
case AccessibilityAnimations.system:
return context.l10n.settingsSystemDefault;
case AccessibilityAnimations.disabled:
return context.l10n.accessibilityAnimationsRemove;
case AccessibilityAnimations.enabled:
return context.l10n.accessibilityAnimationsKeep;
}
}
}

View file

@ -57,9 +57,7 @@ class SettingsDefaults {
static const showOverlayMinimap = false;
static const showOverlayInfo = true;
static const showOverlayShootingDetails = false;
// `enableOverlayBlurEffect` has a contextual default value
static const enableOverlayBlurEffect = true;
static const enableOverlayBlurEffect = true; // `enableOverlayBlurEffect` has a contextual default value
static const viewerUseCutout = true;
// video
@ -80,8 +78,7 @@ class SettingsDefaults {
static const subtitleBackgroundColor = Colors.transparent;
// info
// `infoMapStyle` has a contextual default value
static const infoMapStyle = EntryMapStyle.stamenWatercolor;
static const infoMapStyle = EntryMapStyle.stamenWatercolor; // `infoMapStyle` has a contextual default value
static const infoMapZoom = 12.0;
static const coordinateFormat = CoordinateFormat.dms;
@ -92,6 +89,6 @@ class SettingsDefaults {
static const saveSearchHistory = true;
// accessibility
// `timeToTakeAction` has a contextual default value
static const timeToTakeAction = AccessibilityTimeout.appDefault;
static const accessibilityAnimations = AccessibilityAnimations.system;
static const timeToTakeAction = AccessibilityTimeout.appDefault; // `timeToTakeAction` has a contextual default value
}

View file

@ -1,5 +1,7 @@
enum CoordinateFormat { dms, decimal }
enum AccessibilityAnimations { system, disabled, enabled }
enum AccessibilityTimeout { system, appDefault, s10, s30, s60, s120 }
enum EntryBackground { black, white, checkered }

View file

@ -10,7 +10,7 @@ import 'package:aves/model/settings/defaults.dart';
import 'package:aves/model/settings/enums.dart';
import 'package:aves/model/settings/map_style.dart';
import 'package:aves/model/source/enums.dart';
import 'package:aves/services/a11y_service.dart';
import 'package:aves/services/accessibility_service.dart';
import 'package:aves/services/common/services.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
@ -106,6 +106,7 @@ class Settings extends ChangeNotifier {
static const searchHistoryKey = 'search_history';
// accessibility
static const accessibilityAnimationsKey = 'accessibility_animations';
static const timeToTakeActionKey = 'time_to_take_action';
// version
@ -115,6 +116,8 @@ class Settings extends ChangeNotifier {
// cf Android `Settings.System.ACCELEROMETER_ROTATION`
static const platformAccelerometerRotationKey = 'accelerometer_rotation';
bool get initialized => _prefs != null;
Future<void> init({bool isRotationLocked = false}) async {
_prefs = await SharedPreferences.getInstance();
_isRotationLocked = isRotationLocked;
@ -382,6 +385,10 @@ class Settings extends ChangeNotifier {
// accessibility
AccessibilityAnimations get accessibilityAnimations => getEnumOrDefault(accessibilityAnimationsKey, SettingsDefaults.accessibilityAnimations, AccessibilityAnimations.values);
set accessibilityAnimations(AccessibilityAnimations newValue) => setAndNotify(accessibilityAnimationsKey, newValue.toString());
AccessibilityTimeout get timeToTakeAction => getEnumOrDefault(timeToTakeActionKey, SettingsDefaults.timeToTakeAction, AccessibilityTimeout.values);
set timeToTakeAction(AccessibilityTimeout newValue) => setAndNotify(timeToTakeActionKey, newValue.toString());
@ -538,6 +545,7 @@ class Settings extends ChangeNotifier {
case infoMapStyleKey:
case coordinateFormatKey:
case imageBackgroundKey:
case accessibilityAnimationsKey:
case timeToTakeActionKey:
if (value is String) {
_prefs!.setString(key, value);

View file

@ -2,7 +2,7 @@ import 'package:aves/services/common/services.dart';
import 'package:flutter/services.dart';
class AccessibilityService {
static const platform = MethodChannel('deckers.thibault/aves/a11y');
static const platform = MethodChannel('deckers.thibault/aves/accessibility');
static Future<bool> hasRecommendedTimeouts() async {
try {

View file

@ -1,4 +1,7 @@
import 'package:flutter/scheduler.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/widgets/common/behaviour/accessibility_mixin.dart';
import 'package:flutter/widgets.dart';
import 'package:provider/provider.dart';
class Durations {
// Flutter animations (with margin)
@ -14,8 +17,8 @@ class Durations {
static const sweeperOpacityAnimation = Duration(milliseconds: 150);
static const sweepingAnimation = Duration(milliseconds: 650);
static const staggeredAnimation = Duration(milliseconds: 375);
static const staggeredAnimationPageTarget = Duration(milliseconds: 800);
// static const staggeredAnimation = Duration(milliseconds: 375);
// static const staggeredAnimationPageTarget = Duration(milliseconds: 800);
static const dialogFieldReachAnimation = Duration(milliseconds: 300);
static const appBarTitleAnimation = Duration(milliseconds: 300);
@ -64,7 +67,8 @@ class Durations {
static const highlightScrollInitDelay = Duration(milliseconds: 800);
static const videoOverlayHideDelay = Duration(milliseconds: 500);
static const videoProgressTimerInterval = Duration(milliseconds: 300);
static Duration staggeredAnimationDelay = Durations.staggeredAnimation ~/ 6 * timeDilation;
// static Duration staggeredAnimationDelay = Durations.staggeredAnimation ~/ 6 * timeDilation;
static const doubleBackTimerDelay = Duration(milliseconds: 1000);
static const softKeyboardDisplayDelay = Duration(milliseconds: 300);
static const searchDebounceDelay = Duration(milliseconds: 250);
@ -75,3 +79,44 @@ class Durations {
// app life
static const lastVersionCheckInterval = Duration(days: 7);
}
class DurationsProvider extends StatelessWidget with AccessibilityMixin {
final Widget child;
const DurationsProvider({
Key? key,
required this.child,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return ProxyProvider<Settings, DurationsData>(
update: (_, settings, __) {
return areAnimationsEnabled() ? DurationsData() : DurationsData.noAnimation();
},
child: child,
);
}
}
@immutable
class DurationsData {
// common animations
final Duration staggeredAnimation;
final Duration staggeredAnimationPageTarget;
// delays & refresh intervals
final Duration staggeredAnimationDelay;
const DurationsData({
this.staggeredAnimation = const Duration(milliseconds: 375),
this.staggeredAnimationPageTarget = const Duration(milliseconds: 800),
}) : staggeredAnimationDelay = staggeredAnimation ~/ 6;
factory DurationsData.noAnimation() {
return DurationsData(
staggeredAnimation: Duration.zero,
staggeredAnimationPageTarget: Duration.zero,
);
}
}

View file

@ -7,7 +7,7 @@ class AIcons {
static const IconData video = Icons.movie_outlined;
static const IconData vector = Icons.code_outlined;
static const IconData a11y = Icons.accessibility_new_outlined;
static const IconData accessibility = Icons.accessibility_new_outlined;
static const IconData android = Icons.android;
static const IconData broken = Icons.broken_image_outlined;
static const IconData checked = Icons.done_outlined;

View file

@ -10,6 +10,7 @@ import 'package:aves/theme/durations.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/theme/themes.dart';
import 'package:aves/utils/debouncer.dart';
import 'package:aves/widgets/common/behaviour/accessibility_mixin.dart';
import 'package:aves/widgets/common/behaviour/route_tracker.dart';
import 'package:aves/widgets/common/behaviour/routes.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
@ -23,6 +24,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:overlay_support/overlay_support.dart';
import 'package:provider/provider.dart';
import 'package:tuple/tuple.dart';
class AvesApp extends StatefulWidget {
const AvesApp({Key? key}) : super(key: key);
@ -31,7 +33,7 @@ class AvesApp extends StatefulWidget {
_AvesAppState createState() => _AvesAppState();
}
class _AvesAppState extends State<AvesApp> {
class _AvesAppState extends State<AvesApp> with AccessibilityMixin {
final ValueNotifier<AppMode> appModeNotifier = ValueNotifier(AppMode.main);
late Future<void> _appSetup;
final _mediaStoreSource = MediaStoreSource();
@ -68,24 +70,39 @@ class _AvesAppState extends State<AvesApp> {
value: appModeNotifier,
child: Provider<CollectionSource>.value(
value: _mediaStoreSource,
child: HighlightInfoProvider(
child: OverlaySupport(
child: FutureBuilder<void>(
future: _appSetup,
builder: (context, snapshot) {
final initialized = !snapshot.hasError && snapshot.connectionState == ConnectionState.done;
final home = initialized
? getFirstPage()
: Scaffold(
body: snapshot.hasError ? _buildError(snapshot.error!) : const SizedBox(),
);
return Selector<Settings, Locale?>(
selector: (context, s) => s.locale,
builder: (context, settingsLocale, child) {
child: DurationsProvider(
child: HighlightInfoProvider(
child: OverlaySupport(
child: FutureBuilder<void>(
future: _appSetup,
builder: (context, snapshot) {
final initialized = !snapshot.hasError && snapshot.connectionState == ConnectionState.done;
final home = initialized
? getFirstPage()
: Scaffold(
body: snapshot.hasError ? _buildError(snapshot.error!) : const SizedBox(),
);
return Selector<Settings, Tuple2<Locale?, bool>>(
selector: (context, s) => Tuple2(s.locale, s.initialized ? areAnimationsEnabled() : true),
builder: (context, s, child) {
final settingsLocale = s.item1;
final areAnimationsEnabled = s.item2;
return MaterialApp(
navigatorKey: _navigatorKey,
home: home,
navigatorObservers: _navigatorObservers,
builder: (context, child) {
if (!areAnimationsEnabled) {
child = Theme(
data: Theme.of(context).copyWith(
// strip page transitions used by `MaterialPageRoute`
pageTransitionsTheme: DirectPageTransitionsTheme(),
),
child: child!,
);
}
return child!;
},
onGenerateTitle: (context) => context.l10n.appName,
darkTheme: Themes.darkTheme,
themeMode: ThemeMode.dark,
@ -97,8 +114,10 @@ class _AvesAppState extends State<AvesApp> {
// checkerboardRasterCacheImages: true,
// checkerboardOffscreenLayers: true,
);
});
},
},
);
},
),
),
),
),

View file

@ -86,7 +86,8 @@ class _CollectionGridContent extends StatelessWidget {
final columnCount = c.item2;
final tileSpacing = c.item3;
// do not listen for animation delay change
final tileAnimationDelay = context.read<TileExtentController>().getTileAnimationDelay(Durations.staggeredAnimationPageTarget);
final target = context.read<DurationsData>().staggeredAnimationPageTarget;
final tileAnimationDelay = context.read<TileExtentController>().getTileAnimationDelay(target);
return GridTheme(
extent: tileExtent,
child: SectionedEntryListLayoutProvider(

View file

@ -173,7 +173,8 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
),
),
));
await Future.delayed(Durations.staggeredAnimationPageTarget);
final delayDuration = context.read<DurationsData>().staggeredAnimationPageTarget;
await Future.delayed(delayDuration);
}
await Future.delayed(Durations.highlightScrollInitDelay);
final newUris = movedOps.map((v) => v.newFields['uri'] as String?).toSet();

View file

@ -3,7 +3,7 @@ import 'dart:math';
import 'package:aves/model/settings/enums.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/services/a11y_service.dart';
import 'package:aves/services/accessibility_service.dart';
import 'package:aves/theme/durations.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';

View file

@ -0,0 +1,17 @@
import 'dart:ui';
import 'package:aves/model/settings/enums.dart';
import 'package:aves/model/settings/settings.dart';
mixin AccessibilityMixin {
bool areAnimationsEnabled() {
switch (settings.accessibilityAnimations) {
case AccessibilityAnimations.system:
return !window.accessibilityFeatures.disableAnimations;
case AccessibilityAnimations.disabled:
return false;
case AccessibilityAnimations.enabled:
return true;
}
}
}

View file

@ -1,5 +1,17 @@
import 'package:flutter/material.dart';
class DirectPageTransitionsTheme extends PageTransitionsTheme {
@override
Widget buildTransitions<T>(
PageRoute<T> route,
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) =>
child;
}
class DirectMaterialPageRoute<T> extends PageRouteBuilder<T> {
DirectMaterialPageRoute({
RouteSettings? settings,

View file

@ -108,7 +108,7 @@ abstract class SectionedListLayoutProvider<T> extends StatelessWidget {
) {
if (sectionChildIndex == 0) {
final header = headerExtent > 0 ? buildHeader(context, sectionKey, headerExtent) : const SizedBox.shrink();
return animate ? _buildAnimation(sectionGridIndex, header) : header;
return animate ? _buildAnimation(context, sectionGridIndex, header) : header;
}
sectionChildIndex--;
@ -122,7 +122,7 @@ abstract class SectionedListLayoutProvider<T> extends StatelessWidget {
final item = RepaintBoundary(
child: tileBuilder(section[i]),
);
children.add(animate ? _buildAnimation(itemGridIndex, item) : item);
children.add(animate ? _buildAnimation(context, itemGridIndex, item) : item);
}
return _GridRow(
width: tileWidth,
@ -132,11 +132,12 @@ abstract class SectionedListLayoutProvider<T> extends StatelessWidget {
);
}
Widget _buildAnimation(int index, Widget child) {
Widget _buildAnimation(BuildContext context, int index, Widget child) {
final durations = context.watch<DurationsData>();
return AnimationConfiguration.staggeredGrid(
position: index,
columnCount: columnCount,
duration: Durations.staggeredAnimation,
duration: durations.staggeredAnimation,
delay: tileAnimationDelay,
child: SlideAnimation(
verticalOffset: 50.0,

View file

@ -245,7 +245,8 @@ class _FilterGridContent<T extends CollectionFilter> extends StatelessWidget {
final columnCount = c.item2;
final tileSpacing = c.item3;
// do not listen for animation delay change
final tileAnimationDelay = context.read<TileExtentController>().getTileAnimationDelay(Durations.staggeredAnimationPageTarget);
final target = context.read<DurationsData>().staggeredAnimationPageTarget;
final tileAnimationDelay = context.read<TileExtentController>().getTileAnimationDelay(target);
return Selector<MediaQueryData, double>(
selector: (context, mq) => mq.textScaleFactor,
builder: (context, textScaleFactor, child) {

View file

@ -2,7 +2,8 @@ import 'package:aves/theme/icons.dart';
import 'package:aves/utils/color_utils.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
import 'package:aves/widgets/settings/a11y/time_to_take_action.dart';
import 'package:aves/widgets/settings/accessibility/remove_animations.dart';
import 'package:aves/widgets/settings/accessibility/time_to_take_action.dart';
import 'package:aves/widgets/settings/common/tile_leading.dart';
import 'package:flutter/material.dart';
@ -18,13 +19,14 @@ class AccessibilitySection extends StatelessWidget {
Widget build(BuildContext context) {
return AvesExpansionTile(
leading: SettingsTileLeading(
icon: AIcons.a11y,
icon: AIcons.accessibility,
color: stringToColor('Accessibility'),
),
title: context.l10n.settingsSectionAccessibility,
expandedNotifier: expandedNotifier,
showHighlight: false,
children: const [
RemoveAnimationsTile(),
TimeToTakeActionTile(),
],
);

View file

@ -0,0 +1,38 @@
import 'package:aves/model/settings/accessibility_animations.dart';
import 'package:aves/model/settings/enums.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/dialogs/aves_selection_dialog.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:provider/provider.dart';
class RemoveAnimationsTile extends StatelessWidget {
const RemoveAnimationsTile({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final currentAnimations = context.select<Settings, AccessibilityAnimations>((s) => s.accessibilityAnimations);
return ListTile(
title: Text(context.l10n.settingsRemoveAnimationsTile),
subtitle: Text(currentAnimations.getName(context)),
onTap: () async {
final value = await showDialog<AccessibilityAnimations>(
context: context,
builder: (context) => AvesSelectionDialog<AccessibilityAnimations>(
initialValue: currentAnimations,
options: Map.fromEntries(AccessibilityAnimations.values.map((v) => MapEntry(v, v.getName(context)))),
title: context.l10n.settingsRemoveAnimationsTitle,
),
);
// wait for the dialog to hide as applying the change may block the UI
await Future.delayed(Durations.dialogTransitionAnimation * timeDilation);
if (value != null) {
settings.accessibilityAnimations = value;
}
},
);
}
}

View file

@ -1,7 +1,7 @@
import 'package:aves/model/settings/a11y_timeout.dart';
import 'package:aves/model/settings/accessibility_timeout.dart';
import 'package:aves/model/settings/enums.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/services/a11y_service.dart';
import 'package:aves/services/accessibility_service.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/dialogs/aves_selection_dialog.dart';
import 'package:flutter/material.dart';

View file

@ -11,7 +11,7 @@ import 'package:aves/widgets/common/action_mixins/feedback.dart';
import 'package:aves/widgets/common/basic/menu.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
import 'package:aves/widgets/settings/a11y/a11y.dart';
import 'package:aves/widgets/settings/accessibility/accessibility.dart';
import 'package:aves/widgets/settings/language/language.dart';
import 'package:aves/widgets/settings/navigation/navigation.dart';
import 'package:aves/widgets/settings/privacy/privacy.dart';
@ -22,6 +22,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
class SettingsPage extends StatefulWidget {
static const routeName = '/settings';
@ -38,6 +39,7 @@ class _SettingsPageState extends State<SettingsPage> with FeedbackMixin {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final durations = context.watch<DurationsData>();
return MediaQueryDataProvider(
child: Scaffold(
appBar: AppBar(
@ -78,8 +80,8 @@ class _SettingsPageState extends State<SettingsPage> with FeedbackMixin {
child: ListView(
padding: const EdgeInsets.all(8),
children: AnimationConfiguration.toStaggeredList(
duration: Durations.staggeredAnimation,
delay: Durations.staggeredAnimationDelay,
duration: durations.staggeredAnimation,
delay: durations.staggeredAnimationDelay * timeDilation,
childAnimationBuilder: (child) => SlideAnimation(
verticalOffset: 50.0,
child: FadeInAnimation(

View file

@ -233,7 +233,8 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
),
(route) => false,
));
await Future.delayed(Durations.staggeredAnimationPageTarget + Durations.highlightScrollInitDelay);
final delayDuration = context.read<DurationsData>().staggeredAnimationPageTarget;
await Future.delayed(delayDuration + Durations.highlightScrollInitDelay);
final newUris = movedOps.map((v) => v.newFields['uri'] as String?).toSet();
final targetEntry = targetCollection.sortedEntries.firstWhereOrNull((entry) => newUris.contains(entry.uri));
if (targetEntry != null) {

View file

@ -15,7 +15,9 @@ import 'package:aves/widgets/viewer/info/metadata/metadata_dir_tile.dart';
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:provider/provider.dart';
class MetadataSectionSliver extends StatefulWidget {
final AvesEntry entry;
@ -90,10 +92,11 @@ class _MetadataSectionSliverState extends State<MetadataSectionSliver> {
if (metadata.isEmpty) {
content = const SizedBox.shrink();
} else {
final durations = context.watch<DurationsData>();
content = Column(
children: AnimationConfiguration.toStaggeredList(
duration: Durations.staggeredAnimation,
delay: Durations.staggeredAnimationDelay,
duration: durations.staggeredAnimation,
delay: durations.staggeredAnimationDelay * timeDilation,
childAnimationBuilder: (child) => SlideAnimation(
verticalOffset: 50.0,
child: FadeInAnimation(

View file

@ -116,7 +116,8 @@ class VideoActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
),
(route) => false,
));
await Future.delayed(Durations.staggeredAnimationPageTarget + Durations.highlightScrollInitDelay);
final delayDuration = context.read<DurationsData>().staggeredAnimationPageTarget;
await Future.delayed(delayDuration + Durations.highlightScrollInitDelay);
final newUri = newFields['uri'] as String?;
final targetEntry = targetCollection.sortedEntries.firstWhereOrNull((entry) => entry.uri == newUri);
if (targetEntry != null) {

View file

@ -7,6 +7,7 @@ import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
import 'package:aves/widgets/home_page.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
@ -44,11 +45,12 @@ class _WelcomePageState extends State<WelcomePage> {
builder: (context, snapshot) {
if (snapshot.hasError || snapshot.connectionState != ConnectionState.done) return const SizedBox.shrink();
final terms = snapshot.data!;
final durations = context.watch<DurationsData>();
return Column(
mainAxisSize: MainAxisSize.min,
children: _toStaggeredList(
duration: Durations.staggeredAnimation,
delay: Durations.staggeredAnimationDelay,
duration: durations.staggeredAnimation,
delay: durations.staggeredAnimationDelay * timeDilation,
childAnimationBuilder: (child) => SlideAnimation(
verticalOffset: 50.0,
child: FadeInAnimation(