rebuild performance review

This commit is contained in:
Thibault Deckers 2021-03-14 12:22:01 +09:00
parent 64d80eb4be
commit ff6aef1e82
4 changed files with 268 additions and 135 deletions

View file

@ -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,

View file

@ -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(),
),
), ),
), ),
), ),

View file

@ -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),
), ),
); );

View file

@ -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(),
], ],
); );