filter grid scaling: border radius by extent, shared extent for album list & pick, fixed rebuild on query change, fixed pinned item sort, scroll to scaled item
This commit is contained in:
parent
e218afc6b6
commit
f86eb078a4
10 changed files with 143 additions and 138 deletions
|
@ -43,7 +43,7 @@ class ThumbnailCollection extends StatelessWidget {
|
|||
if (viewportSize.isEmpty) return SizedBox.shrink();
|
||||
|
||||
final tileExtentManager = TileExtentManager(
|
||||
routeName: context.currentRouteName,
|
||||
settingsRouteKey: context.currentRouteName,
|
||||
columnCountMin: columnCountMin,
|
||||
columnCountDefault: columnCountDefault,
|
||||
extentMin: extentMin,
|
||||
|
|
|
@ -18,8 +18,9 @@ class AvesFilterChip extends StatefulWidget {
|
|||
final HeroType heroType;
|
||||
final FilterCallback onTap;
|
||||
final OffsetFilterCallback onLongPress;
|
||||
final BorderRadius borderRadius;
|
||||
|
||||
static final BorderRadius borderRadius = BorderRadius.circular(32);
|
||||
static const double defaultRadius = 32;
|
||||
static const double outlineWidth = 2;
|
||||
static const double minChipHeight = kMinInteractiveDimension;
|
||||
static const double minChipWidth = 80;
|
||||
|
@ -33,6 +34,7 @@ class AvesFilterChip extends StatefulWidget {
|
|||
this.showGenericIcon = true,
|
||||
this.background,
|
||||
this.details,
|
||||
this.borderRadius = const BorderRadius.all(Radius.circular(defaultRadius)),
|
||||
this.padding = 6.0,
|
||||
this.heroType = HeroType.onTap,
|
||||
this.onTap,
|
||||
|
@ -52,6 +54,8 @@ class _AvesFilterChipState extends State<AvesFilterChip> {
|
|||
|
||||
CollectionFilter get filter => widget.filter;
|
||||
|
||||
BorderRadius get borderRadius => widget.borderRadius;
|
||||
|
||||
double get padding => widget.padding;
|
||||
|
||||
@override
|
||||
|
@ -141,8 +145,6 @@ class _AvesFilterChipState extends State<AvesFilterChip> {
|
|||
);
|
||||
}
|
||||
|
||||
final borderRadius = AvesFilterChip.borderRadius;
|
||||
|
||||
Widget chip = Container(
|
||||
constraints: BoxConstraints(
|
||||
minWidth: AvesFilterChip.minChipWidth,
|
||||
|
|
|
@ -4,13 +4,13 @@ import 'package:aves/model/settings/settings.dart';
|
|||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class TileExtentManager {
|
||||
final String routeName;
|
||||
final String settingsRouteKey;
|
||||
final int columnCountMin, columnCountDefault;
|
||||
final double spacing, extentMin;
|
||||
final ValueNotifier<double> extentNotifier;
|
||||
|
||||
const TileExtentManager({
|
||||
@required this.routeName,
|
||||
@required this.settingsRouteKey,
|
||||
@required this.columnCountMin,
|
||||
@required this.columnCountDefault,
|
||||
@required this.extentMin,
|
||||
|
@ -26,7 +26,7 @@ class TileExtentManager {
|
|||
final viewportSizeMin = Size.square(extentMin * columnCountMin);
|
||||
viewportSize = Size(max(viewportSize.width, viewportSizeMin.width), max(viewportSize.height, viewportSizeMin.height));
|
||||
|
||||
final oldUserPreferredExtent = settings.getTileExtent(routeName);
|
||||
final oldUserPreferredExtent = settings.getTileExtent(settingsRouteKey);
|
||||
final currentExtent = extentNotifier.value;
|
||||
final targetExtent = userPreferredExtent > 0
|
||||
? userPreferredExtent
|
||||
|
@ -38,7 +38,7 @@ class TileExtentManager {
|
|||
final newExtent = _extentForColumnCount(viewportSize, columnCount);
|
||||
|
||||
if (userPreferredExtent > 0 || oldUserPreferredExtent == 0) {
|
||||
settings.setTileExtent(routeName, newExtent);
|
||||
settings.setTileExtent(settingsRouteKey, newExtent);
|
||||
}
|
||||
if (extentNotifier.value != newExtent) {
|
||||
extentNotifier.value = newExtent;
|
||||
|
|
|
@ -32,7 +32,7 @@ class AlbumPickPage extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _AlbumPickPageState extends State<AlbumPickPage> {
|
||||
final _filterNotifier = ValueNotifier('');
|
||||
final _queryNotifier = ValueNotifier('');
|
||||
|
||||
CollectionSource get source => widget.source;
|
||||
|
||||
|
@ -41,26 +41,29 @@ class _AlbumPickPageState extends State<AlbumPickPage> {
|
|||
Widget appBar = AlbumPickAppBar(
|
||||
copy: widget.copy,
|
||||
actionDelegate: AlbumChipSetActionDelegate(source: source),
|
||||
filterNotifier: _filterNotifier,
|
||||
queryNotifier: _queryNotifier,
|
||||
);
|
||||
|
||||
return Selector<Settings, ChipSortFactor>(
|
||||
selector: (context, s) => s.albumSortFactor,
|
||||
builder: (context, sortFactor, child) {
|
||||
return ValueListenableBuilder<String>(
|
||||
valueListenable: _filterNotifier,
|
||||
builder: (context, filter, child) => FilterGridPage(
|
||||
return FilterGridPage<AlbumFilter>(
|
||||
source: source,
|
||||
appBar: appBar,
|
||||
filterEntries: AlbumListPage.getAlbumEntries(source, filter: filter),
|
||||
filterBuilder: (s) => AlbumFilter(s, source.getUniqueAlbumName(s)),
|
||||
filterEntries: AlbumListPage.getAlbumEntries(source),
|
||||
applyQuery: (filters, query) {
|
||||
if (query == null || query.isEmpty) return filters;
|
||||
query = query.toUpperCase();
|
||||
return filters.where((filter) => filter.uniqueName.toUpperCase().contains(query)).toList();
|
||||
},
|
||||
queryNotifier: _queryNotifier,
|
||||
emptyBuilder: () => EmptyContent(
|
||||
icon: AIcons.album,
|
||||
text: 'No albums',
|
||||
),
|
||||
settingsRouteKey: AlbumListPage.routeName,
|
||||
appBarHeight: AlbumPickAppBar.preferredHeight,
|
||||
onTap: (filter) => Navigator.pop<String>(context, (filter as AlbumFilter)?.album),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -70,14 +73,14 @@ class _AlbumPickPageState extends State<AlbumPickPage> {
|
|||
class AlbumPickAppBar extends StatelessWidget {
|
||||
final bool copy;
|
||||
final AlbumChipSetActionDelegate actionDelegate;
|
||||
final ValueNotifier<String> filterNotifier;
|
||||
final ValueNotifier<String> queryNotifier;
|
||||
|
||||
static const preferredHeight = kToolbarHeight + AlbumFilterBar.preferredHeight;
|
||||
|
||||
const AlbumPickAppBar({
|
||||
@required this.copy,
|
||||
@required this.actionDelegate,
|
||||
@required this.filterNotifier,
|
||||
@required this.queryNotifier,
|
||||
});
|
||||
|
||||
@override
|
||||
|
@ -86,7 +89,7 @@ class AlbumPickAppBar extends StatelessWidget {
|
|||
leading: BackButton(),
|
||||
title: Text(copy ? 'Copy to Album' : 'Move to Album'),
|
||||
bottom: AlbumFilterBar(
|
||||
filterNotifier: filterNotifier,
|
||||
filterNotifier: queryNotifier,
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
|
|
|
@ -33,7 +33,7 @@ class AlbumListPage extends StatelessWidget {
|
|||
animation: androidFileUtils.appNameChangeNotifier,
|
||||
builder: (context, child) => StreamBuilder(
|
||||
stream: source.eventBus.on<AlbumsChangedEvent>(),
|
||||
builder: (context, snapshot) => FilterNavigationPage(
|
||||
builder: (context, snapshot) => FilterNavigationPage<AlbumFilter>(
|
||||
source: source,
|
||||
title: 'Albums',
|
||||
chipSetActionDelegate: AlbumChipSetActionDelegate(source: source),
|
||||
|
@ -44,7 +44,6 @@ class AlbumListPage extends StatelessWidget {
|
|||
ChipAction.delete,
|
||||
],
|
||||
filterEntries: getAlbumEntries(source),
|
||||
filterBuilder: (album) => AlbumFilter(album, source.getUniqueAlbumName(album)),
|
||||
emptyBuilder: () => EmptyContent(
|
||||
icon: AIcons.album,
|
||||
text: 'No albums',
|
||||
|
@ -58,56 +57,53 @@ class AlbumListPage extends StatelessWidget {
|
|||
|
||||
// common with album selection page to move/copy entries
|
||||
|
||||
static Map<String, ImageEntry> getAlbumEntries(CollectionSource source, {String filter}) {
|
||||
final pinned = settings.pinnedFilters.whereType<AlbumFilter>().map((f) => f.album);
|
||||
static Map<AlbumFilter, ImageEntry> getAlbumEntries(CollectionSource source) {
|
||||
final pinned = settings.pinnedFilters.whereType<AlbumFilter>();
|
||||
final entriesByDate = source.sortedEntriesForFilterList;
|
||||
|
||||
AlbumFilter _buildFilter(String album) => AlbumFilter(album, source.getUniqueAlbumName(album));
|
||||
|
||||
// albums are initially sorted by name at the source level
|
||||
var sortedAlbums = source.sortedAlbums;
|
||||
if (filter != null && filter.isNotEmpty) {
|
||||
filter = filter.toUpperCase();
|
||||
sortedAlbums = sortedAlbums.where((album) => source.getUniqueAlbumName(album).toUpperCase().contains(filter)).toList();
|
||||
}
|
||||
var sortedFilters = source.sortedAlbums.map(_buildFilter);
|
||||
|
||||
if (settings.albumSortFactor == ChipSortFactor.name) {
|
||||
final pinnedAlbums = <String>[], regularAlbums = <String>[], appAlbums = <String>[], specialAlbums = <String>[];
|
||||
for (var album in sortedAlbums) {
|
||||
if (pinned.contains(album)) {
|
||||
pinnedAlbums.add(album);
|
||||
final pinnedAlbums = <AlbumFilter>[], regularAlbums = <AlbumFilter>[], appAlbums = <AlbumFilter>[], specialAlbums = <AlbumFilter>[];
|
||||
for (var filter in sortedFilters) {
|
||||
if (pinned.contains(filter)) {
|
||||
pinnedAlbums.add(filter);
|
||||
} else {
|
||||
switch (androidFileUtils.getAlbumType(album)) {
|
||||
switch (androidFileUtils.getAlbumType(filter.album)) {
|
||||
case AlbumType.regular:
|
||||
regularAlbums.add(album);
|
||||
regularAlbums.add(filter);
|
||||
break;
|
||||
case AlbumType.app:
|
||||
appAlbums.add(album);
|
||||
appAlbums.add(filter);
|
||||
break;
|
||||
default:
|
||||
specialAlbums.add(album);
|
||||
specialAlbums.add(filter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Map.fromEntries([...pinnedAlbums, ...specialAlbums, ...appAlbums, ...regularAlbums].map((album) {
|
||||
return Map.fromEntries([...pinnedAlbums, ...specialAlbums, ...appAlbums, ...regularAlbums].map((filter) {
|
||||
return MapEntry(
|
||||
album,
|
||||
entriesByDate.firstWhere((entry) => entry.directory == album, orElse: () => null),
|
||||
filter,
|
||||
entriesByDate.firstWhere((entry) => entry.directory == filter.album, orElse: () => null),
|
||||
);
|
||||
}));
|
||||
}
|
||||
|
||||
if (settings.albumSortFactor == ChipSortFactor.count) {
|
||||
CollectionFilter _buildFilter(String album) => AlbumFilter(album, source.getUniqueAlbumName(album));
|
||||
var filtersWithCount = List.of(sortedAlbums.map((s) => MapEntry(s, source.count(_buildFilter(s)))));
|
||||
final filtersWithCount = List.of(sortedFilters.map((filter) => MapEntry(filter, source.count(filter))));
|
||||
filtersWithCount.sort(FilterNavigationPage.compareChipsByEntryCount);
|
||||
sortedAlbums = filtersWithCount.map((kv) => kv.key).toList();
|
||||
sortedFilters = filtersWithCount.map((kv) => kv.key).toList();
|
||||
}
|
||||
|
||||
final allMapEntries = sortedAlbums.map((album) => MapEntry(
|
||||
album,
|
||||
entriesByDate.firstWhere((entry) => entry.directory == album, orElse: () => null),
|
||||
final allMapEntries = sortedFilters.map((filter) => MapEntry(
|
||||
filter,
|
||||
entriesByDate.firstWhere((entry) => entry.directory == filter.album, orElse: () => null),
|
||||
));
|
||||
final byPin = groupBy<MapEntry<String, ImageEntry>, bool>(allMapEntries, (e) => pinned.contains(e.key));
|
||||
final byPin = groupBy<MapEntry<AlbumFilter, ImageEntry>, bool>(allMapEntries, (e) => pinned.contains(e.key));
|
||||
final pinnedMapEntries = (byPin[true] ?? []);
|
||||
final unpinnedMapEntries = (byPin[false] ?? []);
|
||||
|
||||
|
|
|
@ -49,12 +49,14 @@ class DecoratedFilterChip extends StatelessWidget {
|
|||
entry: entry,
|
||||
extent: extent,
|
||||
);
|
||||
final borderRadius = min<double>(AvesFilterChip.defaultRadius, extent / 4);
|
||||
final titlePadding = min<double>(6.0, extent / 16);
|
||||
return AvesFilterChip(
|
||||
filter: filter,
|
||||
showGenericIcon: false,
|
||||
background: backgroundImage,
|
||||
details: _buildDetails(filter),
|
||||
borderRadius: BorderRadius.all(Radius.circular(borderRadius)),
|
||||
padding: titlePadding,
|
||||
onTap: onTap,
|
||||
onLongPress: onLongPress,
|
||||
|
|
|
@ -20,12 +20,14 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class FilterGridPage extends StatelessWidget {
|
||||
class FilterGridPage<T extends CollectionFilter> extends StatelessWidget {
|
||||
final CollectionSource source;
|
||||
final Widget appBar;
|
||||
final Map<String, ImageEntry> filterEntries;
|
||||
final CollectionFilter Function(String key) filterBuilder;
|
||||
final Map<T, ImageEntry> filterEntries;
|
||||
final ValueNotifier<String> queryNotifier;
|
||||
final Widget Function() emptyBuilder;
|
||||
final String settingsRouteKey;
|
||||
final Iterable<T> Function(Iterable<T> filters, String query) applyQuery;
|
||||
final FilterCallback onTap;
|
||||
final OffsetFilterCallback onLongPress;
|
||||
|
||||
|
@ -39,8 +41,10 @@ class FilterGridPage extends StatelessWidget {
|
|||
@required this.source,
|
||||
@required this.appBar,
|
||||
@required this.filterEntries,
|
||||
@required this.filterBuilder,
|
||||
@required this.queryNotifier,
|
||||
this.applyQuery,
|
||||
@required this.emptyBuilder,
|
||||
this.settingsRouteKey,
|
||||
double appBarHeight = kToolbarHeight,
|
||||
@required this.onTap,
|
||||
this.onLongPress,
|
||||
|
@ -48,13 +52,8 @@ class FilterGridPage extends StatelessWidget {
|
|||
_appBarHeightNotifier.value = appBarHeight;
|
||||
}
|
||||
|
||||
List<String> get filterKeys => filterEntries.keys.toList();
|
||||
|
||||
static const Color detailColor = Color(0xFFE0E0E0);
|
||||
|
||||
// TODO TLAD enforce max extent?
|
||||
// static const double maxCrossAxisExtent = 180;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MediaQueryDataProvider(
|
||||
|
@ -68,7 +67,7 @@ class FilterGridPage extends StatelessWidget {
|
|||
if (viewportSize.isEmpty) return SizedBox.shrink();
|
||||
|
||||
final tileExtentManager = TileExtentManager(
|
||||
routeName: context.currentRouteName,
|
||||
settingsRouteKey: settingsRouteKey ?? context.currentRouteName,
|
||||
columnCountMin: 2,
|
||||
columnCountDefault: 2,
|
||||
extentMin: 60,
|
||||
|
@ -80,8 +79,15 @@ class FilterGridPage extends StatelessWidget {
|
|||
valueListenable: _tileExtentNotifier,
|
||||
builder: (context, tileExtent, child) {
|
||||
final columnCount = tileExtentManager.getEffectiveColumnCountForExtent(viewportSize, tileExtent);
|
||||
|
||||
return ValueListenableBuilder<String>(
|
||||
valueListenable: queryNotifier,
|
||||
builder: (context, query, child) {
|
||||
final allFilters = filterEntries.keys;
|
||||
final visibleFilters = (applyQuery != null ? applyQuery(allFilters, query) : allFilters).toList();
|
||||
|
||||
final scrollView = AnimationLimiter(
|
||||
child: _buildDraggableScrollView(_buildScrollView(context, columnCount)),
|
||||
child: _buildDraggableScrollView(_buildScrollView(context, columnCount, visibleFilters)),
|
||||
);
|
||||
|
||||
return GridScaleGestureDetector<FilterGridItem>(
|
||||
|
@ -105,17 +111,23 @@ class FilterGridPage extends StatelessWidget {
|
|||
);
|
||||
},
|
||||
getScaledItemTileRect: (context, item) {
|
||||
// TODO TLAD
|
||||
return Rect.zero;
|
||||
final index = visibleFilters.indexOf(item.filter);
|
||||
final column = index % columnCount;
|
||||
final row = (index / columnCount).floor();
|
||||
final left = tileExtent * column + spacing * (column - 1);
|
||||
final top = tileExtent * row + spacing * (row - 1);
|
||||
return Rect.fromLTWH(left, top, tileExtent, tileExtent);
|
||||
},
|
||||
onScaled: (item) {
|
||||
// TODO TLAD
|
||||
// TODO TLAD highlight scaled item
|
||||
},
|
||||
child: scrollView,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -148,14 +160,14 @@ class FilterGridPage extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
ScrollView _buildScrollView(BuildContext context, int columnCount) {
|
||||
ScrollView _buildScrollView(BuildContext context, int columnCount, List<T> visibleFilters) {
|
||||
final pinnedFilters = settings.pinnedFilters;
|
||||
return CustomScrollView(
|
||||
key: _scrollableKey,
|
||||
controller: PrimaryScrollController.of(context),
|
||||
slivers: [
|
||||
appBar,
|
||||
filterKeys.isEmpty
|
||||
visibleFilters.isEmpty
|
||||
? SliverFillRemaining(
|
||||
child: Selector<MediaQueryData, double>(
|
||||
selector: (context, mq) => mq.viewInsets.bottom,
|
||||
|
@ -171,13 +183,12 @@ class FilterGridPage extends StatelessWidget {
|
|||
: SliverGrid(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, i) {
|
||||
final key = filterKeys[i];
|
||||
final filter = filterBuilder(key);
|
||||
final entry = filterEntries[key];
|
||||
final filter = visibleFilters[i];
|
||||
final entry = filterEntries[filter];
|
||||
final child = MetaData(
|
||||
metaData: ScalerMetadata(FilterGridItem(filter, entry)),
|
||||
child: DecoratedFilterChip(
|
||||
key: Key(key),
|
||||
key: Key(filter.key),
|
||||
source: source,
|
||||
filter: filter,
|
||||
entry: entry,
|
||||
|
@ -200,7 +211,7 @@ class FilterGridPage extends StatelessWidget {
|
|||
),
|
||||
);
|
||||
},
|
||||
childCount: filterKeys.length,
|
||||
childCount: visibleFilters.length,
|
||||
),
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: columnCount,
|
||||
|
@ -221,8 +232,8 @@ class FilterGridPage extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
class FilterGridItem {
|
||||
final CollectionFilter filter;
|
||||
class FilterGridItem<T extends CollectionFilter> {
|
||||
final T filter;
|
||||
final ImageEntry entry;
|
||||
|
||||
const FilterGridItem(this.filter, this.entry);
|
||||
|
|
|
@ -18,20 +18,18 @@ import 'package:aves/widgets/filter_grids/common/chip_set_action_delegate.dart';
|
|||
import 'package:aves/widgets/filter_grids/common/filter_grid_page.dart';
|
||||
import 'package:aves/widgets/search/search_button.dart';
|
||||
import 'package:aves/widgets/search/search_delegate.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
|
||||
class FilterNavigationPage extends StatelessWidget {
|
||||
class FilterNavigationPage<T extends CollectionFilter> extends StatelessWidget {
|
||||
final CollectionSource source;
|
||||
final String title;
|
||||
final ChipSetActionDelegate chipSetActionDelegate;
|
||||
final ChipActionDelegate chipActionDelegate;
|
||||
final Map<String, ImageEntry> filterEntries;
|
||||
final CollectionFilter Function(String key) filterBuilder;
|
||||
final Map<T, ImageEntry> filterEntries;
|
||||
final Widget Function() emptyBuilder;
|
||||
final List<ChipAction> Function(CollectionFilter filter) chipActionsBuilder;
|
||||
final List<ChipAction> Function(T filter) chipActionsBuilder;
|
||||
|
||||
const FilterNavigationPage({
|
||||
@required this.source,
|
||||
|
@ -40,13 +38,12 @@ class FilterNavigationPage extends StatelessWidget {
|
|||
@required this.chipActionDelegate,
|
||||
@required this.chipActionsBuilder,
|
||||
@required this.filterEntries,
|
||||
@required this.filterBuilder,
|
||||
@required this.emptyBuilder,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FilterGridPage(
|
||||
return FilterGridPage<T>(
|
||||
source: source,
|
||||
appBar: SliverAppBar(
|
||||
title: TappableAppBarTitle(
|
||||
|
@ -61,7 +58,7 @@ class FilterNavigationPage extends StatelessWidget {
|
|||
floating: true,
|
||||
),
|
||||
filterEntries: filterEntries,
|
||||
filterBuilder: filterBuilder,
|
||||
queryNotifier: ValueNotifier(''),
|
||||
emptyBuilder: () => ValueListenableBuilder<SourceState>(
|
||||
valueListenable: source.stateNotifier,
|
||||
builder: (context, sourceState, child) {
|
||||
|
@ -84,7 +81,7 @@ class FilterNavigationPage extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
Future<void> _showMenu(BuildContext context, CollectionFilter filter, Offset tapPosition) async {
|
||||
Future<void> _showMenu(BuildContext context, T filter, Offset tapPosition) async {
|
||||
final RenderBox overlay = Overlay.of(context).context.findRenderObject();
|
||||
final touchArea = Size(40, 40);
|
||||
final selectedAction = await showMenu<ChipAction>(
|
||||
|
@ -144,13 +141,13 @@ class FilterNavigationPage extends StatelessWidget {
|
|||
));
|
||||
}
|
||||
|
||||
static int compareChipsByDate(MapEntry<String, ImageEntry> a, MapEntry<String, ImageEntry> b) {
|
||||
static int compareChipsByDate(MapEntry<CollectionFilter, ImageEntry> a, MapEntry<CollectionFilter, ImageEntry> b) {
|
||||
final c = b.value.bestDate?.compareTo(a.value.bestDate) ?? -1;
|
||||
return c != 0 ? c : compareAsciiUpperCase(a.key, b.key);
|
||||
return c != 0 ? c : a.key.compareTo(b.key);
|
||||
}
|
||||
|
||||
static int compareChipsByEntryCount(MapEntry<String, num> a, MapEntry<String, num> b) {
|
||||
static int compareChipsByEntryCount(MapEntry<CollectionFilter, num> a, MapEntry<CollectionFilter, num> b) {
|
||||
final c = b.value.compareTo(a.value) ?? -1;
|
||||
return c != 0 ? c : compareAsciiUpperCase(a.key, b.key);
|
||||
return c != 0 ? c : a.key.compareTo(b.key);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,6 @@ class CountryListPage extends StatelessWidget {
|
|||
settings.pinnedFilters.contains(filter) ? ChipAction.unpin : ChipAction.pin,
|
||||
],
|
||||
filterEntries: _getCountryEntries(),
|
||||
filterBuilder: _buildFilter,
|
||||
emptyBuilder: () => EmptyContent(
|
||||
icon: AIcons.location,
|
||||
text: 'No countries',
|
||||
|
@ -50,31 +49,29 @@ class CountryListPage extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
CollectionFilter _buildFilter(String location) => LocationFilter(LocationLevel.country, location);
|
||||
|
||||
Map<String, ImageEntry> _getCountryEntries() {
|
||||
final pinned = settings.pinnedFilters.whereType<LocationFilter>().map((f) => f.countryNameAndCode);
|
||||
Map<LocationFilter, ImageEntry> _getCountryEntries() {
|
||||
final pinned = settings.pinnedFilters.whereType<LocationFilter>();
|
||||
|
||||
final entriesByDate = source.sortedEntriesForFilterList;
|
||||
// countries are initially sorted by name at the source level
|
||||
var sortedCountries = source.sortedCountries;
|
||||
var sortedFilters = source.sortedCountries.map((location) => LocationFilter(LocationLevel.country, location));
|
||||
if (settings.countrySortFactor == ChipSortFactor.count) {
|
||||
var filtersWithCount = List.of(sortedCountries.map((s) => MapEntry(s, source.count(_buildFilter(s)))));
|
||||
final filtersWithCount = List.of(sortedFilters.map((filter) => MapEntry(filter, source.count(filter))));
|
||||
filtersWithCount.sort(FilterNavigationPage.compareChipsByEntryCount);
|
||||
sortedCountries = filtersWithCount.map((kv) => kv.key).toList();
|
||||
sortedFilters = filtersWithCount.map((kv) => kv.key).toList();
|
||||
}
|
||||
|
||||
final locatedEntries = entriesByDate.where((entry) => entry.isLocated);
|
||||
final allMapEntries = sortedCountries.map((countryNameAndCode) {
|
||||
final split = countryNameAndCode.split(LocationFilter.locationSeparator);
|
||||
final allMapEntries = sortedFilters.map((filter) {
|
||||
final split = filter.countryNameAndCode.split(LocationFilter.locationSeparator);
|
||||
ImageEntry entry;
|
||||
if (split.length > 1) {
|
||||
final countryCode = split[1];
|
||||
entry = locatedEntries.firstWhere((entry) => entry.addressDetails.countryCode == countryCode, orElse: () => null);
|
||||
}
|
||||
return MapEntry(countryNameAndCode, entry);
|
||||
return MapEntry(filter, entry);
|
||||
});
|
||||
final byPin = groupBy<MapEntry<String, ImageEntry>, bool>(allMapEntries, (e) => pinned.contains(e.key));
|
||||
final byPin = groupBy<MapEntry<LocationFilter, ImageEntry>, bool>(allMapEntries, (e) => pinned.contains(e.key));
|
||||
final pinnedMapEntries = (byPin[true] ?? []);
|
||||
final unpinnedMapEntries = (byPin[false] ?? []);
|
||||
|
||||
|
|
|
@ -39,7 +39,6 @@ class TagListPage extends StatelessWidget {
|
|||
settings.pinnedFilters.contains(filter) ? ChipAction.unpin : ChipAction.pin,
|
||||
],
|
||||
filterEntries: _getTagEntries(),
|
||||
filterBuilder: _buildFilter,
|
||||
emptyBuilder: () => EmptyContent(
|
||||
icon: AIcons.tag,
|
||||
text: 'No tags',
|
||||
|
@ -50,25 +49,23 @@ class TagListPage extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
CollectionFilter _buildFilter(String tag) => TagFilter(tag);
|
||||
|
||||
Map<String, ImageEntry> _getTagEntries() {
|
||||
final pinned = settings.pinnedFilters.whereType<TagFilter>().map((f) => f.tag);
|
||||
Map<TagFilter, ImageEntry> _getTagEntries() {
|
||||
final pinned = settings.pinnedFilters.whereType<TagFilter>();
|
||||
|
||||
final entriesByDate = source.sortedEntriesForFilterList;
|
||||
// tags are initially sorted by name at the source level
|
||||
var sortedTags = source.sortedTags;
|
||||
var sortedFilters = source.sortedTags.map((tag) => TagFilter(tag));
|
||||
if (settings.tagSortFactor == ChipSortFactor.count) {
|
||||
var filtersWithCount = List.of(sortedTags.map((s) => MapEntry(s, source.count(_buildFilter(s)))));
|
||||
final filtersWithCount = List.of(sortedFilters.map((filter) => MapEntry(filter, source.count(filter))));
|
||||
filtersWithCount.sort(FilterNavigationPage.compareChipsByEntryCount);
|
||||
sortedTags = filtersWithCount.map((kv) => kv.key).toList();
|
||||
sortedFilters = filtersWithCount.map((kv) => kv.key).toList();
|
||||
}
|
||||
|
||||
final allMapEntries = sortedTags.map((tag) => MapEntry(
|
||||
tag,
|
||||
entriesByDate.firstWhere((entry) => entry.xmpSubjects.contains(tag), orElse: () => null),
|
||||
final allMapEntries = sortedFilters.map((filter) => MapEntry(
|
||||
filter,
|
||||
entriesByDate.firstWhere((entry) => entry.xmpSubjects.contains(filter.tag), orElse: () => null),
|
||||
));
|
||||
final byPin = groupBy<MapEntry<String, ImageEntry>, bool>(allMapEntries, (e) => pinned.contains(e.key));
|
||||
final byPin = groupBy<MapEntry<TagFilter, ImageEntry>, bool>(allMapEntries, (e) => pinned.contains(e.key));
|
||||
final pinnedMapEntries = (byPin[true] ?? []);
|
||||
final unpinnedMapEntries = (byPin[false] ?? []);
|
||||
|
||||
|
|
Loading…
Reference in a new issue