diff --git a/CHANGELOG.md b/CHANGELOG.md index 502c4a057..6d427e653 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Added +- Collection / Albums / Countries / Tags: added label when dragging scrollbar thumb + ### Changed - Upgraded Flutter to beta v2.1.0-12.2.pre diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index e1b873657..a245d94a4 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -7,6 +7,12 @@ "@welcomeAnalyticsToggle": {}, "welcomeTermsToggle": "I agree to the terms and conditions", "@welcomeTermsToggle": {}, + "itemCount": "{count, plural, =1{1 item} other{{count} items}}", + "@itemCount": { + "placeholders": { + "count": {} + } + }, "applyButtonLabel": "APPLY", "@applyButtonLabel": {}, diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index c53f3385b..eca076e12 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -3,6 +3,7 @@ "welcomeMessage": "아베스 사용을 환영합니다", "welcomeAnalyticsToggle": "진단 데이터를 보내는 것에 동의합니다 (선택)", "welcomeTermsToggle": "이용약관에 동의합니다", + "itemCount": "{count, plural, other{{count}개}}", "applyButtonLabel": "확인", "deleteButtonLabel": "삭제", diff --git a/lib/theme/durations.dart b/lib/theme/durations.dart index f0c590209..55f5a0928 100644 --- a/lib/theme/durations.dart +++ b/lib/theme/durations.dart @@ -9,6 +9,7 @@ class Durations { static const dialogTransitionAnimation = Duration(milliseconds: 150); // ref `transitionDuration` in `showDialog()` static const staggeredAnimation = Duration(milliseconds: 375); + static const staggeredAnimationPageTarget = Duration(milliseconds: 900); static const dialogFieldReachAnimation = Duration(milliseconds: 300); static const appBarTitleAnimation = Duration(milliseconds: 300); diff --git a/lib/widgets/collection/collection_grid.dart b/lib/widgets/collection/collection_grid.dart index 2c0ebb4f7..fba492c3a 100644 --- a/lib/widgets/collection/collection_grid.dart +++ b/lib/widgets/collection/collection_grid.dart @@ -11,6 +11,7 @@ import 'package:aves/ref/mime_types.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/collection/app_bar.dart'; +import 'package:aves/widgets/collection/draggable_thumb_label.dart'; import 'package:aves/widgets/collection/grid/section_layout.dart'; import 'package:aves/widgets/collection/grid/selector.dart'; import 'package:aves/widgets/collection/grid/thumbnail.dart'; @@ -29,9 +30,9 @@ import 'package:aves/widgets/common/providers/tile_extent_controller_provider.da import 'package:aves/widgets/common/scaling.dart'; import 'package:aves/widgets/common/tile_extent_controller.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:provider/provider.dart'; +import 'package:tuple/tuple.dart'; class CollectionGrid extends StatefulWidget { final String settingsRouteKey; @@ -74,23 +75,34 @@ class _CollectionGridContent extends StatelessWidget { builder: (context, tileExtent, child) { return ThumbnailTheme( extent: tileExtent, - child: SectionedEntryListLayoutProvider( - collection: collection, - scrollableWidth: context.select((controller) => controller.viewportSize.width), - tileExtent: tileExtent, - columnCount: context.select((controller) => controller.getEffectiveColumnCountForExtent(tileExtent)), - tileBuilder: (entry) => InteractiveThumbnail( - key: ValueKey(entry.contentId), - collection: collection, - entry: entry, - tileExtent: tileExtent, - isScrollingNotifier: _isScrollingNotifier, - ), - child: _CollectionSectionedContent( - collection: collection, - isScrollingNotifier: _isScrollingNotifier, - scrollController: PrimaryScrollController.of(context), - ), + child: Selector>( + selector: (context, c) => Tuple2(c.viewportSize.width, c.columnCount), + builder: (context, c, child) { + final scrollableWidth = c.item1; + final columnCount = c.item2; + // do not listen for animation delay change + final controller = Provider.of(context, listen: false); + final tileAnimationDelay = controller.getTileAnimationDelay(Durations.staggeredAnimationPageTarget); + return SectionedEntryListLayoutProvider( + collection: collection, + scrollableWidth: scrollableWidth, + tileExtent: tileExtent, + columnCount: columnCount, + tileBuilder: (entry) => InteractiveThumbnail( + key: ValueKey(entry.contentId), + collection: collection, + entry: entry, + tileExtent: tileExtent, + isScrollingNotifier: _isScrollingNotifier, + ), + tileAnimationDelay: tileAnimationDelay, + child: _CollectionSectionedContent( + collection: collection, + isScrollingNotifier: _isScrollingNotifier, + scrollController: PrimaryScrollController.of(context), + ), + ); + }, ), ); }, @@ -266,10 +278,10 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> { @override Widget build(BuildContext context) { final scrollView = _buildScrollView(widget.appBar, widget.collection); - return _buildDraggableScrollView(scrollView); + return _buildDraggableScrollView(scrollView, widget.collection); } - Widget _buildDraggableScrollView(ScrollView scrollView) { + Widget _buildDraggableScrollView(ScrollView scrollView, CollectionLens collection) { return ValueListenableBuilder( valueListenable: widget.appBarHeightNotifier, builder: (context, appBarHeight, child) => Selector( @@ -287,6 +299,10 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> { top: appBarHeight, bottom: mqPaddingBottom, ), + labelTextBuilder: (offsetY) => CollectionDraggableThumbLabel( + collection: collection, + offsetY: offsetY, + ), child: scrollView, ), child: child, diff --git a/lib/widgets/collection/draggable_thumb_label.dart b/lib/widgets/collection/draggable_thumb_label.dart new file mode 100644 index 000000000..df13fb0a2 --- /dev/null +++ b/lib/widgets/collection/draggable_thumb_label.dart @@ -0,0 +1,60 @@ +import 'package:aves/model/entry.dart'; +import 'package:aves/model/source/collection_lens.dart'; +import 'package:aves/model/source/collection_source.dart'; +import 'package:aves/model/source/enums.dart'; +import 'package:aves/utils/file_utils.dart'; +import 'package:aves/widgets/common/grid/draggable_thumb_label.dart'; +import 'package:aves/widgets/common/grid/section_layout.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class CollectionDraggableThumbLabel extends StatelessWidget { + final CollectionLens collection; + final double offsetY; + + const CollectionDraggableThumbLabel({ + @required this.collection, + @required this.offsetY, + }); + + @override + Widget build(BuildContext context) { + return DraggableThumbLabel( + offsetY: offsetY, + lineBuilder: (context, entry) { + switch (collection.sortFactor) { + case EntrySortFactor.date: + switch (collection.groupFactor) { + case EntryGroupFactor.album: + return [ + DraggableThumbLabel.formatMonthThumbLabel(context, entry.bestDate), + if (_hasMultipleSections(context)) context.read().getUniqueAlbumName(context, entry.directory), + ]; + case EntryGroupFactor.month: + case EntryGroupFactor.none: + return [ + DraggableThumbLabel.formatMonthThumbLabel(context, entry.bestDate), + ]; + case EntryGroupFactor.day: + return [ + DraggableThumbLabel.formatDayThumbLabel(context, entry.bestDate), + ]; + } + break; + case EntrySortFactor.name: + return [ + if (_hasMultipleSections(context)) context.read().getUniqueAlbumName(context, entry.directory), + entry.bestTitle, + ]; + case EntrySortFactor.size: + return [ + formatFilesize(entry.sizeBytes, round: 0), + ]; + } + return []; + }, + ); + } + + bool _hasMultipleSections(BuildContext context) => context.read>().sections.length > 1; +} diff --git a/lib/widgets/collection/grid/section_layout.dart b/lib/widgets/collection/grid/section_layout.dart index c481e2ce1..33597cbd5 100644 --- a/lib/widgets/collection/grid/section_layout.dart +++ b/lib/widgets/collection/grid/section_layout.dart @@ -15,12 +15,14 @@ class SectionedEntryListLayoutProvider extends SectionedListLayoutProvider with TickerProviderStateMixin { final ValueNotifier _thumbOffsetNotifier = ValueNotifier(0), _viewOffsetNotifier = ValueNotifier(0); bool _isDragInProcess = false; + Offset _longPressLastGlobalPosition; AnimationController _thumbAnimationController; Animation _thumbAnimation; @@ -193,9 +195,19 @@ class _DraggableScrollbarState extends State with TickerProv ), RepaintBoundary( child: GestureDetector( - onVerticalDragStart: _onVerticalDragStart, - onVerticalDragUpdate: _onVerticalDragUpdate, - onVerticalDragEnd: _onVerticalDragEnd, + onLongPressStart: (details) { + _longPressLastGlobalPosition = details.globalPosition; + _onVerticalDragStart(); + }, + onLongPressMoveUpdate: (details) { + final dy = (details.globalPosition - _longPressLastGlobalPosition).dy; + _longPressLastGlobalPosition = details.globalPosition; + _onVerticalDragUpdate(dy); + }, + onLongPressEnd: (_) => _onVerticalDragEnd(), + onVerticalDragStart: (_) => _onVerticalDragStart(), + onVerticalDragUpdate: (details) => _onVerticalDragUpdate(details.delta.dy), + onVerticalDragEnd: (_) => _onVerticalDragEnd(), child: ValueListenableBuilder( valueListenable: _thumbOffsetNotifier, builder: (context, thumbOffset, child) => Container( @@ -244,17 +256,18 @@ class _DraggableScrollbarState extends State with TickerProv } } - void _onVerticalDragStart(DragStartDetails details) { + void _onVerticalDragStart() { _labelAnimationController.forward(); _fadeoutTimer?.cancel(); + _showThumb(); setState(() => _isDragInProcess = true); } - void _onVerticalDragUpdate(DragUpdateDetails details) { + void _onVerticalDragUpdate(double deltaY) { _showThumb(); if (_isDragInProcess) { // thumb offset - _thumbOffsetNotifier.value = (_thumbOffsetNotifier.value + details.delta.dy).clamp(thumbMinScrollExtent, thumbMaxScrollExtent); + _thumbOffsetNotifier.value = (_thumbOffsetNotifier.value + deltaY).clamp(thumbMinScrollExtent, thumbMaxScrollExtent); // scroll offset final min = controller.position.minScrollExtent; @@ -263,7 +276,7 @@ class _DraggableScrollbarState extends State with TickerProv } } - void _onVerticalDragEnd(DragEndDetails details) { + void _onVerticalDragEnd() { _scheduleFadeout(); setState(() => _isDragInProcess = false); } diff --git a/lib/widgets/common/grid/draggable_thumb_label.dart b/lib/widgets/common/grid/draggable_thumb_label.dart new file mode 100644 index 000000000..515039299 --- /dev/null +++ b/lib/widgets/common/grid/draggable_thumb_label.dart @@ -0,0 +1,67 @@ +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/common/grid/section_layout.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; + +class DraggableThumbLabel extends StatelessWidget { + final double offsetY; + final List Function(BuildContext context, T item) lineBuilder; + + const DraggableThumbLabel({ + @required this.offsetY, + @required this.lineBuilder, + }); + + @override + Widget build(BuildContext context) { + final sll = context.read>(); + final sectionLayout = sll.getSectionAt(offsetY); + if (sectionLayout == null) return null; + + final section = sll.sections[sectionLayout.sectionKey]; + final dy = offsetY - (sectionLayout.minOffset + sectionLayout.headerExtent); + final itemIndex = dy < 0 ? 0 : (dy ~/ (sll.tileExtent + sll.spacing)) * sll.columnCount; + final item = section[itemIndex]; + if (item == null) return SizedBox(); + + final lines = lineBuilder(context, item); + if (lines.isEmpty) return SizedBox(); + + return ConstrainedBox( + constraints: BoxConstraints(maxWidth: 140), + child: Padding( + padding: EdgeInsets.symmetric(vertical: 4, horizontal: 8), + child: lines.length > 1 + ? Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: lines.map(_buildText).toList(), + ) + : _buildText(lines.first), + ), + ); + } + + Widget _buildText(String text) => Text( + text, + style: TextStyle( + color: Colors.black, + ), + softWrap: false, + overflow: TextOverflow.fade, + maxLines: 1, + ); + + static String formatMonthThumbLabel(BuildContext context, DateTime date) { + final l10n = context.l10n; + if (date == null) return l10n.sectionUnknown; + return DateFormat.yMMM(l10n.localeName).format(date); + } + + static String formatDayThumbLabel(BuildContext context, DateTime date) { + final l10n = context.l10n; + if (date == null) return l10n.sectionUnknown; + return DateFormat.yMMMd(l10n.localeName).format(date); + } +} diff --git a/lib/widgets/common/grid/section_layout.dart b/lib/widgets/common/grid/section_layout.dart index b1a6f1657..329ec9c58 100644 --- a/lib/widgets/common/grid/section_layout.dart +++ b/lib/widgets/common/grid/section_layout.dart @@ -12,6 +12,7 @@ abstract class SectionedListLayoutProvider extends StatelessWidget { final int columnCount; final double spacing, tileExtent; final Widget Function(T item) tileBuilder; + final Duration tileAnimationDelay; final Widget child; const SectionedListLayoutProvider({ @@ -20,6 +21,7 @@ abstract class SectionedListLayoutProvider extends StatelessWidget { this.spacing = 0, @required this.tileExtent, @required this.tileBuilder, + this.tileAnimationDelay, @required this.child, }) : assert(scrollableWidth != 0); @@ -40,6 +42,7 @@ abstract class SectionedListLayoutProvider extends StatelessWidget { final _showHeaders = showHeaders; final _sections = sections; final sectionKeys = _sections.keys.toList(); + final animate = tileAnimationDelay > Duration.zero; final sectionLayouts = []; var currentIndex = 0, currentOffset = 0.0; @@ -76,6 +79,7 @@ abstract class SectionedListLayoutProvider extends StatelessWidget { listIndex - sectionFirstIndex, sectionKey, headerExtent, + animate, ), ), ); @@ -97,10 +101,11 @@ abstract class SectionedListLayoutProvider extends StatelessWidget { int sectionChildIndex, SectionKey sectionKey, double headerExtent, + bool animate, ) { if (sectionChildIndex == 0) { final header = headerExtent > 0 ? buildHeader(context, sectionKey, headerExtent) : SizedBox.shrink(); - return _buildAnimation(sectionGridIndex, header); + return animate ? _buildAnimation(sectionGridIndex, header) : header; } sectionChildIndex--; @@ -113,7 +118,7 @@ abstract class SectionedListLayoutProvider extends StatelessWidget { final itemGridIndex = sectionGridIndex + i - minItemIndex; final item = tileBuilder(section[i]); if (i != minItemIndex) children.add(SizedBox(width: spacing)); - children.add(_buildAnimation(itemGridIndex, item)); + children.add(animate ? _buildAnimation(itemGridIndex, item) : item); } return Row( mainAxisSize: MainAxisSize.min, @@ -126,7 +131,7 @@ abstract class SectionedListLayoutProvider extends StatelessWidget { position: index, columnCount: columnCount, duration: Durations.staggeredAnimation, - delay: Durations.staggeredAnimationDelay, + delay: tileAnimationDelay ?? Durations.staggeredAnimationDelay, child: SlideAnimation( verticalOffset: 50.0, child: FadeInAnimation( @@ -189,9 +194,11 @@ class SectionedListLayout { return Rect.fromLTWH(left, top, tileExtent, tileExtent); } + SectionLayout getSectionAt(double offsetY) => sectionLayouts.firstWhere((sl) => offsetY < sl.maxOffset, orElse: () => null); + T getItemAt(Offset position) { var dy = position.dy; - final sectionLayout = sectionLayouts.firstWhere((sl) => dy < sl.maxOffset, orElse: () => null); + final sectionLayout = getSectionAt(dy); if (sectionLayout == null) return null; final section = sections[sectionLayout.sectionKey]; diff --git a/lib/widgets/common/tile_extent_controller.dart b/lib/widgets/common/tile_extent_controller.dart index fa782e058..9500d1641 100644 --- a/lib/widgets/common/tile_extent_controller.dart +++ b/lib/widgets/common/tile_extent_controller.dart @@ -1,6 +1,7 @@ import 'dart:math'; import 'package:aves/model/settings/settings.dart'; +import 'package:flutter/scheduler.dart'; import 'package:flutter/widgets.dart'; class TileExtentController { @@ -45,7 +46,7 @@ class TileExtentController { ? oldUserPreferredExtent : currentExtent; - final columnCount = getEffectiveColumnCountForExtent(targetExtent); + final columnCount = _effectiveColumnCountForExtent(targetExtent); final newExtent = _extentForColumnCount(columnCount); if (userPreferredExtent > 0 || oldUserPreferredExtent == 0) { @@ -67,15 +68,24 @@ class TileExtentController { int _effectiveColumnCountMax() => _columnCountForExtent(extentMin).floor(); - double get effectiveExtentMin => _extentForColumnCount(_effectiveColumnCountMax()); - - double get effectiveExtentMax => _extentForColumnCount(_effectiveColumnCountMin()); - - int getEffectiveColumnCountForExtent(double extent) { + int _effectiveColumnCountForExtent(double extent) { if (extent > 0) { final columnCount = _columnCountForExtent(extent); return columnCount.clamp(_effectiveColumnCountMin(), _effectiveColumnCountMax()).round(); } return columnCountDefault; } + + double get effectiveExtentMin => _extentForColumnCount(_effectiveColumnCountMax()); + + double get effectiveExtentMax => _extentForColumnCount(_effectiveColumnCountMin()); + + int get columnCount => _effectiveColumnCountForExtent(extentNotifier.value); + + Duration getTileAnimationDelay(Duration pageTarget) { + final extent = extentNotifier.value; + final columnCount = ((viewportSize.width + spacing) / (extent + spacing)).round(); + final rowCount = (viewportSize.height + spacing) ~/ (extent + spacing); + return pageTarget ~/ (columnCount + rowCount) * timeDilation; + } } diff --git a/lib/widgets/filter_grids/album_pick.dart b/lib/widgets/filter_grids/album_pick.dart index f53813f83..88661bb57 100644 --- a/lib/widgets/filter_grids/album_pick.dart +++ b/lib/widgets/filter_grids/album_pick.dart @@ -62,6 +62,7 @@ class _AlbumPickPageState extends State { appBar: appBar, appBarHeight: AlbumPickAppBar.preferredHeight, filterSections: AlbumListPage.getAlbumEntries(context, source), + sortFactor: settings.albumSortFactor, showHeaders: settings.albumGroupFactor != AlbumChipGroupFactor.none, queryNotifier: _queryNotifier, applyQuery: (filters, query) { diff --git a/lib/widgets/filter_grids/albums_page.dart b/lib/widgets/filter_grids/albums_page.dart index 5b4df6a3e..328762240 100644 --- a/lib/widgets/filter_grids/albums_page.dart +++ b/lib/widgets/filter_grids/albums_page.dart @@ -34,6 +34,7 @@ class AlbumListPage extends StatelessWidget { builder: (context, snapshot) => FilterNavigationPage( source: source, title: context.l10n.albumPageTitle, + sortFactor: settings.albumSortFactor, groupable: true, showHeaders: settings.albumGroupFactor != AlbumChipGroupFactor.none, chipSetActionDelegate: AlbumChipSetActionDelegate(), diff --git a/lib/widgets/filter_grids/common/draggable_thumb_label.dart b/lib/widgets/filter_grids/common/draggable_thumb_label.dart new file mode 100644 index 000000000..ba1dab337 --- /dev/null +++ b/lib/widgets/filter_grids/common/draggable_thumb_label.dart @@ -0,0 +1,42 @@ +import 'package:aves/model/filters/filters.dart'; +import 'package:aves/model/source/collection_source.dart'; +import 'package:aves/model/source/enums.dart'; +import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/common/grid/draggable_thumb_label.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class FilterDraggableThumbLabel extends StatelessWidget { + final ChipSortFactor sortFactor; + final double offsetY; + + const FilterDraggableThumbLabel({ + @required this.sortFactor, + @required this.offsetY, + }); + + @override + Widget build(BuildContext context) { + return DraggableThumbLabel>( + offsetY: offsetY, + lineBuilder: (context, filterGridItem) { + switch (sortFactor) { + case ChipSortFactor.count: + return [ + context.l10n.itemCount(context.read().count(filterGridItem.filter)), + ]; + break; + case ChipSortFactor.date: + return [ + DraggableThumbLabel.formatMonthThumbLabel(context, filterGridItem.entry.bestDate), + ]; + case ChipSortFactor.name: + return [ + filterGridItem.filter.getLabel(context), + ]; + } + return []; + }, + ); + } +} diff --git a/lib/widgets/filter_grids/common/filter_grid_page.dart b/lib/widgets/filter_grids/common/filter_grid_page.dart index 65833b2ba..af4f98381 100644 --- a/lib/widgets/filter_grids/common/filter_grid_page.dart +++ b/lib/widgets/filter_grids/common/filter_grid_page.dart @@ -4,6 +4,7 @@ import 'package:aves/model/covers.dart'; import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/highlight.dart'; import 'package:aves/model/settings/settings.dart'; +import 'package:aves/model/source/enums.dart'; import 'package:aves/theme/durations.dart'; import 'package:aves/widgets/common/basic/draggable_scrollbar.dart'; import 'package:aves/widgets/common/basic/insets.dart'; @@ -20,12 +21,14 @@ import 'package:aves/widgets/common/scaling.dart'; import 'package:aves/widgets/common/tile_extent_controller.dart'; import 'package:aves/widgets/drawer/app_drawer.dart'; import 'package:aves/widgets/filter_grids/common/decorated_filter_chip.dart'; +import 'package:aves/widgets/filter_grids/common/draggable_thumb_label.dart'; import 'package:aves/widgets/filter_grids/common/section_keys.dart'; import 'package:aves/widgets/filter_grids/common/section_layout.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:provider/provider.dart'; +import 'package:tuple/tuple.dart'; typedef QueryTest = Iterable> Function(Iterable> filters, String query); @@ -34,6 +37,7 @@ class FilterGridPage extends StatelessWidget { final Widget appBar; final double appBarHeight; final Map>> filterSections; + final ChipSortFactor sortFactor; final bool showHeaders; final ValueNotifier queryNotifier; final QueryTest applyQuery; @@ -47,6 +51,7 @@ class FilterGridPage extends StatelessWidget { @required this.appBar, this.appBarHeight = kToolbarHeight, @required this.filterSections, + @required this.sortFactor, @required this.showHeaders, @required this.queryNotifier, this.applyQuery, @@ -72,6 +77,7 @@ class FilterGridPage extends StatelessWidget { appBar: appBar, appBarHeight: appBarHeight, filterSections: filterSections, + sortFactor: sortFactor, showHeaders: showHeaders, queryNotifier: queryNotifier, applyQuery: applyQuery, @@ -95,6 +101,7 @@ class FilterGrid extends StatefulWidget { final Widget appBar; final double appBarHeight; final Map>> filterSections; + final ChipSortFactor sortFactor; final bool showHeaders; final ValueNotifier queryNotifier; final QueryTest applyQuery; @@ -108,6 +115,7 @@ class FilterGrid extends StatefulWidget { @required this.appBar, @required this.appBarHeight, @required this.filterSections, + @required this.sortFactor, @required this.showHeaders, @required this.queryNotifier, @required this.applyQuery, @@ -137,6 +145,7 @@ class _FilterGridState extends State> appBar: widget.appBar, appBarHeight: widget.appBarHeight, filterSections: widget.filterSections, + sortFactor: widget.sortFactor, showHeaders: widget.showHeaders, queryNotifier: widget.queryNotifier, applyQuery: widget.applyQuery, @@ -151,6 +160,7 @@ class _FilterGridState extends State> class _FilterGridContent extends StatelessWidget { final Widget appBar; final Map>> filterSections; + final ChipSortFactor sortFactor; final bool showHeaders; final ValueNotifier queryNotifier; final Widget Function() emptyBuilder; @@ -165,6 +175,7 @@ class _FilterGridContent extends StatelessWidget { @required this.appBar, @required double appBarHeight, @required this.filterSections, + @required this.sortFactor, @required this.showHeaders, @required this.queryNotifier, @required this.applyQuery, @@ -197,38 +208,48 @@ class _FilterGridContent extends StatelessWidget { final sectionedListLayoutProvider = ValueListenableBuilder( valueListenable: context.select>((controller) => controller.extentNotifier), builder: (context, tileExtent, child) { - final columnCount = context.select((controller) => controller.getEffectiveColumnCountForExtent(tileExtent)); - final tileSpacing = context.select((controller) => controller.spacing); - return SectionedFilterListLayoutProvider( - sections: visibleFilterSections, - showHeaders: showHeaders, - scrollableWidth: context.select((controller) => controller.viewportSize.width), - tileExtent: tileExtent, - columnCount: columnCount, - spacing: tileSpacing, - tileBuilder: (gridItem) { - final filter = gridItem.filter; - final entry = gridItem.entry; - return MetaData( - metaData: ScalerMetadata(FilterGridItem(filter, entry)), - child: DecoratedFilterChip( - key: Key(filter.key), - filter: filter, - extent: tileExtent, - pinned: pinnedFilters.contains(filter), - onTap: onTap, - onLongPress: onLongPress, - ), - ); - }, - child: _FilterSectionedContent( - appBar: appBar, - appBarHeightNotifier: _appBarHeightNotifier, - visibleFilterSections: visibleFilterSections, - emptyBuilder: emptyBuilder, - scrollController: PrimaryScrollController.of(context), - ), - ); + return Selector>( + selector: (context, c) => Tuple3(c.viewportSize.width, c.columnCount, c.spacing), + builder: (context, c, child) { + final scrollableWidth = c.item1; + final columnCount = c.item2; + final tileSpacing = c.item3; + // do not listen for animation delay change + final controller = Provider.of(context, listen: false); + final tileAnimationDelay = controller.getTileAnimationDelay(Durations.staggeredAnimationPageTarget); + return SectionedFilterListLayoutProvider( + sections: visibleFilterSections, + showHeaders: showHeaders, + scrollableWidth: scrollableWidth, + tileExtent: tileExtent, + columnCount: columnCount, + spacing: tileSpacing, + tileBuilder: (gridItem) { + final filter = gridItem.filter; + final entry = gridItem.entry; + return MetaData( + metaData: ScalerMetadata(FilterGridItem(filter, entry)), + child: DecoratedFilterChip( + key: Key(filter.key), + filter: filter, + extent: tileExtent, + pinned: pinnedFilters.contains(filter), + onTap: onTap, + onLongPress: onLongPress, + ), + ); + }, + tileAnimationDelay: tileAnimationDelay, + child: _FilterSectionedContent( + appBar: appBar, + appBarHeightNotifier: _appBarHeightNotifier, + visibleFilterSections: visibleFilterSections, + sortFactor: sortFactor, + emptyBuilder: emptyBuilder, + scrollController: PrimaryScrollController.of(context), + ), + ); + }); }, ); return sectionedListLayoutProvider; @@ -241,6 +262,7 @@ class _FilterSectionedContent extends StatefulWidget final Widget appBar; final ValueNotifier appBarHeightNotifier; final Map>> visibleFilterSections; + final ChipSortFactor sortFactor; final Widget Function() emptyBuilder; final ScrollController scrollController; @@ -248,6 +270,7 @@ class _FilterSectionedContent extends StatefulWidget @required this.appBar, @required this.appBarHeightNotifier, @required this.visibleFilterSections, + @required this.sortFactor, @required this.emptyBuilder, @required this.scrollController, }); @@ -282,6 +305,7 @@ class _FilterSectionedContentState extends State<_Fi scrollableKey: _scrollableKey, appBar: appBar, appBarHeightNotifier: appBarHeightNotifier, + sortFactor: widget.sortFactor, emptyBuilder: emptyBuilder, scrollController: scrollController, ), @@ -380,6 +404,7 @@ class _FilterScrollView extends StatelessWidget { final GlobalKey scrollableKey; final Widget appBar; final ValueNotifier appBarHeightNotifier; + final ChipSortFactor sortFactor; final Widget Function() emptyBuilder; final ScrollController scrollController; @@ -387,6 +412,7 @@ class _FilterScrollView extends StatelessWidget { @required this.scrollableKey, @required this.appBar, @required this.appBarHeightNotifier, + @required this.sortFactor, @required this.emptyBuilder, @required this.scrollController, }); @@ -413,6 +439,10 @@ class _FilterScrollView extends StatelessWidget { top: appBarHeightNotifier.value, bottom: mqPaddingBottom, ), + labelTextBuilder: (offsetY) => FilterDraggableThumbLabel( + sortFactor: sortFactor, + offsetY: offsetY, + ), child: scrollView, ), ); diff --git a/lib/widgets/filter_grids/common/filter_nav_page.dart b/lib/widgets/filter_grids/common/filter_nav_page.dart index e85d4a88c..4c8a29fe6 100644 --- a/lib/widgets/filter_grids/common/filter_nav_page.dart +++ b/lib/widgets/filter_grids/common/filter_nav_page.dart @@ -28,6 +28,7 @@ class FilterNavigationPage extends StatelessWidget { final CollectionSource source; final String title; final ChipSetActionDelegate chipSetActionDelegate; + final ChipSortFactor sortFactor; final bool groupable, showHeaders; final ChipActionDelegate chipActionDelegate; final List Function(T filter) chipActionsBuilder; @@ -37,6 +38,7 @@ class FilterNavigationPage extends StatelessWidget { const FilterNavigationPage({ @required this.source, @required this.title, + @required this.sortFactor, this.groupable = false, this.showHeaders = false, @required this.chipSetActionDelegate, @@ -64,6 +66,7 @@ class FilterNavigationPage extends StatelessWidget { floating: true, ), filterSections: filterSections, + sortFactor: sortFactor, showHeaders: showHeaders, queryNotifier: ValueNotifier(''), emptyBuilder: () => ValueListenableBuilder( diff --git a/lib/widgets/filter_grids/common/section_layout.dart b/lib/widgets/filter_grids/common/section_layout.dart index 4c1d690a8..154bcc401 100644 --- a/lib/widgets/filter_grids/common/section_layout.dart +++ b/lib/widgets/filter_grids/common/section_layout.dart @@ -14,6 +14,7 @@ class SectionedFilterListLayoutProvider extends Sect double spacing = 0, @required double tileExtent, @required Widget Function(FilterGridItem gridItem) tileBuilder, + @required Duration tileAnimationDelay, @required Widget child, }) : super( scrollableWidth: scrollableWidth, @@ -21,6 +22,7 @@ class SectionedFilterListLayoutProvider extends Sect spacing: spacing, tileExtent: tileExtent, tileBuilder: tileBuilder, + tileAnimationDelay: tileAnimationDelay, child: child, ); diff --git a/lib/widgets/filter_grids/countries_page.dart b/lib/widgets/filter_grids/countries_page.dart index 774f4899d..087e00c5e 100644 --- a/lib/widgets/filter_grids/countries_page.dart +++ b/lib/widgets/filter_grids/countries_page.dart @@ -31,6 +31,7 @@ class CountryListPage extends StatelessWidget { builder: (context, snapshot) => FilterNavigationPage( source: source, title: context.l10n.countryPageTitle, + sortFactor: settings.countrySortFactor, chipSetActionDelegate: CountryChipSetActionDelegate(), chipActionDelegate: ChipActionDelegate(), chipActionsBuilder: (filter) => [ diff --git a/lib/widgets/filter_grids/tags_page.dart b/lib/widgets/filter_grids/tags_page.dart index faf4b7cbb..6b0f86520 100644 --- a/lib/widgets/filter_grids/tags_page.dart +++ b/lib/widgets/filter_grids/tags_page.dart @@ -31,6 +31,7 @@ class TagListPage extends StatelessWidget { builder: (context, snapshot) => FilterNavigationPage( source: source, title: context.l10n.tagPageTitle, + sortFactor: settings.tagSortFactor, chipSetActionDelegate: TagChipSetActionDelegate(), chipActionDelegate: ChipActionDelegate(), chipActionsBuilder: (filter) => [