rebuild performance review
This commit is contained in:
parent
64d80eb4be
commit
ff6aef1e82
4 changed files with 268 additions and 135 deletions
|
@ -32,12 +32,12 @@ 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';
|
||||||
|
|
||||||
class ThumbnailCollection extends StatefulWidget {
|
class CollectionGrid extends StatefulWidget {
|
||||||
@override
|
@override
|
||||||
_ThumbnailCollectionState createState() => _ThumbnailCollectionState();
|
_CollectionGridState createState() => _CollectionGridState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ThumbnailCollectionState extends State<ThumbnailCollection> {
|
class _CollectionGridState extends State<CollectionGrid> {
|
||||||
TileExtentController _tileExtentController;
|
TileExtentController _tileExtentController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -48,54 +48,20 @@ class _ThumbnailCollectionState extends State<ThumbnailCollection> {
|
||||||
extentMin: 46,
|
extentMin: 46,
|
||||||
spacing: 0,
|
spacing: 0,
|
||||||
);
|
);
|
||||||
return SafeArea(
|
return TileExtentControllerProvider(
|
||||||
bottom: false,
|
controller: _tileExtentController,
|
||||||
child: TileExtentControllerProvider(
|
child: _CollectionGridContent(),
|
||||||
controller: _tileExtentController,
|
|
||||||
child: _ThumbnailCollectionContent(),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ThumbnailCollectionContent extends StatelessWidget {
|
class _CollectionGridContent extends StatelessWidget {
|
||||||
final ValueNotifier<double> _appBarHeightNotifier = ValueNotifier(0);
|
|
||||||
final ValueNotifier<bool> _isScrollingNotifier = ValueNotifier(false);
|
final ValueNotifier<bool> _isScrollingNotifier = ValueNotifier(false);
|
||||||
final GlobalKey _scrollableKey = GlobalKey(debugLabel: 'thumbnail-collection-scrollable');
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final scrollController = PrimaryScrollController.of(context);
|
|
||||||
return Consumer<CollectionLens>(
|
return Consumer<CollectionLens>(
|
||||||
builder: (context, collection, child) {
|
builder: (context, collection, child) {
|
||||||
final scrollView = AnimationLimiter(
|
|
||||||
child: CollectionScrollView(
|
|
||||||
scrollableKey: _scrollableKey,
|
|
||||||
collection: collection,
|
|
||||||
appBar: CollectionAppBar(
|
|
||||||
appBarHeightNotifier: _appBarHeightNotifier,
|
|
||||||
collection: collection,
|
|
||||||
),
|
|
||||||
appBarHeightNotifier: _appBarHeightNotifier,
|
|
||||||
isScrollingNotifier: _isScrollingNotifier,
|
|
||||||
scrollController: scrollController,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
final scaler = _ThumbnailGridScaleGestureDetector(
|
|
||||||
scrollableKey: _scrollableKey,
|
|
||||||
appBarHeightNotifier: _appBarHeightNotifier,
|
|
||||||
child: scrollView,
|
|
||||||
);
|
|
||||||
|
|
||||||
final selector = GridSelectionGestureDetector(
|
|
||||||
selectable: AvesApp.mode == AppMode.main,
|
|
||||||
collection: collection,
|
|
||||||
scrollController: scrollController,
|
|
||||||
appBarHeightNotifier: _appBarHeightNotifier,
|
|
||||||
child: scaler,
|
|
||||||
);
|
|
||||||
|
|
||||||
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) {
|
||||||
|
@ -111,7 +77,11 @@ class _ThumbnailCollectionContent extends StatelessWidget {
|
||||||
tileExtent: tileExtent,
|
tileExtent: tileExtent,
|
||||||
isScrollingNotifier: _isScrollingNotifier,
|
isScrollingNotifier: _isScrollingNotifier,
|
||||||
),
|
),
|
||||||
child: selector,
|
child: _CollectionSectionedContent(
|
||||||
|
collection: collection,
|
||||||
|
isScrollingNotifier: _isScrollingNotifier,
|
||||||
|
scrollController: PrimaryScrollController.of(context),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -121,12 +91,69 @@ class _ThumbnailCollectionContent extends StatelessWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ThumbnailGridScaleGestureDetector extends StatelessWidget {
|
class _CollectionSectionedContent extends StatefulWidget {
|
||||||
|
final CollectionLens collection;
|
||||||
|
final ValueNotifier<bool> isScrollingNotifier;
|
||||||
|
final ScrollController scrollController;
|
||||||
|
|
||||||
|
const _CollectionSectionedContent({
|
||||||
|
@required this.collection,
|
||||||
|
@required this.isScrollingNotifier,
|
||||||
|
@required this.scrollController,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_CollectionSectionedContentState createState() => _CollectionSectionedContentState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CollectionSectionedContentState extends State<_CollectionSectionedContent> {
|
||||||
|
CollectionLens get collection => widget.collection;
|
||||||
|
|
||||||
|
ScrollController get scrollController => widget.scrollController;
|
||||||
|
|
||||||
|
final ValueNotifier<double> _appBarHeightNotifier = ValueNotifier(0);
|
||||||
|
final GlobalKey _scrollableKey = GlobalKey(debugLabel: 'thumbnail-collection-scrollable');
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final scrollView = AnimationLimiter(
|
||||||
|
child: _CollectionScrollView(
|
||||||
|
scrollableKey: _scrollableKey,
|
||||||
|
collection: collection,
|
||||||
|
appBar: CollectionAppBar(
|
||||||
|
appBarHeightNotifier: _appBarHeightNotifier,
|
||||||
|
collection: collection,
|
||||||
|
),
|
||||||
|
appBarHeightNotifier: _appBarHeightNotifier,
|
||||||
|
isScrollingNotifier: widget.isScrollingNotifier,
|
||||||
|
scrollController: scrollController,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final scaler = _CollectionScaler(
|
||||||
|
scrollableKey: _scrollableKey,
|
||||||
|
appBarHeightNotifier: _appBarHeightNotifier,
|
||||||
|
child: scrollView,
|
||||||
|
);
|
||||||
|
|
||||||
|
final selector = GridSelectionGestureDetector(
|
||||||
|
selectable: AvesApp.mode == AppMode.main,
|
||||||
|
collection: collection,
|
||||||
|
scrollController: scrollController,
|
||||||
|
appBarHeightNotifier: _appBarHeightNotifier,
|
||||||
|
child: scaler,
|
||||||
|
);
|
||||||
|
|
||||||
|
return selector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CollectionScaler extends StatelessWidget {
|
||||||
final GlobalKey scrollableKey;
|
final GlobalKey scrollableKey;
|
||||||
final ValueNotifier<double> appBarHeightNotifier;
|
final ValueNotifier<double> appBarHeightNotifier;
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
|
||||||
const _ThumbnailGridScaleGestureDetector({
|
const _CollectionScaler({
|
||||||
@required this.scrollableKey,
|
@required this.scrollableKey,
|
||||||
@required this.appBarHeightNotifier,
|
@required this.appBarHeightNotifier,
|
||||||
@required this.child,
|
@required this.child,
|
||||||
|
@ -166,7 +193,7 @@ class _ThumbnailGridScaleGestureDetector extends StatelessWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CollectionScrollView extends StatefulWidget {
|
class _CollectionScrollView extends StatefulWidget {
|
||||||
final GlobalKey scrollableKey;
|
final GlobalKey scrollableKey;
|
||||||
final CollectionLens collection;
|
final CollectionLens collection;
|
||||||
final Widget appBar;
|
final Widget appBar;
|
||||||
|
@ -174,7 +201,7 @@ class CollectionScrollView extends StatefulWidget {
|
||||||
final ValueNotifier<bool> isScrollingNotifier;
|
final ValueNotifier<bool> isScrollingNotifier;
|
||||||
final ScrollController scrollController;
|
final ScrollController scrollController;
|
||||||
|
|
||||||
const CollectionScrollView({
|
const _CollectionScrollView({
|
||||||
@required this.scrollableKey,
|
@required this.scrollableKey,
|
||||||
@required this.collection,
|
@required this.collection,
|
||||||
@required this.appBar,
|
@required this.appBar,
|
||||||
|
@ -187,7 +214,7 @@ class CollectionScrollView extends StatefulWidget {
|
||||||
_CollectionScrollViewState createState() => _CollectionScrollViewState();
|
_CollectionScrollViewState createState() => _CollectionScrollViewState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CollectionScrollViewState extends State<CollectionScrollView> {
|
class _CollectionScrollViewState extends State<_CollectionScrollView> {
|
||||||
Timer _scrollMonitoringTimer;
|
Timer _scrollMonitoringTimer;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -197,7 +224,7 @@ class _CollectionScrollViewState extends State<CollectionScrollView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didUpdateWidget(covariant CollectionScrollView oldWidget) {
|
void didUpdateWidget(covariant _CollectionScrollView oldWidget) {
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
_unregisterWidget(oldWidget);
|
_unregisterWidget(oldWidget);
|
||||||
_registerWidget(widget);
|
_registerWidget(widget);
|
||||||
|
@ -210,13 +237,13 @@ class _CollectionScrollViewState extends State<CollectionScrollView> {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _registerWidget(CollectionScrollView widget) {
|
void _registerWidget(_CollectionScrollView widget) {
|
||||||
widget.collection.filterChangeNotifier.addListener(_scrollToTop);
|
widget.collection.filterChangeNotifier.addListener(_scrollToTop);
|
||||||
widget.collection.sortGroupChangeNotifier.addListener(_scrollToTop);
|
widget.collection.sortGroupChangeNotifier.addListener(_scrollToTop);
|
||||||
widget.scrollController.addListener(_onScrollChange);
|
widget.scrollController.addListener(_onScrollChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _unregisterWidget(CollectionScrollView widget) {
|
void _unregisterWidget(_CollectionScrollView widget) {
|
||||||
widget.collection.filterChangeNotifier.removeListener(_scrollToTop);
|
widget.collection.filterChangeNotifier.removeListener(_scrollToTop);
|
||||||
widget.collection.sortGroupChangeNotifier.removeListener(_scrollToTop);
|
widget.collection.sortGroupChangeNotifier.removeListener(_scrollToTop);
|
||||||
widget.scrollController.removeListener(_onScrollChange);
|
widget.scrollController.removeListener(_onScrollChange);
|
||||||
|
@ -228,27 +255,6 @@ class _CollectionScrollViewState extends State<CollectionScrollView> {
|
||||||
return _buildDraggableScrollView(scrollView);
|
return _buildDraggableScrollView(scrollView);
|
||||||
}
|
}
|
||||||
|
|
||||||
ScrollView _buildScrollView(Widget appBar, CollectionLens collection) {
|
|
||||||
return CustomScrollView(
|
|
||||||
key: widget.scrollableKey,
|
|
||||||
primary: true,
|
|
||||||
// workaround to prevent scrolling the app bar away
|
|
||||||
// when there is no content and we use `SliverFillRemaining`
|
|
||||||
physics: collection.isEmpty ? NeverScrollableScrollPhysics() : SloppyScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
|
|
||||||
cacheExtent: context.select<TileExtentController, double>((controller) => controller.effectiveExtentMax * 2),
|
|
||||||
slivers: [
|
|
||||||
appBar,
|
|
||||||
collection.isEmpty
|
|
||||||
? SliverFillRemaining(
|
|
||||||
hasScrollBody: false,
|
|
||||||
child: _buildEmptyCollectionPlaceholder(collection),
|
|
||||||
)
|
|
||||||
: SectionedListSliver<AvesEntry>(),
|
|
||||||
BottomPaddingSliver(),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildDraggableScrollView(ScrollView scrollView) {
|
Widget _buildDraggableScrollView(ScrollView scrollView) {
|
||||||
return ValueListenableBuilder<double>(
|
return ValueListenableBuilder<double>(
|
||||||
valueListenable: widget.appBarHeightNotifier,
|
valueListenable: widget.appBarHeightNotifier,
|
||||||
|
@ -274,6 +280,27 @@ class _CollectionScrollViewState extends State<CollectionScrollView> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ScrollView _buildScrollView(Widget appBar, CollectionLens collection) {
|
||||||
|
return CustomScrollView(
|
||||||
|
key: widget.scrollableKey,
|
||||||
|
primary: true,
|
||||||
|
// workaround to prevent scrolling the app bar away
|
||||||
|
// when there is no content and we use `SliverFillRemaining`
|
||||||
|
physics: collection.isEmpty ? NeverScrollableScrollPhysics() : SloppyScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
|
||||||
|
cacheExtent: context.select<TileExtentController, double>((controller) => controller.effectiveExtentMax * 2),
|
||||||
|
slivers: [
|
||||||
|
appBar,
|
||||||
|
collection.isEmpty
|
||||||
|
? SliverFillRemaining(
|
||||||
|
hasScrollBody: false,
|
||||||
|
child: _buildEmptyCollectionPlaceholder(collection),
|
||||||
|
)
|
||||||
|
: SectionedListSliver<AvesEntry>(),
|
||||||
|
BottomPaddingSliver(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildEmptyCollectionPlaceholder(CollectionLens collection) {
|
Widget _buildEmptyCollectionPlaceholder(CollectionLens collection) {
|
||||||
return ValueListenableBuilder<SourceState>(
|
return ValueListenableBuilder<SourceState>(
|
||||||
valueListenable: collection.source.stateNotifier,
|
valueListenable: collection.source.stateNotifier,
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:aves/model/source/collection_lens.dart';
|
import 'package:aves/model/source/collection_lens.dart';
|
||||||
import 'package:aves/widgets/collection/thumbnail_collection.dart';
|
import 'package:aves/widgets/collection/collection_grid.dart';
|
||||||
import 'package:aves/widgets/common/basic/insets.dart';
|
import 'package:aves/widgets/common/basic/insets.dart';
|
||||||
import 'package:aves/widgets/common/behaviour/double_back_pop.dart';
|
import 'package:aves/widgets/common/behaviour/double_back_pop.dart';
|
||||||
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||||
|
@ -42,9 +42,12 @@ class _CollectionPageState extends State<CollectionPage> {
|
||||||
},
|
},
|
||||||
child: DoubleBackPopScope(
|
child: DoubleBackPopScope(
|
||||||
child: GestureAreaProtectorStack(
|
child: GestureAreaProtectorStack(
|
||||||
child: ChangeNotifierProvider<CollectionLens>.value(
|
child: SafeArea(
|
||||||
value: collection,
|
bottom: false,
|
||||||
child: ThumbnailCollection(),
|
child: ChangeNotifierProvider<CollectionLens>.value(
|
||||||
|
value: collection,
|
||||||
|
child: CollectionGrid(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -58,21 +58,21 @@ class _AlbumPickPageState extends State<AlbumPickPage> {
|
||||||
return StreamBuilder(
|
return StreamBuilder(
|
||||||
stream: source.eventBus.on<AlbumsChangedEvent>(),
|
stream: source.eventBus.on<AlbumsChangedEvent>(),
|
||||||
builder: (context, snapshot) => FilterGridPage<AlbumFilter>(
|
builder: (context, snapshot) => FilterGridPage<AlbumFilter>(
|
||||||
|
settingsRouteKey: AlbumListPage.routeName,
|
||||||
appBar: appBar,
|
appBar: appBar,
|
||||||
|
appBarHeight: AlbumPickAppBar.preferredHeight,
|
||||||
filterSections: AlbumListPage.getAlbumEntries(context, source),
|
filterSections: AlbumListPage.getAlbumEntries(context, source),
|
||||||
showHeaders: settings.albumGroupFactor != AlbumChipGroupFactor.none,
|
showHeaders: settings.albumGroupFactor != AlbumChipGroupFactor.none,
|
||||||
|
queryNotifier: _queryNotifier,
|
||||||
applyQuery: (filters, query) {
|
applyQuery: (filters, query) {
|
||||||
if (query == null || query.isEmpty) return filters;
|
if (query == null || query.isEmpty) return filters;
|
||||||
query = query.toUpperCase();
|
query = query.toUpperCase();
|
||||||
return filters.where((item) => item.filter.uniqueName.toUpperCase().contains(query)).toList();
|
return filters.where((item) => item.filter.uniqueName.toUpperCase().contains(query)).toList();
|
||||||
},
|
},
|
||||||
queryNotifier: _queryNotifier,
|
|
||||||
emptyBuilder: () => EmptyContent(
|
emptyBuilder: () => EmptyContent(
|
||||||
icon: AIcons.album,
|
icon: AIcons.album,
|
||||||
text: context.l10n.albumEmpty,
|
text: context.l10n.albumEmpty,
|
||||||
),
|
),
|
||||||
settingsRouteKey: AlbumListPage.routeName,
|
|
||||||
appBarHeight: AlbumPickAppBar.preferredHeight,
|
|
||||||
onTap: (filter) => Navigator.pop<String>(context, (filter as AlbumFilter)?.album),
|
onTap: (filter) => Navigator.pop<String>(context, (filter as AlbumFilter)?.album),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -29,27 +29,27 @@ import 'package:provider/provider.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);
|
||||||
|
|
||||||
class FilterGridPage<T extends CollectionFilter> extends StatelessWidget {
|
class FilterGridPage<T extends CollectionFilter> extends StatelessWidget {
|
||||||
|
final String settingsRouteKey;
|
||||||
final Widget appBar;
|
final Widget appBar;
|
||||||
|
final double appBarHeight;
|
||||||
final Map<ChipSectionKey, List<FilterGridItem<T>>> filterSections;
|
final Map<ChipSectionKey, List<FilterGridItem<T>>> filterSections;
|
||||||
final bool showHeaders;
|
final bool showHeaders;
|
||||||
final ValueNotifier<String> queryNotifier;
|
final ValueNotifier<String> queryNotifier;
|
||||||
final Widget Function() emptyBuilder;
|
|
||||||
final String settingsRouteKey;
|
|
||||||
final double appBarHeight;
|
|
||||||
final QueryTest<T> applyQuery;
|
final QueryTest<T> applyQuery;
|
||||||
|
final Widget Function() emptyBuilder;
|
||||||
final FilterCallback onTap;
|
final FilterCallback onTap;
|
||||||
final OffsetFilterCallback onLongPress;
|
final OffsetFilterCallback onLongPress;
|
||||||
|
|
||||||
const FilterGridPage({
|
const FilterGridPage({
|
||||||
Key key,
|
Key key,
|
||||||
|
this.settingsRouteKey,
|
||||||
@required this.appBar,
|
@required this.appBar,
|
||||||
|
this.appBarHeight = kToolbarHeight,
|
||||||
@required this.filterSections,
|
@required this.filterSections,
|
||||||
this.showHeaders = false,
|
@required this.showHeaders,
|
||||||
@required this.queryNotifier,
|
@required this.queryNotifier,
|
||||||
this.applyQuery,
|
this.applyQuery,
|
||||||
@required this.emptyBuilder,
|
@required this.emptyBuilder,
|
||||||
this.settingsRouteKey,
|
|
||||||
this.appBarHeight = kToolbarHeight,
|
|
||||||
@required this.onTap,
|
@required this.onTap,
|
||||||
this.onLongPress,
|
this.onLongPress,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
@ -64,24 +64,17 @@ class FilterGridPage<T extends CollectionFilter> extends StatelessWidget {
|
||||||
child: GestureAreaProtectorStack(
|
child: GestureAreaProtectorStack(
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
bottom: false,
|
bottom: false,
|
||||||
child: TileExtentControllerProvider(
|
child: FilterGrid<T>(
|
||||||
controller: TileExtentController(
|
settingsRouteKey: settingsRouteKey,
|
||||||
settingsRouteKey: settingsRouteKey ?? context.currentRouteName,
|
appBar: appBar,
|
||||||
columnCountDefault: 2,
|
appBarHeight: appBarHeight,
|
||||||
extentMin: 60,
|
filterSections: filterSections,
|
||||||
spacing: 8,
|
showHeaders: showHeaders,
|
||||||
),
|
queryNotifier: queryNotifier,
|
||||||
child: _FilterGridPageContent<T>(
|
applyQuery: applyQuery,
|
||||||
appBar: appBar,
|
emptyBuilder: emptyBuilder,
|
||||||
filterSections: filterSections,
|
onTap: onTap,
|
||||||
showHeaders: showHeaders,
|
onLongPress: onLongPress,
|
||||||
queryNotifier: queryNotifier,
|
|
||||||
applyQuery: applyQuery,
|
|
||||||
emptyBuilder: emptyBuilder,
|
|
||||||
appBarHeight: appBarHeight,
|
|
||||||
onTap: onTap,
|
|
||||||
onLongPress: onLongPress,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -93,7 +86,65 @@ class FilterGridPage<T extends CollectionFilter> extends StatelessWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FilterGridPageContent<T extends CollectionFilter> extends StatelessWidget {
|
class FilterGrid<T extends CollectionFilter> extends StatefulWidget {
|
||||||
|
final String settingsRouteKey;
|
||||||
|
final Widget appBar;
|
||||||
|
final double appBarHeight;
|
||||||
|
final Map<ChipSectionKey, List<FilterGridItem<T>>> filterSections;
|
||||||
|
final bool showHeaders;
|
||||||
|
final ValueNotifier<String> queryNotifier;
|
||||||
|
final QueryTest<T> applyQuery;
|
||||||
|
final Widget Function() emptyBuilder;
|
||||||
|
final FilterCallback onTap;
|
||||||
|
final OffsetFilterCallback onLongPress;
|
||||||
|
|
||||||
|
const FilterGrid({
|
||||||
|
Key key,
|
||||||
|
@required this.settingsRouteKey,
|
||||||
|
@required this.appBar,
|
||||||
|
@required this.appBarHeight,
|
||||||
|
@required this.filterSections,
|
||||||
|
@required this.showHeaders,
|
||||||
|
@required this.queryNotifier,
|
||||||
|
@required this.applyQuery,
|
||||||
|
@required this.emptyBuilder,
|
||||||
|
@required this.onTap,
|
||||||
|
@required this.onLongPress,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_FilterGridState createState() => _FilterGridState<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FilterGridState<T extends CollectionFilter> extends State<FilterGrid<T>> {
|
||||||
|
TileExtentController _tileExtentController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
_tileExtentController ??= TileExtentController(
|
||||||
|
settingsRouteKey: widget.settingsRouteKey ?? context.currentRouteName,
|
||||||
|
columnCountDefault: 2,
|
||||||
|
extentMin: 60,
|
||||||
|
spacing: 8,
|
||||||
|
);
|
||||||
|
return TileExtentControllerProvider(
|
||||||
|
controller: _tileExtentController,
|
||||||
|
child: _FilterGridContent<T>(
|
||||||
|
appBar: widget.appBar,
|
||||||
|
appBarHeight: widget.appBarHeight,
|
||||||
|
filterSections: widget.filterSections,
|
||||||
|
showHeaders: widget.showHeaders,
|
||||||
|
queryNotifier: widget.queryNotifier,
|
||||||
|
applyQuery: widget.applyQuery,
|
||||||
|
emptyBuilder: widget.emptyBuilder,
|
||||||
|
onTap: widget.onTap,
|
||||||
|
onLongPress: widget.onLongPress,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 bool showHeaders;
|
final bool showHeaders;
|
||||||
|
@ -105,15 +156,15 @@ class _FilterGridPageContent<T extends CollectionFilter> extends StatelessWidget
|
||||||
|
|
||||||
final ValueNotifier<double> _appBarHeightNotifier = ValueNotifier(0);
|
final ValueNotifier<double> _appBarHeightNotifier = ValueNotifier(0);
|
||||||
|
|
||||||
_FilterGridPageContent({
|
_FilterGridContent({
|
||||||
Key key,
|
Key key,
|
||||||
@required this.appBar,
|
@required this.appBar,
|
||||||
|
@required double appBarHeight,
|
||||||
@required this.filterSections,
|
@required this.filterSections,
|
||||||
@required this.showHeaders,
|
@required this.showHeaders,
|
||||||
@required this.queryNotifier,
|
@required this.queryNotifier,
|
||||||
@required this.applyQuery,
|
@required this.applyQuery,
|
||||||
@required this.emptyBuilder,
|
@required this.emptyBuilder,
|
||||||
@required double appBarHeight,
|
|
||||||
@required this.onTap,
|
@required this.onTap,
|
||||||
@required this.onLongPress,
|
@required this.onLongPress,
|
||||||
}) : super(key: key) {
|
}) : super(key: key) {
|
||||||
|
@ -166,7 +217,7 @@ class _FilterGridPageContent<T extends CollectionFilter> extends StatelessWidget
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: _SectionedContent<T>(
|
child: _FilterSectionedContent<T>(
|
||||||
appBar: appBar,
|
appBar: appBar,
|
||||||
appBarHeightNotifier: _appBarHeightNotifier,
|
appBarHeightNotifier: _appBarHeightNotifier,
|
||||||
visibleFilterSections: visibleFilterSections,
|
visibleFilterSections: visibleFilterSections,
|
||||||
|
@ -182,14 +233,14 @@ class _FilterGridPageContent<T extends CollectionFilter> extends StatelessWidget
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SectionedContent<T extends CollectionFilter> extends StatefulWidget {
|
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 Widget Function() emptyBuilder;
|
final Widget Function() emptyBuilder;
|
||||||
final ScrollController scrollController;
|
final ScrollController scrollController;
|
||||||
|
|
||||||
const _SectionedContent({
|
const _FilterSectionedContent({
|
||||||
@required this.appBar,
|
@required this.appBar,
|
||||||
@required this.appBarHeightNotifier,
|
@required this.appBarHeightNotifier,
|
||||||
@required this.visibleFilterSections,
|
@required this.visibleFilterSections,
|
||||||
|
@ -198,10 +249,10 @@ class _SectionedContent<T extends CollectionFilter> extends StatefulWidget {
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_SectionedContentState createState() => _SectionedContentState<T>();
|
_FilterSectionedContentState createState() => _FilterSectionedContentState<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SectionedContentState<T extends CollectionFilter> extends State<_SectionedContent<T>> {
|
class _FilterSectionedContentState<T extends CollectionFilter> extends State<_FilterSectionedContent<T>> {
|
||||||
Widget get appBar => widget.appBar;
|
Widget get appBar => widget.appBar;
|
||||||
|
|
||||||
ValueNotifier<double> get appBarHeightNotifier => widget.appBarHeightNotifier;
|
ValueNotifier<double> get appBarHeightNotifier => widget.appBarHeightNotifier;
|
||||||
|
@ -220,6 +271,27 @@ class _SectionedContentState<T extends CollectionFilter> extends State<_Sectione
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) => _checkInitHighlight());
|
WidgetsBinding.instance.addPostFrameCallback((_) => _checkInitHighlight());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final scrollView = AnimationLimiter(
|
||||||
|
child: _FilterScrollView<T>(
|
||||||
|
scrollableKey: _scrollableKey,
|
||||||
|
appBar: appBar,
|
||||||
|
appBarHeightNotifier: appBarHeightNotifier,
|
||||||
|
emptyBuilder: emptyBuilder,
|
||||||
|
scrollController: scrollController,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final scaler = _FilterScaler<T>(
|
||||||
|
scrollableKey: _scrollableKey,
|
||||||
|
appBarHeightNotifier: appBarHeightNotifier,
|
||||||
|
child: scrollView,
|
||||||
|
);
|
||||||
|
|
||||||
|
return scaler;
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _checkInitHighlight() async {
|
Future<void> _checkInitHighlight() async {
|
||||||
final highlightInfo = context.read<HighlightInfo>();
|
final highlightInfo = context.read<HighlightInfo>();
|
||||||
final filter = highlightInfo.clear();
|
final filter = highlightInfo.clear();
|
||||||
|
@ -252,13 +324,25 @@ class _SectionedContentState<T extends CollectionFilter> extends State<_Sectione
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FilterScaler<T extends CollectionFilter> extends StatelessWidget {
|
||||||
|
final GlobalKey scrollableKey;
|
||||||
|
final ValueNotifier<double> appBarHeightNotifier;
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
const _FilterScaler({
|
||||||
|
@required this.scrollableKey,
|
||||||
|
@required this.appBarHeightNotifier,
|
||||||
|
@required this.child,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final pinnedFilters = settings.pinnedFilters;
|
final pinnedFilters = settings.pinnedFilters;
|
||||||
final tileSpacing = context.select<TileExtentController, double>((controller) => controller.spacing);
|
final tileSpacing = context.select<TileExtentController, double>((controller) => controller.spacing);
|
||||||
return GridScaleGestureDetector<FilterGridItem<T>>(
|
return GridScaleGestureDetector<FilterGridItem<T>>(
|
||||||
scrollableKey: _scrollableKey,
|
scrollableKey: scrollableKey,
|
||||||
appBarHeightNotifier: appBarHeightNotifier,
|
appBarHeightNotifier: appBarHeightNotifier,
|
||||||
gridBuilder: (center, extent, child) => CustomPaint(
|
gridBuilder: (center, extent, child) => CustomPaint(
|
||||||
painter: GridPainter(
|
painter: GridPainter(
|
||||||
|
@ -283,11 +367,31 @@ class _SectionedContentState<T extends CollectionFilter> extends State<_Sectione
|
||||||
return sectionedListLayout.getTileRect(item) ?? Rect.zero;
|
return sectionedListLayout.getTileRect(item) ?? Rect.zero;
|
||||||
},
|
},
|
||||||
onScaled: (item) => context.read<HighlightInfo>().set(item.filter),
|
onScaled: (item) => context.read<HighlightInfo>().set(item.filter),
|
||||||
child: AnimationLimiter(
|
child: child,
|
||||||
child: _buildDraggableScrollView(_buildScrollView(context, visibleFilterSections.isEmpty)),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FilterScrollView<T extends CollectionFilter> extends StatelessWidget {
|
||||||
|
final GlobalKey scrollableKey;
|
||||||
|
final Widget appBar;
|
||||||
|
final ValueNotifier<double> appBarHeightNotifier;
|
||||||
|
final Widget Function() emptyBuilder;
|
||||||
|
final ScrollController scrollController;
|
||||||
|
|
||||||
|
const _FilterScrollView({
|
||||||
|
@required this.scrollableKey,
|
||||||
|
@required this.appBar,
|
||||||
|
@required this.appBarHeightNotifier,
|
||||||
|
@required this.emptyBuilder,
|
||||||
|
@required this.scrollController,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final scrollView = _buildScrollView(context);
|
||||||
|
return _buildDraggableScrollView(scrollView);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildDraggableScrollView(ScrollView scrollView) {
|
Widget _buildDraggableScrollView(ScrollView scrollView) {
|
||||||
return Selector<MediaQueryData, double>(
|
return Selector<MediaQueryData, double>(
|
||||||
|
@ -310,31 +414,30 @@ class _SectionedContentState<T extends CollectionFilter> extends State<_Sectione
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ScrollView _buildScrollView(BuildContext context, bool empty) {
|
ScrollView _buildScrollView(BuildContext context) {
|
||||||
Widget content;
|
|
||||||
if (empty) {
|
|
||||||
content = SliverFillRemaining(
|
|
||||||
hasScrollBody: false,
|
|
||||||
child: Selector<MediaQueryData, double>(
|
|
||||||
selector: (context, mq) => mq.effectiveBottomPadding,
|
|
||||||
builder: (context, mqPaddingBottom, child) {
|
|
||||||
return Padding(
|
|
||||||
padding: EdgeInsets.only(bottom: mqPaddingBottom),
|
|
||||||
child: emptyBuilder(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
content = SectionedListSliver<FilterGridItem<T>>();
|
|
||||||
}
|
|
||||||
|
|
||||||
return CustomScrollView(
|
return CustomScrollView(
|
||||||
key: _scrollableKey,
|
key: scrollableKey,
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
slivers: [
|
slivers: [
|
||||||
appBar,
|
appBar,
|
||||||
content,
|
Selector<SectionedListLayout<FilterGridItem<T>>, bool>(
|
||||||
|
selector: (context, layout) => layout.sections.isEmpty,
|
||||||
|
builder: (context, empty, child) {
|
||||||
|
return empty
|
||||||
|
? SliverFillRemaining(
|
||||||
|
hasScrollBody: false,
|
||||||
|
child: Selector<MediaQueryData, double>(
|
||||||
|
selector: (context, mq) => mq.effectiveBottomPadding,
|
||||||
|
builder: (context, mqPaddingBottom, child) {
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(bottom: mqPaddingBottom),
|
||||||
|
child: emptyBuilder(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: SectionedListSliver<FilterGridItem<T>>();
|
||||||
|
}),
|
||||||
BottomPaddingSliver(),
|
BottomPaddingSliver(),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in a new issue