#9 fast scroll feedback + staggered animation improvements
This commit is contained in:
parent
81c9c8a757
commit
2a4722736b
19 changed files with 337 additions and 70 deletions
|
@ -2,6 +2,9 @@
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
### Added
|
||||||
|
- Collection / Albums / Countries / Tags: added label when dragging scrollbar thumb
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Upgraded Flutter to beta v2.1.0-12.2.pre
|
- Upgraded Flutter to beta v2.1.0-12.2.pre
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,12 @@
|
||||||
"@welcomeAnalyticsToggle": {},
|
"@welcomeAnalyticsToggle": {},
|
||||||
"welcomeTermsToggle": "I agree to the terms and conditions",
|
"welcomeTermsToggle": "I agree to the terms and conditions",
|
||||||
"@welcomeTermsToggle": {},
|
"@welcomeTermsToggle": {},
|
||||||
|
"itemCount": "{count, plural, =1{1 item} other{{count} items}}",
|
||||||
|
"@itemCount": {
|
||||||
|
"placeholders": {
|
||||||
|
"count": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
"applyButtonLabel": "APPLY",
|
"applyButtonLabel": "APPLY",
|
||||||
"@applyButtonLabel": {},
|
"@applyButtonLabel": {},
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
"welcomeMessage": "아베스 사용을 환영합니다",
|
"welcomeMessage": "아베스 사용을 환영합니다",
|
||||||
"welcomeAnalyticsToggle": "진단 데이터를 보내는 것에 동의합니다 (선택)",
|
"welcomeAnalyticsToggle": "진단 데이터를 보내는 것에 동의합니다 (선택)",
|
||||||
"welcomeTermsToggle": "이용약관에 동의합니다",
|
"welcomeTermsToggle": "이용약관에 동의합니다",
|
||||||
|
"itemCount": "{count, plural, other{{count}개}}",
|
||||||
|
|
||||||
"applyButtonLabel": "확인",
|
"applyButtonLabel": "확인",
|
||||||
"deleteButtonLabel": "삭제",
|
"deleteButtonLabel": "삭제",
|
||||||
|
|
|
@ -9,6 +9,7 @@ class Durations {
|
||||||
static const dialogTransitionAnimation = Duration(milliseconds: 150); // ref `transitionDuration` in `showDialog()`
|
static const dialogTransitionAnimation = Duration(milliseconds: 150); // ref `transitionDuration` in `showDialog()`
|
||||||
|
|
||||||
static const staggeredAnimation = Duration(milliseconds: 375);
|
static const staggeredAnimation = Duration(milliseconds: 375);
|
||||||
|
static const staggeredAnimationPageTarget = Duration(milliseconds: 900);
|
||||||
static const dialogFieldReachAnimation = Duration(milliseconds: 300);
|
static const dialogFieldReachAnimation = Duration(milliseconds: 300);
|
||||||
|
|
||||||
static const appBarTitleAnimation = Duration(milliseconds: 300);
|
static const appBarTitleAnimation = Duration(milliseconds: 300);
|
||||||
|
|
|
@ -11,6 +11,7 @@ import 'package:aves/ref/mime_types.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/widgets/collection/app_bar.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/section_layout.dart';
|
||||||
import 'package:aves/widgets/collection/grid/selector.dart';
|
import 'package:aves/widgets/collection/grid/selector.dart';
|
||||||
import 'package:aves/widgets/collection/grid/thumbnail.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/scaling.dart';
|
||||||
import 'package:aves/widgets/common/tile_extent_controller.dart';
|
import 'package:aves/widgets/common/tile_extent_controller.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
|
||||||
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
class CollectionGrid extends StatefulWidget {
|
class CollectionGrid extends StatefulWidget {
|
||||||
final String settingsRouteKey;
|
final String settingsRouteKey;
|
||||||
|
@ -74,23 +75,34 @@ class _CollectionGridContent extends StatelessWidget {
|
||||||
builder: (context, tileExtent, child) {
|
builder: (context, tileExtent, child) {
|
||||||
return ThumbnailTheme(
|
return ThumbnailTheme(
|
||||||
extent: tileExtent,
|
extent: tileExtent,
|
||||||
child: SectionedEntryListLayoutProvider(
|
child: Selector<TileExtentController, Tuple2<double, int>>(
|
||||||
collection: collection,
|
selector: (context, c) => Tuple2(c.viewportSize.width, c.columnCount),
|
||||||
scrollableWidth: context.select<TileExtentController, double>((controller) => controller.viewportSize.width),
|
builder: (context, c, child) {
|
||||||
tileExtent: tileExtent,
|
final scrollableWidth = c.item1;
|
||||||
columnCount: context.select<TileExtentController, int>((controller) => controller.getEffectiveColumnCountForExtent(tileExtent)),
|
final columnCount = c.item2;
|
||||||
tileBuilder: (entry) => InteractiveThumbnail(
|
// do not listen for animation delay change
|
||||||
key: ValueKey(entry.contentId),
|
final controller = Provider.of<TileExtentController>(context, listen: false);
|
||||||
collection: collection,
|
final tileAnimationDelay = controller.getTileAnimationDelay(Durations.staggeredAnimationPageTarget);
|
||||||
entry: entry,
|
return SectionedEntryListLayoutProvider(
|
||||||
tileExtent: tileExtent,
|
collection: collection,
|
||||||
isScrollingNotifier: _isScrollingNotifier,
|
scrollableWidth: scrollableWidth,
|
||||||
),
|
tileExtent: tileExtent,
|
||||||
child: _CollectionSectionedContent(
|
columnCount: columnCount,
|
||||||
collection: collection,
|
tileBuilder: (entry) => InteractiveThumbnail(
|
||||||
isScrollingNotifier: _isScrollingNotifier,
|
key: ValueKey(entry.contentId),
|
||||||
scrollController: PrimaryScrollController.of(context),
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final scrollView = _buildScrollView(widget.appBar, widget.collection);
|
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<double>(
|
return ValueListenableBuilder<double>(
|
||||||
valueListenable: widget.appBarHeightNotifier,
|
valueListenable: widget.appBarHeightNotifier,
|
||||||
builder: (context, appBarHeight, child) => Selector<MediaQueryData, double>(
|
builder: (context, appBarHeight, child) => Selector<MediaQueryData, double>(
|
||||||
|
@ -287,6 +299,10 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> {
|
||||||
top: appBarHeight,
|
top: appBarHeight,
|
||||||
bottom: mqPaddingBottom,
|
bottom: mqPaddingBottom,
|
||||||
),
|
),
|
||||||
|
labelTextBuilder: (offsetY) => CollectionDraggableThumbLabel(
|
||||||
|
collection: collection,
|
||||||
|
offsetY: offsetY,
|
||||||
|
),
|
||||||
child: scrollView,
|
child: scrollView,
|
||||||
),
|
),
|
||||||
child: child,
|
child: child,
|
||||||
|
|
60
lib/widgets/collection/draggable_thumb_label.dart
Normal file
60
lib/widgets/collection/draggable_thumb_label.dart
Normal file
|
@ -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<AvesEntry>(
|
||||||
|
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<CollectionSource>().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<CollectionSource>().getUniqueAlbumName(context, entry.directory),
|
||||||
|
entry.bestTitle,
|
||||||
|
];
|
||||||
|
case EntrySortFactor.size:
|
||||||
|
return [
|
||||||
|
formatFilesize(entry.sizeBytes, round: 0),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _hasMultipleSections(BuildContext context) => context.read<SectionedListLayout<AvesEntry>>().sections.length > 1;
|
||||||
|
}
|
|
@ -15,12 +15,14 @@ class SectionedEntryListLayoutProvider extends SectionedListLayoutProvider<AvesE
|
||||||
@required int columnCount,
|
@required int columnCount,
|
||||||
@required double tileExtent,
|
@required double tileExtent,
|
||||||
@required Widget Function(AvesEntry entry) tileBuilder,
|
@required Widget Function(AvesEntry entry) tileBuilder,
|
||||||
|
@required Duration tileAnimationDelay,
|
||||||
@required Widget child,
|
@required Widget child,
|
||||||
}) : super(
|
}) : super(
|
||||||
scrollableWidth: scrollableWidth,
|
scrollableWidth: scrollableWidth,
|
||||||
columnCount: columnCount,
|
columnCount: columnCount,
|
||||||
tileExtent: tileExtent,
|
tileExtent: tileExtent,
|
||||||
tileBuilder: tileBuilder,
|
tileBuilder: tileBuilder,
|
||||||
|
tileAnimationDelay: tileAnimationDelay,
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,7 @@ class DraggableScrollbar extends StatefulWidget {
|
||||||
@required this.controller,
|
@required this.controller,
|
||||||
this.padding,
|
this.padding,
|
||||||
this.scrollbarAnimationDuration = const Duration(milliseconds: 300),
|
this.scrollbarAnimationDuration = const Duration(milliseconds: 300),
|
||||||
this.scrollbarTimeToFade = const Duration(milliseconds: 600),
|
this.scrollbarTimeToFade = const Duration(milliseconds: 1000),
|
||||||
this.labelTextBuilder,
|
this.labelTextBuilder,
|
||||||
@required this.child,
|
@required this.child,
|
||||||
}) : assert(controller != null),
|
}) : assert(controller != null),
|
||||||
|
@ -91,6 +91,7 @@ class DraggableScrollbar extends StatefulWidget {
|
||||||
backgroundColor: backgroundColor,
|
backgroundColor: backgroundColor,
|
||||||
child: labelText,
|
child: labelText,
|
||||||
),
|
),
|
||||||
|
SizedBox(width: 24),
|
||||||
scrollThumb,
|
scrollThumb,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -133,6 +134,7 @@ class ScrollLabel extends StatelessWidget {
|
||||||
class _DraggableScrollbarState extends State<DraggableScrollbar> with TickerProviderStateMixin {
|
class _DraggableScrollbarState extends State<DraggableScrollbar> with TickerProviderStateMixin {
|
||||||
final ValueNotifier<double> _thumbOffsetNotifier = ValueNotifier(0), _viewOffsetNotifier = ValueNotifier(0);
|
final ValueNotifier<double> _thumbOffsetNotifier = ValueNotifier(0), _viewOffsetNotifier = ValueNotifier(0);
|
||||||
bool _isDragInProcess = false;
|
bool _isDragInProcess = false;
|
||||||
|
Offset _longPressLastGlobalPosition;
|
||||||
|
|
||||||
AnimationController _thumbAnimationController;
|
AnimationController _thumbAnimationController;
|
||||||
Animation<double> _thumbAnimation;
|
Animation<double> _thumbAnimation;
|
||||||
|
@ -193,9 +195,19 @@ class _DraggableScrollbarState extends State<DraggableScrollbar> with TickerProv
|
||||||
),
|
),
|
||||||
RepaintBoundary(
|
RepaintBoundary(
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onVerticalDragStart: _onVerticalDragStart,
|
onLongPressStart: (details) {
|
||||||
onVerticalDragUpdate: _onVerticalDragUpdate,
|
_longPressLastGlobalPosition = details.globalPosition;
|
||||||
onVerticalDragEnd: _onVerticalDragEnd,
|
_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(
|
child: ValueListenableBuilder(
|
||||||
valueListenable: _thumbOffsetNotifier,
|
valueListenable: _thumbOffsetNotifier,
|
||||||
builder: (context, thumbOffset, child) => Container(
|
builder: (context, thumbOffset, child) => Container(
|
||||||
|
@ -244,17 +256,18 @@ class _DraggableScrollbarState extends State<DraggableScrollbar> with TickerProv
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onVerticalDragStart(DragStartDetails details) {
|
void _onVerticalDragStart() {
|
||||||
_labelAnimationController.forward();
|
_labelAnimationController.forward();
|
||||||
_fadeoutTimer?.cancel();
|
_fadeoutTimer?.cancel();
|
||||||
|
_showThumb();
|
||||||
setState(() => _isDragInProcess = true);
|
setState(() => _isDragInProcess = true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onVerticalDragUpdate(DragUpdateDetails details) {
|
void _onVerticalDragUpdate(double deltaY) {
|
||||||
_showThumb();
|
_showThumb();
|
||||||
if (_isDragInProcess) {
|
if (_isDragInProcess) {
|
||||||
// thumb offset
|
// thumb offset
|
||||||
_thumbOffsetNotifier.value = (_thumbOffsetNotifier.value + details.delta.dy).clamp(thumbMinScrollExtent, thumbMaxScrollExtent);
|
_thumbOffsetNotifier.value = (_thumbOffsetNotifier.value + deltaY).clamp(thumbMinScrollExtent, thumbMaxScrollExtent);
|
||||||
|
|
||||||
// scroll offset
|
// scroll offset
|
||||||
final min = controller.position.minScrollExtent;
|
final min = controller.position.minScrollExtent;
|
||||||
|
@ -263,7 +276,7 @@ class _DraggableScrollbarState extends State<DraggableScrollbar> with TickerProv
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onVerticalDragEnd(DragEndDetails details) {
|
void _onVerticalDragEnd() {
|
||||||
_scheduleFadeout();
|
_scheduleFadeout();
|
||||||
setState(() => _isDragInProcess = false);
|
setState(() => _isDragInProcess = false);
|
||||||
}
|
}
|
||||||
|
|
67
lib/widgets/common/grid/draggable_thumb_label.dart
Normal file
67
lib/widgets/common/grid/draggable_thumb_label.dart
Normal file
|
@ -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<T> extends StatelessWidget {
|
||||||
|
final double offsetY;
|
||||||
|
final List<String> Function(BuildContext context, T item) lineBuilder;
|
||||||
|
|
||||||
|
const DraggableThumbLabel({
|
||||||
|
@required this.offsetY,
|
||||||
|
@required this.lineBuilder,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final sll = context.read<SectionedListLayout<T>>();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ abstract class SectionedListLayoutProvider<T> extends StatelessWidget {
|
||||||
final int columnCount;
|
final int columnCount;
|
||||||
final double spacing, tileExtent;
|
final double spacing, tileExtent;
|
||||||
final Widget Function(T item) tileBuilder;
|
final Widget Function(T item) tileBuilder;
|
||||||
|
final Duration tileAnimationDelay;
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
|
||||||
const SectionedListLayoutProvider({
|
const SectionedListLayoutProvider({
|
||||||
|
@ -20,6 +21,7 @@ abstract class SectionedListLayoutProvider<T> extends StatelessWidget {
|
||||||
this.spacing = 0,
|
this.spacing = 0,
|
||||||
@required this.tileExtent,
|
@required this.tileExtent,
|
||||||
@required this.tileBuilder,
|
@required this.tileBuilder,
|
||||||
|
this.tileAnimationDelay,
|
||||||
@required this.child,
|
@required this.child,
|
||||||
}) : assert(scrollableWidth != 0);
|
}) : assert(scrollableWidth != 0);
|
||||||
|
|
||||||
|
@ -40,6 +42,7 @@ abstract class SectionedListLayoutProvider<T> extends StatelessWidget {
|
||||||
final _showHeaders = showHeaders;
|
final _showHeaders = showHeaders;
|
||||||
final _sections = sections;
|
final _sections = sections;
|
||||||
final sectionKeys = _sections.keys.toList();
|
final sectionKeys = _sections.keys.toList();
|
||||||
|
final animate = tileAnimationDelay > Duration.zero;
|
||||||
|
|
||||||
final sectionLayouts = <SectionLayout>[];
|
final sectionLayouts = <SectionLayout>[];
|
||||||
var currentIndex = 0, currentOffset = 0.0;
|
var currentIndex = 0, currentOffset = 0.0;
|
||||||
|
@ -76,6 +79,7 @@ abstract class SectionedListLayoutProvider<T> extends StatelessWidget {
|
||||||
listIndex - sectionFirstIndex,
|
listIndex - sectionFirstIndex,
|
||||||
sectionKey,
|
sectionKey,
|
||||||
headerExtent,
|
headerExtent,
|
||||||
|
animate,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -97,10 +101,11 @@ abstract class SectionedListLayoutProvider<T> extends StatelessWidget {
|
||||||
int sectionChildIndex,
|
int sectionChildIndex,
|
||||||
SectionKey sectionKey,
|
SectionKey sectionKey,
|
||||||
double headerExtent,
|
double headerExtent,
|
||||||
|
bool animate,
|
||||||
) {
|
) {
|
||||||
if (sectionChildIndex == 0) {
|
if (sectionChildIndex == 0) {
|
||||||
final header = headerExtent > 0 ? buildHeader(context, sectionKey, headerExtent) : SizedBox.shrink();
|
final header = headerExtent > 0 ? buildHeader(context, sectionKey, headerExtent) : SizedBox.shrink();
|
||||||
return _buildAnimation(sectionGridIndex, header);
|
return animate ? _buildAnimation(sectionGridIndex, header) : header;
|
||||||
}
|
}
|
||||||
sectionChildIndex--;
|
sectionChildIndex--;
|
||||||
|
|
||||||
|
@ -113,7 +118,7 @@ abstract class SectionedListLayoutProvider<T> extends StatelessWidget {
|
||||||
final itemGridIndex = sectionGridIndex + i - minItemIndex;
|
final itemGridIndex = sectionGridIndex + i - minItemIndex;
|
||||||
final item = tileBuilder(section[i]);
|
final item = tileBuilder(section[i]);
|
||||||
if (i != minItemIndex) children.add(SizedBox(width: spacing));
|
if (i != minItemIndex) children.add(SizedBox(width: spacing));
|
||||||
children.add(_buildAnimation(itemGridIndex, item));
|
children.add(animate ? _buildAnimation(itemGridIndex, item) : item);
|
||||||
}
|
}
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
@ -126,7 +131,7 @@ abstract class SectionedListLayoutProvider<T> extends StatelessWidget {
|
||||||
position: index,
|
position: index,
|
||||||
columnCount: columnCount,
|
columnCount: columnCount,
|
||||||
duration: Durations.staggeredAnimation,
|
duration: Durations.staggeredAnimation,
|
||||||
delay: Durations.staggeredAnimationDelay,
|
delay: tileAnimationDelay ?? Durations.staggeredAnimationDelay,
|
||||||
child: SlideAnimation(
|
child: SlideAnimation(
|
||||||
verticalOffset: 50.0,
|
verticalOffset: 50.0,
|
||||||
child: FadeInAnimation(
|
child: FadeInAnimation(
|
||||||
|
@ -189,9 +194,11 @@ class SectionedListLayout<T> {
|
||||||
return Rect.fromLTWH(left, top, tileExtent, tileExtent);
|
return Rect.fromLTWH(left, top, tileExtent, tileExtent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SectionLayout getSectionAt(double offsetY) => sectionLayouts.firstWhere((sl) => offsetY < sl.maxOffset, orElse: () => null);
|
||||||
|
|
||||||
T getItemAt(Offset position) {
|
T getItemAt(Offset position) {
|
||||||
var dy = position.dy;
|
var dy = position.dy;
|
||||||
final sectionLayout = sectionLayouts.firstWhere((sl) => dy < sl.maxOffset, orElse: () => null);
|
final sectionLayout = getSectionAt(dy);
|
||||||
if (sectionLayout == null) return null;
|
if (sectionLayout == null) return null;
|
||||||
|
|
||||||
final section = sections[sectionLayout.sectionKey];
|
final section = sections[sectionLayout.sectionKey];
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
|
import 'package:flutter/scheduler.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
class TileExtentController {
|
class TileExtentController {
|
||||||
|
@ -45,7 +46,7 @@ class TileExtentController {
|
||||||
? oldUserPreferredExtent
|
? oldUserPreferredExtent
|
||||||
: currentExtent;
|
: currentExtent;
|
||||||
|
|
||||||
final columnCount = getEffectiveColumnCountForExtent(targetExtent);
|
final columnCount = _effectiveColumnCountForExtent(targetExtent);
|
||||||
final newExtent = _extentForColumnCount(columnCount);
|
final newExtent = _extentForColumnCount(columnCount);
|
||||||
|
|
||||||
if (userPreferredExtent > 0 || oldUserPreferredExtent == 0) {
|
if (userPreferredExtent > 0 || oldUserPreferredExtent == 0) {
|
||||||
|
@ -67,15 +68,24 @@ class TileExtentController {
|
||||||
|
|
||||||
int _effectiveColumnCountMax() => _columnCountForExtent(extentMin).floor();
|
int _effectiveColumnCountMax() => _columnCountForExtent(extentMin).floor();
|
||||||
|
|
||||||
double get effectiveExtentMin => _extentForColumnCount(_effectiveColumnCountMax());
|
int _effectiveColumnCountForExtent(double extent) {
|
||||||
|
|
||||||
double get effectiveExtentMax => _extentForColumnCount(_effectiveColumnCountMin());
|
|
||||||
|
|
||||||
int getEffectiveColumnCountForExtent(double extent) {
|
|
||||||
if (extent > 0) {
|
if (extent > 0) {
|
||||||
final columnCount = _columnCountForExtent(extent);
|
final columnCount = _columnCountForExtent(extent);
|
||||||
return columnCount.clamp(_effectiveColumnCountMin(), _effectiveColumnCountMax()).round();
|
return columnCount.clamp(_effectiveColumnCountMin(), _effectiveColumnCountMax()).round();
|
||||||
}
|
}
|
||||||
return columnCountDefault;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,6 +62,7 @@ class _AlbumPickPageState extends State<AlbumPickPage> {
|
||||||
appBar: appBar,
|
appBar: appBar,
|
||||||
appBarHeight: AlbumPickAppBar.preferredHeight,
|
appBarHeight: AlbumPickAppBar.preferredHeight,
|
||||||
filterSections: AlbumListPage.getAlbumEntries(context, source),
|
filterSections: AlbumListPage.getAlbumEntries(context, source),
|
||||||
|
sortFactor: settings.albumSortFactor,
|
||||||
showHeaders: settings.albumGroupFactor != AlbumChipGroupFactor.none,
|
showHeaders: settings.albumGroupFactor != AlbumChipGroupFactor.none,
|
||||||
queryNotifier: _queryNotifier,
|
queryNotifier: _queryNotifier,
|
||||||
applyQuery: (filters, query) {
|
applyQuery: (filters, query) {
|
||||||
|
|
|
@ -34,6 +34,7 @@ class AlbumListPage extends StatelessWidget {
|
||||||
builder: (context, snapshot) => FilterNavigationPage<AlbumFilter>(
|
builder: (context, snapshot) => FilterNavigationPage<AlbumFilter>(
|
||||||
source: source,
|
source: source,
|
||||||
title: context.l10n.albumPageTitle,
|
title: context.l10n.albumPageTitle,
|
||||||
|
sortFactor: settings.albumSortFactor,
|
||||||
groupable: true,
|
groupable: true,
|
||||||
showHeaders: settings.albumGroupFactor != AlbumChipGroupFactor.none,
|
showHeaders: settings.albumGroupFactor != AlbumChipGroupFactor.none,
|
||||||
chipSetActionDelegate: AlbumChipSetActionDelegate(),
|
chipSetActionDelegate: AlbumChipSetActionDelegate(),
|
||||||
|
|
42
lib/widgets/filter_grids/common/draggable_thumb_label.dart
Normal file
42
lib/widgets/filter_grids/common/draggable_thumb_label.dart
Normal file
|
@ -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<T extends CollectionFilter> extends StatelessWidget {
|
||||||
|
final ChipSortFactor sortFactor;
|
||||||
|
final double offsetY;
|
||||||
|
|
||||||
|
const FilterDraggableThumbLabel({
|
||||||
|
@required this.sortFactor,
|
||||||
|
@required this.offsetY,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return DraggableThumbLabel<FilterGridItem<T>>(
|
||||||
|
offsetY: offsetY,
|
||||||
|
lineBuilder: (context, filterGridItem) {
|
||||||
|
switch (sortFactor) {
|
||||||
|
case ChipSortFactor.count:
|
||||||
|
return [
|
||||||
|
context.l10n.itemCount(context.read<CollectionSource>().count(filterGridItem.filter)),
|
||||||
|
];
|
||||||
|
break;
|
||||||
|
case ChipSortFactor.date:
|
||||||
|
return [
|
||||||
|
DraggableThumbLabel.formatMonthThumbLabel(context, filterGridItem.entry.bestDate),
|
||||||
|
];
|
||||||
|
case ChipSortFactor.name:
|
||||||
|
return [
|
||||||
|
filterGridItem.filter.getLabel(context),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import 'package:aves/model/covers.dart';
|
||||||
import 'package:aves/model/filters/filters.dart';
|
import 'package:aves/model/filters/filters.dart';
|
||||||
import 'package:aves/model/highlight.dart';
|
import 'package:aves/model/highlight.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
|
import 'package:aves/model/source/enums.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/widgets/common/basic/draggable_scrollbar.dart';
|
import 'package:aves/widgets/common/basic/draggable_scrollbar.dart';
|
||||||
import 'package:aves/widgets/common/basic/insets.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/common/tile_extent_controller.dart';
|
||||||
import 'package:aves/widgets/drawer/app_drawer.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/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_keys.dart';
|
||||||
import 'package:aves/widgets/filter_grids/common/section_layout.dart';
|
import 'package:aves/widgets/filter_grids/common/section_layout.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
typedef QueryTest<T extends CollectionFilter> = Iterable<FilterGridItem<T>> Function(Iterable<FilterGridItem<T>> filters, String query);
|
typedef QueryTest<T extends CollectionFilter> = Iterable<FilterGridItem<T>> Function(Iterable<FilterGridItem<T>> filters, String query);
|
||||||
|
|
||||||
|
@ -34,6 +37,7 @@ class FilterGridPage<T extends CollectionFilter> extends StatelessWidget {
|
||||||
final Widget appBar;
|
final Widget appBar;
|
||||||
final double appBarHeight;
|
final double appBarHeight;
|
||||||
final Map<ChipSectionKey, List<FilterGridItem<T>>> filterSections;
|
final Map<ChipSectionKey, List<FilterGridItem<T>>> filterSections;
|
||||||
|
final ChipSortFactor sortFactor;
|
||||||
final bool showHeaders;
|
final bool showHeaders;
|
||||||
final ValueNotifier<String> queryNotifier;
|
final ValueNotifier<String> queryNotifier;
|
||||||
final QueryTest<T> applyQuery;
|
final QueryTest<T> applyQuery;
|
||||||
|
@ -47,6 +51,7 @@ class FilterGridPage<T extends CollectionFilter> extends StatelessWidget {
|
||||||
@required this.appBar,
|
@required this.appBar,
|
||||||
this.appBarHeight = kToolbarHeight,
|
this.appBarHeight = kToolbarHeight,
|
||||||
@required this.filterSections,
|
@required this.filterSections,
|
||||||
|
@required this.sortFactor,
|
||||||
@required this.showHeaders,
|
@required this.showHeaders,
|
||||||
@required this.queryNotifier,
|
@required this.queryNotifier,
|
||||||
this.applyQuery,
|
this.applyQuery,
|
||||||
|
@ -72,6 +77,7 @@ class FilterGridPage<T extends CollectionFilter> extends StatelessWidget {
|
||||||
appBar: appBar,
|
appBar: appBar,
|
||||||
appBarHeight: appBarHeight,
|
appBarHeight: appBarHeight,
|
||||||
filterSections: filterSections,
|
filterSections: filterSections,
|
||||||
|
sortFactor: sortFactor,
|
||||||
showHeaders: showHeaders,
|
showHeaders: showHeaders,
|
||||||
queryNotifier: queryNotifier,
|
queryNotifier: queryNotifier,
|
||||||
applyQuery: applyQuery,
|
applyQuery: applyQuery,
|
||||||
|
@ -95,6 +101,7 @@ class FilterGrid<T extends CollectionFilter> extends StatefulWidget {
|
||||||
final Widget appBar;
|
final Widget appBar;
|
||||||
final double appBarHeight;
|
final double appBarHeight;
|
||||||
final Map<ChipSectionKey, List<FilterGridItem<T>>> filterSections;
|
final Map<ChipSectionKey, List<FilterGridItem<T>>> filterSections;
|
||||||
|
final ChipSortFactor sortFactor;
|
||||||
final bool showHeaders;
|
final bool showHeaders;
|
||||||
final ValueNotifier<String> queryNotifier;
|
final ValueNotifier<String> queryNotifier;
|
||||||
final QueryTest<T> applyQuery;
|
final QueryTest<T> applyQuery;
|
||||||
|
@ -108,6 +115,7 @@ class FilterGrid<T extends CollectionFilter> extends StatefulWidget {
|
||||||
@required this.appBar,
|
@required this.appBar,
|
||||||
@required this.appBarHeight,
|
@required this.appBarHeight,
|
||||||
@required this.filterSections,
|
@required this.filterSections,
|
||||||
|
@required this.sortFactor,
|
||||||
@required this.showHeaders,
|
@required this.showHeaders,
|
||||||
@required this.queryNotifier,
|
@required this.queryNotifier,
|
||||||
@required this.applyQuery,
|
@required this.applyQuery,
|
||||||
|
@ -137,6 +145,7 @@ class _FilterGridState<T extends CollectionFilter> extends State<FilterGrid<T>>
|
||||||
appBar: widget.appBar,
|
appBar: widget.appBar,
|
||||||
appBarHeight: widget.appBarHeight,
|
appBarHeight: widget.appBarHeight,
|
||||||
filterSections: widget.filterSections,
|
filterSections: widget.filterSections,
|
||||||
|
sortFactor: widget.sortFactor,
|
||||||
showHeaders: widget.showHeaders,
|
showHeaders: widget.showHeaders,
|
||||||
queryNotifier: widget.queryNotifier,
|
queryNotifier: widget.queryNotifier,
|
||||||
applyQuery: widget.applyQuery,
|
applyQuery: widget.applyQuery,
|
||||||
|
@ -151,6 +160,7 @@ class _FilterGridState<T extends CollectionFilter> extends State<FilterGrid<T>>
|
||||||
class _FilterGridContent<T extends CollectionFilter> extends StatelessWidget {
|
class _FilterGridContent<T extends CollectionFilter> extends StatelessWidget {
|
||||||
final Widget appBar;
|
final Widget appBar;
|
||||||
final Map<ChipSectionKey, List<FilterGridItem<T>>> filterSections;
|
final Map<ChipSectionKey, List<FilterGridItem<T>>> filterSections;
|
||||||
|
final ChipSortFactor sortFactor;
|
||||||
final bool showHeaders;
|
final bool showHeaders;
|
||||||
final ValueNotifier<String> queryNotifier;
|
final ValueNotifier<String> queryNotifier;
|
||||||
final Widget Function() emptyBuilder;
|
final Widget Function() emptyBuilder;
|
||||||
|
@ -165,6 +175,7 @@ class _FilterGridContent<T extends CollectionFilter> extends StatelessWidget {
|
||||||
@required this.appBar,
|
@required this.appBar,
|
||||||
@required double appBarHeight,
|
@required double appBarHeight,
|
||||||
@required this.filterSections,
|
@required this.filterSections,
|
||||||
|
@required this.sortFactor,
|
||||||
@required this.showHeaders,
|
@required this.showHeaders,
|
||||||
@required this.queryNotifier,
|
@required this.queryNotifier,
|
||||||
@required this.applyQuery,
|
@required this.applyQuery,
|
||||||
|
@ -197,38 +208,48 @@ class _FilterGridContent<T extends CollectionFilter> extends StatelessWidget {
|
||||||
final sectionedListLayoutProvider = ValueListenableBuilder<double>(
|
final sectionedListLayoutProvider = ValueListenableBuilder<double>(
|
||||||
valueListenable: context.select<TileExtentController, ValueNotifier<double>>((controller) => controller.extentNotifier),
|
valueListenable: context.select<TileExtentController, ValueNotifier<double>>((controller) => controller.extentNotifier),
|
||||||
builder: (context, tileExtent, child) {
|
builder: (context, tileExtent, child) {
|
||||||
final columnCount = context.select<TileExtentController, int>((controller) => controller.getEffectiveColumnCountForExtent(tileExtent));
|
return Selector<TileExtentController, Tuple3<double, int, double>>(
|
||||||
final tileSpacing = context.select<TileExtentController, double>((controller) => controller.spacing);
|
selector: (context, c) => Tuple3(c.viewportSize.width, c.columnCount, c.spacing),
|
||||||
return SectionedFilterListLayoutProvider<T>(
|
builder: (context, c, child) {
|
||||||
sections: visibleFilterSections,
|
final scrollableWidth = c.item1;
|
||||||
showHeaders: showHeaders,
|
final columnCount = c.item2;
|
||||||
scrollableWidth: context.select<TileExtentController, double>((controller) => controller.viewportSize.width),
|
final tileSpacing = c.item3;
|
||||||
tileExtent: tileExtent,
|
// do not listen for animation delay change
|
||||||
columnCount: columnCount,
|
final controller = Provider.of<TileExtentController>(context, listen: false);
|
||||||
spacing: tileSpacing,
|
final tileAnimationDelay = controller.getTileAnimationDelay(Durations.staggeredAnimationPageTarget);
|
||||||
tileBuilder: (gridItem) {
|
return SectionedFilterListLayoutProvider<T>(
|
||||||
final filter = gridItem.filter;
|
sections: visibleFilterSections,
|
||||||
final entry = gridItem.entry;
|
showHeaders: showHeaders,
|
||||||
return MetaData(
|
scrollableWidth: scrollableWidth,
|
||||||
metaData: ScalerMetadata(FilterGridItem<T>(filter, entry)),
|
tileExtent: tileExtent,
|
||||||
child: DecoratedFilterChip(
|
columnCount: columnCount,
|
||||||
key: Key(filter.key),
|
spacing: tileSpacing,
|
||||||
filter: filter,
|
tileBuilder: (gridItem) {
|
||||||
extent: tileExtent,
|
final filter = gridItem.filter;
|
||||||
pinned: pinnedFilters.contains(filter),
|
final entry = gridItem.entry;
|
||||||
onTap: onTap,
|
return MetaData(
|
||||||
onLongPress: onLongPress,
|
metaData: ScalerMetadata(FilterGridItem<T>(filter, entry)),
|
||||||
),
|
child: DecoratedFilterChip(
|
||||||
);
|
key: Key(filter.key),
|
||||||
},
|
filter: filter,
|
||||||
child: _FilterSectionedContent<T>(
|
extent: tileExtent,
|
||||||
appBar: appBar,
|
pinned: pinnedFilters.contains(filter),
|
||||||
appBarHeightNotifier: _appBarHeightNotifier,
|
onTap: onTap,
|
||||||
visibleFilterSections: visibleFilterSections,
|
onLongPress: onLongPress,
|
||||||
emptyBuilder: emptyBuilder,
|
),
|
||||||
scrollController: PrimaryScrollController.of(context),
|
);
|
||||||
),
|
},
|
||||||
);
|
tileAnimationDelay: tileAnimationDelay,
|
||||||
|
child: _FilterSectionedContent<T>(
|
||||||
|
appBar: appBar,
|
||||||
|
appBarHeightNotifier: _appBarHeightNotifier,
|
||||||
|
visibleFilterSections: visibleFilterSections,
|
||||||
|
sortFactor: sortFactor,
|
||||||
|
emptyBuilder: emptyBuilder,
|
||||||
|
scrollController: PrimaryScrollController.of(context),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return sectionedListLayoutProvider;
|
return sectionedListLayoutProvider;
|
||||||
|
@ -241,6 +262,7 @@ class _FilterSectionedContent<T extends CollectionFilter> extends StatefulWidget
|
||||||
final Widget appBar;
|
final Widget appBar;
|
||||||
final ValueNotifier<double> appBarHeightNotifier;
|
final ValueNotifier<double> appBarHeightNotifier;
|
||||||
final Map<ChipSectionKey, List<FilterGridItem<T>>> visibleFilterSections;
|
final Map<ChipSectionKey, List<FilterGridItem<T>>> visibleFilterSections;
|
||||||
|
final ChipSortFactor sortFactor;
|
||||||
final Widget Function() emptyBuilder;
|
final Widget Function() emptyBuilder;
|
||||||
final ScrollController scrollController;
|
final ScrollController scrollController;
|
||||||
|
|
||||||
|
@ -248,6 +270,7 @@ class _FilterSectionedContent<T extends CollectionFilter> extends StatefulWidget
|
||||||
@required this.appBar,
|
@required this.appBar,
|
||||||
@required this.appBarHeightNotifier,
|
@required this.appBarHeightNotifier,
|
||||||
@required this.visibleFilterSections,
|
@required this.visibleFilterSections,
|
||||||
|
@required this.sortFactor,
|
||||||
@required this.emptyBuilder,
|
@required this.emptyBuilder,
|
||||||
@required this.scrollController,
|
@required this.scrollController,
|
||||||
});
|
});
|
||||||
|
@ -282,6 +305,7 @@ class _FilterSectionedContentState<T extends CollectionFilter> extends State<_Fi
|
||||||
scrollableKey: _scrollableKey,
|
scrollableKey: _scrollableKey,
|
||||||
appBar: appBar,
|
appBar: appBar,
|
||||||
appBarHeightNotifier: appBarHeightNotifier,
|
appBarHeightNotifier: appBarHeightNotifier,
|
||||||
|
sortFactor: widget.sortFactor,
|
||||||
emptyBuilder: emptyBuilder,
|
emptyBuilder: emptyBuilder,
|
||||||
scrollController: scrollController,
|
scrollController: scrollController,
|
||||||
),
|
),
|
||||||
|
@ -380,6 +404,7 @@ class _FilterScrollView<T extends CollectionFilter> extends StatelessWidget {
|
||||||
final GlobalKey scrollableKey;
|
final GlobalKey scrollableKey;
|
||||||
final Widget appBar;
|
final Widget appBar;
|
||||||
final ValueNotifier<double> appBarHeightNotifier;
|
final ValueNotifier<double> appBarHeightNotifier;
|
||||||
|
final ChipSortFactor sortFactor;
|
||||||
final Widget Function() emptyBuilder;
|
final Widget Function() emptyBuilder;
|
||||||
final ScrollController scrollController;
|
final ScrollController scrollController;
|
||||||
|
|
||||||
|
@ -387,6 +412,7 @@ class _FilterScrollView<T extends CollectionFilter> extends StatelessWidget {
|
||||||
@required this.scrollableKey,
|
@required this.scrollableKey,
|
||||||
@required this.appBar,
|
@required this.appBar,
|
||||||
@required this.appBarHeightNotifier,
|
@required this.appBarHeightNotifier,
|
||||||
|
@required this.sortFactor,
|
||||||
@required this.emptyBuilder,
|
@required this.emptyBuilder,
|
||||||
@required this.scrollController,
|
@required this.scrollController,
|
||||||
});
|
});
|
||||||
|
@ -413,6 +439,10 @@ class _FilterScrollView<T extends CollectionFilter> extends StatelessWidget {
|
||||||
top: appBarHeightNotifier.value,
|
top: appBarHeightNotifier.value,
|
||||||
bottom: mqPaddingBottom,
|
bottom: mqPaddingBottom,
|
||||||
),
|
),
|
||||||
|
labelTextBuilder: (offsetY) => FilterDraggableThumbLabel<T>(
|
||||||
|
sortFactor: sortFactor,
|
||||||
|
offsetY: offsetY,
|
||||||
|
),
|
||||||
child: scrollView,
|
child: scrollView,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -28,6 +28,7 @@ class FilterNavigationPage<T extends CollectionFilter> extends StatelessWidget {
|
||||||
final CollectionSource source;
|
final CollectionSource source;
|
||||||
final String title;
|
final String title;
|
||||||
final ChipSetActionDelegate chipSetActionDelegate;
|
final ChipSetActionDelegate chipSetActionDelegate;
|
||||||
|
final ChipSortFactor sortFactor;
|
||||||
final bool groupable, showHeaders;
|
final bool groupable, showHeaders;
|
||||||
final ChipActionDelegate chipActionDelegate;
|
final ChipActionDelegate chipActionDelegate;
|
||||||
final List<ChipAction> Function(T filter) chipActionsBuilder;
|
final List<ChipAction> Function(T filter) chipActionsBuilder;
|
||||||
|
@ -37,6 +38,7 @@ class FilterNavigationPage<T extends CollectionFilter> extends StatelessWidget {
|
||||||
const FilterNavigationPage({
|
const FilterNavigationPage({
|
||||||
@required this.source,
|
@required this.source,
|
||||||
@required this.title,
|
@required this.title,
|
||||||
|
@required this.sortFactor,
|
||||||
this.groupable = false,
|
this.groupable = false,
|
||||||
this.showHeaders = false,
|
this.showHeaders = false,
|
||||||
@required this.chipSetActionDelegate,
|
@required this.chipSetActionDelegate,
|
||||||
|
@ -64,6 +66,7 @@ class FilterNavigationPage<T extends CollectionFilter> extends StatelessWidget {
|
||||||
floating: true,
|
floating: true,
|
||||||
),
|
),
|
||||||
filterSections: filterSections,
|
filterSections: filterSections,
|
||||||
|
sortFactor: sortFactor,
|
||||||
showHeaders: showHeaders,
|
showHeaders: showHeaders,
|
||||||
queryNotifier: ValueNotifier(''),
|
queryNotifier: ValueNotifier(''),
|
||||||
emptyBuilder: () => ValueListenableBuilder<SourceState>(
|
emptyBuilder: () => ValueListenableBuilder<SourceState>(
|
||||||
|
|
|
@ -14,6 +14,7 @@ class SectionedFilterListLayoutProvider<T extends CollectionFilter> extends Sect
|
||||||
double spacing = 0,
|
double spacing = 0,
|
||||||
@required double tileExtent,
|
@required double tileExtent,
|
||||||
@required Widget Function(FilterGridItem<T> gridItem) tileBuilder,
|
@required Widget Function(FilterGridItem<T> gridItem) tileBuilder,
|
||||||
|
@required Duration tileAnimationDelay,
|
||||||
@required Widget child,
|
@required Widget child,
|
||||||
}) : super(
|
}) : super(
|
||||||
scrollableWidth: scrollableWidth,
|
scrollableWidth: scrollableWidth,
|
||||||
|
@ -21,6 +22,7 @@ class SectionedFilterListLayoutProvider<T extends CollectionFilter> extends Sect
|
||||||
spacing: spacing,
|
spacing: spacing,
|
||||||
tileExtent: tileExtent,
|
tileExtent: tileExtent,
|
||||||
tileBuilder: tileBuilder,
|
tileBuilder: tileBuilder,
|
||||||
|
tileAnimationDelay: tileAnimationDelay,
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@ class CountryListPage extends StatelessWidget {
|
||||||
builder: (context, snapshot) => FilterNavigationPage<LocationFilter>(
|
builder: (context, snapshot) => FilterNavigationPage<LocationFilter>(
|
||||||
source: source,
|
source: source,
|
||||||
title: context.l10n.countryPageTitle,
|
title: context.l10n.countryPageTitle,
|
||||||
|
sortFactor: settings.countrySortFactor,
|
||||||
chipSetActionDelegate: CountryChipSetActionDelegate(),
|
chipSetActionDelegate: CountryChipSetActionDelegate(),
|
||||||
chipActionDelegate: ChipActionDelegate(),
|
chipActionDelegate: ChipActionDelegate(),
|
||||||
chipActionsBuilder: (filter) => [
|
chipActionsBuilder: (filter) => [
|
||||||
|
|
|
@ -31,6 +31,7 @@ class TagListPage extends StatelessWidget {
|
||||||
builder: (context, snapshot) => FilterNavigationPage<TagFilter>(
|
builder: (context, snapshot) => FilterNavigationPage<TagFilter>(
|
||||||
source: source,
|
source: source,
|
||||||
title: context.l10n.tagPageTitle,
|
title: context.l10n.tagPageTitle,
|
||||||
|
sortFactor: settings.tagSortFactor,
|
||||||
chipSetActionDelegate: TagChipSetActionDelegate(),
|
chipSetActionDelegate: TagChipSetActionDelegate(),
|
||||||
chipActionDelegate: ChipActionDelegate(),
|
chipActionDelegate: ChipActionDelegate(),
|
||||||
chipActionsBuilder: (filter) => [
|
chipActionsBuilder: (filter) => [
|
||||||
|
|
Loading…
Reference in a new issue