durations cleanup

This commit is contained in:
Thibault Deckers 2020-06-11 11:34:09 +09:00
parent ac1dc99cba
commit 3fe1d955d6
21 changed files with 99 additions and 57 deletions

View file

@ -15,4 +15,3 @@ __We collect anonymous data to improve the app.__ We use Google Firebase for Ana
## Links
[Sources](https://github.com/deckerst/aves)
''';

View file

@ -19,9 +19,6 @@ class Constants {
],
);
// ref _PopupMenuRoute._kMenuDuration
static const popupMenuTransitionDuration = Duration(milliseconds: 300);
static const svgBackground = Colors.white;
static const svgColorFilter = ColorFilter.mode(svgBackground, BlendMode.dstOver);

35
lib/utils/durations.dart Normal file
View file

@ -0,0 +1,35 @@
import 'package:flutter/scheduler.dart';
class Durations {
// common animations
static const iconAnimation = Duration(milliseconds: 300);
static const opToastAnimation = Duration(milliseconds: 600);
static const sweeperOpacityAnimation = Duration(milliseconds: 150);
static const sweepingAnimation = Duration(milliseconds: 650);
static const popupMenuAnimation = Duration(milliseconds: 300); // ref _PopupMenuRoute._kMenuDuration
static const staggeredAnimation = Duration(milliseconds: 375);
// collection animations
static const appBarTitleAnimation = Duration(milliseconds: 300);
static const filterBarRemovalAnimation = Duration(milliseconds: 200);
static const collectionOpOverlayAnimation = Duration(milliseconds: 300);
static const collectionScalingBackgroundAnimation = Duration(milliseconds: 200);
static const sectionHeaderAnimation = Duration(milliseconds: 200);
static const thumbnailTransition = Duration(milliseconds: 200);
static const thumbnailOverlayAnimation = Duration(milliseconds: 200);
// search animations
static const filterRowExpandAnimation = Duration(milliseconds: 300);
// fullscreen animations
static const fullscreenPageAnimation = Duration(milliseconds: 300);
static const fullscreenOverlayAnimation = Duration(milliseconds: 200);
// delays & refresh intervals
static const opToastDisplay = Duration(seconds: 2);
static const collectionScrollMonitoringTimerDelay = Duration(milliseconds: 100);
static const collectionScalingCompleteNotificationDelay = Duration(milliseconds: 300);
static const appBarProgressTimerInterval = Duration(seconds: 1);
static const videoProgressTimerInterval = Duration(milliseconds: 300);
static var staggeredAnimationDelay = Durations.staggeredAnimation ~/ 6 * timeDilation;
}

View file

@ -1,4 +1,5 @@
import 'package:aves/utils/constants.dart';
import 'package:aves/utils/durations.dart';
import 'package:aves/widgets/common/icons.dart';
import 'package:aves/widgets/common/menu_row.dart';
import 'package:collection/collection.dart';
@ -48,7 +49,8 @@ class _LicensesState extends State<Licenses> {
final child = LicenseRow(_packages[index]);
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 375),
duration: Durations.staggeredAnimation,
delay: Durations.staggeredAnimationDelay,
child: SlideAnimation(
verticalOffset: 50.0,
child: FadeInAnimation(

View file

@ -5,7 +5,7 @@ import 'package:aves/model/collection_lens.dart';
import 'package:aves/model/collection_source.dart';
import 'package:aves/model/image_entry.dart';
import 'package:aves/model/settings.dart';
import 'package:aves/utils/constants.dart';
import 'package:aves/utils/durations.dart';
import 'package:aves/widgets/album/filter_bar.dart';
import 'package:aves/widgets/album/search/search_delegate.dart';
import 'package:aves/widgets/common/action_delegates/selection_action_delegate.dart';
@ -49,7 +49,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
collection: collection,
);
_browseToSelectAnimation = AnimationController(
duration: const Duration(milliseconds: 300),
duration: Durations.iconAnimation,
vsync: this,
);
_registerWidget(widget);
@ -142,7 +142,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
valueListenable: collection.source.stateNotifier,
builder: (context, sourceState, child) {
return AnimatedSwitcher(
duration: Duration(milliseconds: (300 * timeDilation).toInt()),
duration: Durations.appBarTitleAnimation,
transitionBuilder: (child, animation) => FadeTransition(
opacity: animation,
child: SizeTransition(
@ -297,7 +297,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
void _onCollectionActionSelected(CollectionAction action) async {
// wait for the popup menu to hide before proceeding with the action
await Future.delayed(Constants.popupMenuTransitionDuration);
await Future.delayed(Durations.popupMenuAnimation * timeDilation);
switch (action) {
case CollectionAction.copy:
case CollectionAction.move:
@ -384,7 +384,7 @@ class _SourceStateSubtitleState extends State<SourceStateSubtitle> {
@override
void initState() {
super.initState();
_progressTimer = Timer.periodic(const Duration(milliseconds: 1000), (timer) => setState(() {}));
_progressTimer = Timer.periodic(Durations.appBarProgressTimerInterval, (_) => setState(() {}));
}
@override

View file

@ -1,4 +1,5 @@
import 'package:aves/model/filters/filters.dart';
import 'package:aves/utils/durations.dart';
import 'package:aves/widgets/common/aves_filter_chip.dart';
import 'package:flutter/material.dart';
@ -56,7 +57,7 @@ class _FilterBarState extends State<FilterBar> {
),
)
: (context, animation) => _buildChip(filter),
duration: animate ? const Duration(milliseconds: 200) : Duration.zero,
duration: animate ? Durations.filterBarRemovalAnimation : Duration.zero,
);
});
added.forEach((filter) {

View file

@ -4,12 +4,12 @@ import 'package:aves/model/collection_lens.dart';
import 'package:aves/model/collection_source.dart';
import 'package:aves/utils/android_file_utils.dart';
import 'package:aves/utils/constants.dart';
import 'package:aves/utils/durations.dart';
import 'package:aves/widgets/album/grid/header_album.dart';
import 'package:aves/widgets/album/grid/header_date.dart';
import 'package:aves/widgets/common/icons.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';
import 'package:provider/provider.dart';
class SectionHeader extends StatelessWidget {
@ -190,11 +190,11 @@ class SectionSelectableLeading extends StatelessWidget {
final sectionEntries = collection.sections[sectionKey];
final selected = collection.isSelected(sectionEntries);
final child = TooltipTheme(
key: ValueKey(selected),
data: TooltipTheme.of(context).copyWith(
preferBelow: false,
),
child: IconButton(
key: ValueKey(selected),
iconSize: 26,
padding: const EdgeInsets.only(top: 1),
alignment: Alignment.topLeft,
@ -214,7 +214,7 @@ class SectionSelectableLeading extends StatelessWidget {
),
);
return AnimatedSwitcher(
duration: Duration(milliseconds: (200 * timeDilation).toInt()),
duration: Durations.sectionHeaderAnimation,
switchInCurve: Curves.easeOutBack,
switchOutCurve: Curves.easeOutBack,
transitionBuilder: (child, animation) => ScaleTransition(
@ -227,7 +227,7 @@ class SectionSelectableLeading extends StatelessWidget {
)
: browsingBuilder?.call(context) ?? const SizedBox(height: leadingDimension);
return AnimatedSwitcher(
duration: Duration(milliseconds: (200 * timeDilation).toInt()),
duration: Durations.sectionHeaderAnimation,
switchInCurve: Curves.easeInOut,
switchOutCurve: Curves.easeInOut,
transitionBuilder: (child, animation) {

View file

@ -2,6 +2,7 @@ import 'dart:math';
import 'dart:ui' as ui;
import 'package:aves/model/image_entry.dart';
import 'package:aves/utils/durations.dart';
import 'package:aves/widgets/album/grid/list_section_layout.dart';
import 'package:aves/widgets/album/grid/list_sliver.dart';
import 'package:aves/widgets/album/grid/tile_extent_manager.dart';
@ -118,7 +119,7 @@ class _GridScaleGestureDetectorState extends State<GridScaleGestureDetector> {
_scrollToEntry(entry);
// warning: posting `onScaled` in the next frame with `addPostFrameCallback`
// would trigger only when the scrollable offset actually changes
Future.delayed(const Duration(milliseconds: 300)).then((_) => widget.onScaled?.call(entry));
Future.delayed(Durations.collectionScalingCompleteNotificationDelay).then((_) => widget.onScaled?.call(entry));
_applyingScale = false;
});
}
@ -200,7 +201,7 @@ class _ScaleOverlayState extends State<ScaleOverlay> {
],
),
),
duration: const Duration(milliseconds: 200),
duration: Durations.collectionScalingBackgroundAnimation,
child: ValueListenableBuilder<double>(
valueListenable: widget.scaledExtentNotifier,
builder: (context, extent, child) {

View file

@ -1,5 +1,6 @@
import 'package:aves/model/filters/filters.dart';
import 'package:aves/utils/constants.dart';
import 'package:aves/utils/durations.dart';
import 'package:aves/widgets/common/aves_filter_chip.dart';
import 'package:aves/widgets/common/icons.dart';
import 'package:flutter/material.dart';
@ -95,7 +96,7 @@ class ExpandableFilterRow extends StatelessWidget {
children: [
titleRow,
AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
duration: Durations.filterRowExpandAnimation,
child: filterChips,
layoutBuilder: (currentChild, previousChildren) => Stack(
children: [

View file

@ -2,10 +2,10 @@ import 'dart:math';
import 'package:aves/model/collection_lens.dart';
import 'package:aves/model/image_entry.dart';
import 'package:aves/utils/durations.dart';
import 'package:aves/widgets/common/fx/sweeper.dart';
import 'package:aves/widgets/common/icons.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:provider/provider.dart';
class ThumbnailEntryOverlay extends StatelessWidget {
@ -57,7 +57,7 @@ class ThumbnailSelectionOverlay extends StatelessWidget {
@override
Widget build(BuildContext context) {
final duration = Duration(milliseconds: (200 * timeDilation).toInt());
final duration = Durations.thumbnailOverlayAnimation;
final fontSize = min(14.0, (extent / 8)).roundToDouble();
final iconSize = fontSize * 2;
final collection = Provider.of<CollectionLens>(context);

View file

@ -1,6 +1,7 @@
import 'dart:math';
import 'package:aves/model/image_entry.dart';
import 'package:aves/utils/durations.dart';
import 'package:aves/widgets/common/image_providers/thumbnail_provider.dart';
import 'package:aves/widgets/common/image_providers/uri_image_provider.dart';
import 'package:aves/widgets/common/transition_image.dart';
@ -93,7 +94,7 @@ class _ThumbnailRasterImageState extends State<ThumbnailRasterImage> {
frameBuilder: (context, child, frame, wasSynchronouslyLoaded) {
if (wasSynchronouslyLoaded) return child;
return AnimatedSwitcher(
duration: const Duration(milliseconds: 200),
duration: Durations.thumbnailTransition,
transitionBuilder: (child, animation) {
var shouldFade = true;
if (child is Image && child.image == _fastThumbnailProvider) {

View file

@ -4,6 +4,7 @@ import 'package:aves/model/collection_lens.dart';
import 'package:aves/model/filters/favourite.dart';
import 'package:aves/model/filters/mime.dart';
import 'package:aves/model/mime_types.dart';
import 'package:aves/utils/durations.dart';
import 'package:aves/widgets/album/app_bar.dart';
import 'package:aves/widgets/album/empty.dart';
import 'package:aves/widgets/album/grid/list_section_layout.dart';
@ -220,7 +221,7 @@ class _CollectionScrollViewState extends State<CollectionScrollView> {
void _onScrollChange() {
widget.isScrollingNotifier.value = true;
_stopScrollMonitoringTimer();
_scrollMonitoringTimer = Timer(const Duration(milliseconds: 100), () {
_scrollMonitoringTimer = Timer(Durations.collectionScrollMonitoringTimerDelay, () {
widget.isScrollingNotifier.value = false;
});
}

View file

@ -53,14 +53,14 @@ class _CreateAlbumDialogState extends State<CreateAlbumDialog> {
isExpanded: true,
items: allVolumes
.map((volume) => DropdownMenuItem(
value: volume,
child: Text(
volume.description,
softWrap: false,
overflow: TextOverflow.fade,
maxLines: 1,
),
))
value: volume,
child: Text(
volume.description,
softWrap: false,
overflow: TextOverflow.fade,
maxLines: 1,
),
))
.toList(),
value: selectedVolume,
onChanged: (volume) => setState(() => selectedVolume = volume),

View file

@ -1,5 +1,7 @@
import 'package:aves/utils/durations.dart';
import 'package:flushbar/flushbar.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
mixin FeedbackMixin {
void showFeedback(BuildContext context, String message) {
@ -9,9 +11,9 @@ mixin FeedbackMixin {
borderRadius: 8,
borderColor: Colors.white30,
borderWidth: 0.5,
duration: const Duration(seconds: 2),
duration: Durations.opToastDisplay * timeDilation,
flushbarPosition: FlushbarPosition.TOP,
animationDuration: const Duration(milliseconds: 600),
animationDuration: Durations.opToastAnimation,
).show(context);
}
}
}

View file

@ -7,6 +7,7 @@ import 'package:aves/model/image_entry.dart';
import 'package:aves/model/metadata_db.dart';
import 'package:aves/services/android_app_service.dart';
import 'package:aves/services/image_file_service.dart';
import 'package:aves/utils/durations.dart';
import 'package:aves/widgets/album/app_bar.dart';
import 'package:aves/widgets/album/empty.dart';
import 'package:aves/widgets/common/action_delegates/create_album_dialog.dart';
@ -18,6 +19,7 @@ import 'package:aves/widgets/filter_grid_page.dart';
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/widgets.dart';
import 'package:intl/intl.dart';
import 'package:percent_indicator/circular_percent_indicator.dart';
@ -221,8 +223,6 @@ class SelectionActionDelegate with FeedbackMixin, PermissionAwareMixin {
OverlayEntry _opReportOverlayEntry;
static const _overlayAnimationDuration = Duration(milliseconds: 300);
void _showOpReport<T extends ImageOpEvent>({
@required BuildContext context,
@required List<ImageEntry> selection,
@ -260,7 +260,7 @@ class SelectionActionDelegate with FeedbackMixin, PermissionAwareMixin {
);
}
return AnimatedSwitcher(
duration: _overlayAnimationDuration,
duration: Durations.collectionOpOverlayAnimation,
child: child,
);
});
@ -270,7 +270,7 @@ class SelectionActionDelegate with FeedbackMixin, PermissionAwareMixin {
}
Future<void> _hideOpReportOverlay() async {
await Future.delayed(_overlayAnimationDuration);
await Future.delayed(Durations.collectionOpOverlayAnimation * timeDilation);
_opReportOverlayEntry.remove();
_opReportOverlayEntry = null;
}

View file

@ -1,5 +1,6 @@
import 'dart:math';
import 'package:aves/utils/durations.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
@ -34,14 +35,11 @@ class _SweeperState extends State<Sweeper> with SingleTickerProviderStateMixin {
bool get isToggled => widget.toggledNotifier.value;
static const opacityAnimationDurationMillis = 150;
static const sweepingDurationMillis = 650;
@override
void initState() {
super.initState();
_angleAnimationController = AnimationController(
duration: const Duration(milliseconds: sweepingDurationMillis),
duration: Durations.sweepingAnimation,
vsync: this,
);
final startAngle = widget.startAngle;
@ -86,7 +84,7 @@ class _SweeperState extends State<Sweeper> with SingleTickerProviderStateMixin {
return IgnorePointer(
child: AnimatedOpacity(
opacity: isToggled && (_isAppearing || _angleAnimationController.status == AnimationStatus.forward) ? 1 : 0,
duration: const Duration(milliseconds: opacityAnimationDurationMillis),
duration: Durations.sweeperOpacityAnimation,
child: ValueListenableBuilder<double>(
valueListenable: _angleAnimationController,
builder: (context, value, child) {
@ -113,7 +111,7 @@ class _SweeperState extends State<Sweeper> with SingleTickerProviderStateMixin {
if (isToggled) {
_isAppearing = true;
setState(() {});
await Future.delayed(Duration(milliseconds: (opacityAnimationDurationMillis * timeDilation).toInt()));
await Future.delayed(Durations.sweeperOpacityAnimation * timeDilation);
_isAppearing = false;
if (mounted) {
_angleAnimationController.reset();

View file

@ -7,6 +7,7 @@ import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/image_entry.dart';
import 'package:aves/model/settings.dart';
import 'package:aves/utils/android_file_utils.dart';
import 'package:aves/utils/durations.dart';
import 'package:aves/widgets/album/collection_page.dart';
import 'package:aves/widgets/album/thumbnail/raster.dart';
import 'package:aves/widgets/album/thumbnail/vector.dart';
@ -115,7 +116,8 @@ class FilterGridPage extends StatelessWidget {
return AnimationConfiguration.staggeredGrid(
position: i,
columnCount: columnCount,
duration: const Duration(milliseconds: 375),
duration: Durations.staggeredAnimation,
delay: Durations.staggeredAnimationDelay,
child: SlideAnimation(
verticalOffset: 50.0,
child: FadeInAnimation(

View file

@ -5,6 +5,7 @@ import 'package:aves/model/collection_lens.dart';
import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/image_entry.dart';
import 'package:aves/utils/change_notifier.dart';
import 'package:aves/utils/durations.dart';
import 'package:aves/widgets/album/collection_page.dart';
import 'package:aves/widgets/common/action_delegates/entry_action_delegate.dart';
import 'package:aves/widgets/common/image_providers/thumbnail_provider.dart';
@ -73,7 +74,7 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
_horizontalPager = PageController(initialPage: _currentHorizontalPage);
_verticalPager = PageController(initialPage: _currentVerticalPage.value)..addListener(_onVerticalPageControllerChange);
_overlayAnimationController = AnimationController(
duration: const Duration(milliseconds: 200),
duration: Durations.fullscreenOverlayAnimation,
vsync: this,
);
_topOverlayScale = CurvedAnimation(
@ -97,8 +98,8 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
);
WidgetsBinding.instance.addObserver(this);
_initVideoController();
_initOverlay();
_registerWidget(widget);
WidgetsBinding.instance.addPostFrameCallback((_) => _initOverlay());
}
@override
@ -283,7 +284,7 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
Future<void> _goToVerticalPage(int page) {
return _verticalPager.animateToPage(
page,
duration: Duration(milliseconds: (300 * timeDilation).toInt()),
duration: Durations.fullscreenPageAnimation,
curve: Curves.easeInOut,
);
}
@ -321,18 +322,18 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
_showSystemUI();
}
// system UI
// system UI
static void _showSystemUI() => SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values);
static void _hideSystemUI() => SystemChrome.setEnabledSystemUIOverlays([]);
// overlay
// overlay
Future<void> _initOverlay() async {
// wait for MaterialPageRoute.transitionDuration
// to show overlay after hero animation is complete
await Future.delayed(Duration(milliseconds: (300 * timeDilation).toInt()));
await Future.delayed(ModalRoute.of(context).transitionDuration * timeDilation);
await _onOverlayVisibleChange();
}

View file

@ -1,6 +1,7 @@
import 'package:aves/model/collection_lens.dart';
import 'package:aves/model/filters/filters.dart';
import 'package:aves/model/image_entry.dart';
import 'package:aves/utils/durations.dart';
import 'package:aves/widgets/common/aves_filter_chip.dart';
import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart';
import 'package:aves/widgets/common/icons.dart';
@ -8,7 +9,6 @@ import 'package:aves/widgets/fullscreen/info/basic_section.dart';
import 'package:aves/widgets/fullscreen/info/location_section.dart';
import 'package:aves/widgets/fullscreen/info/metadata_section.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:provider/provider.dart';
import 'package:tuple/tuple.dart';
@ -177,7 +177,7 @@ class InfoPageState extends State<InfoPage> {
BackUpNotification().dispatch(context);
_scrollController.animateTo(
0,
duration: Duration(milliseconds: (300 * timeDilation).toInt()),
duration: Durations.fullscreenPageAnimation,
curve: Curves.easeInOut,
);
}

View file

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:aves/model/image_entry.dart';
import 'package:aves/services/android_app_service.dart';
import 'package:aves/utils/durations.dart';
import 'package:aves/utils/time_utils.dart';
import 'package:aves/widgets/common/fx/blurred.dart';
import 'package:aves/widgets/common/icons.dart';
@ -61,7 +62,7 @@ class VideoControlOverlayState extends State<VideoControlOverlay> with SingleTic
void initState() {
super.initState();
_playPauseAnimation = AnimationController(
duration: const Duration(milliseconds: 300),
duration: Durations.iconAnimation,
vsync: this,
);
_registerWidget(widget);
@ -220,9 +221,7 @@ class VideoControlOverlayState extends State<VideoControlOverlay> with SingleTic
void _startTimer() {
if (controller.textureId == null) return;
_progressTimer?.cancel();
_progressTimer = Timer.periodic(const Duration(milliseconds: 300), (timer) {
controller.refreshVideoInfo();
});
_progressTimer = Timer.periodic(Durations.videoProgressTimerInterval, (_) => controller.refreshVideoInfo());
}
void _stopTimer() {

View file

@ -1,6 +1,7 @@
import 'package:aves/main.dart';
import 'package:aves/model/settings.dart';
import 'package:aves/model/terms.dart';
import 'package:aves/utils/durations.dart';
import 'package:aves/widgets/common/aves_logo.dart';
import 'package:aves/widgets/common/labeled_checkbox.dart';
import 'package:flutter/material.dart';
@ -28,7 +29,8 @@ class _WelcomePageState extends State<WelcomePage> {
child: Column(
mainAxisSize: MainAxisSize.min,
children: _toStaggeredList(
duration: const Duration(milliseconds: 375),
duration: Durations.staggeredAnimation,
delay: Durations.staggeredAnimationDelay,
childAnimationBuilder: (child) => SlideAnimation(
verticalOffset: 50.0,
child: FadeInAnimation(