sort chips on countries/tags pages
This commit is contained in:
parent
78ba136a21
commit
7e530aed74
7 changed files with 193 additions and 100 deletions
|
@ -29,6 +29,8 @@ class Settings extends ChangeNotifier {
|
|||
|
||||
// filter grids
|
||||
static const albumSortFactorKey = 'album_sort_factor';
|
||||
static const countrySortFactorKey = 'country_sort_factor';
|
||||
static const tagSortFactorKey = 'tag_sort_factor';
|
||||
|
||||
// info
|
||||
static const infoMapStyleKey = 'info_map_style';
|
||||
|
@ -84,6 +86,14 @@ class Settings extends ChangeNotifier {
|
|||
|
||||
set albumSortFactor(ChipSortFactor newValue) => setAndNotify(albumSortFactorKey, newValue.toString());
|
||||
|
||||
ChipSortFactor get countrySortFactor => getEnumOrDefault(countrySortFactorKey, ChipSortFactor.name, ChipSortFactor.values);
|
||||
|
||||
set countrySortFactor(ChipSortFactor newValue) => setAndNotify(countrySortFactorKey, newValue.toString());
|
||||
|
||||
ChipSortFactor get tagSortFactor => getEnumOrDefault(tagSortFactorKey, ChipSortFactor.name, ChipSortFactor.values);
|
||||
|
||||
set tagSortFactor(ChipSortFactor newValue) => setAndNotify(tagSortFactorKey, newValue.toString());
|
||||
|
||||
// info
|
||||
|
||||
EntryMapStyle get infoMapStyle => getEnumOrDefault(infoMapStyleKey, EntryMapStyle.stamenWatercolor, EntryMapStyle.values);
|
||||
|
|
|
@ -83,19 +83,6 @@ mixin LocationMixin on SourceBase {
|
|||
invalidateFilterEntryCounts();
|
||||
eventBus.fire(LocationsChangedEvent());
|
||||
}
|
||||
|
||||
Map<String, ImageEntry> getCountryEntries() {
|
||||
final locatedEntries = sortedEntriesForFilterList.where((entry) => entry.isLocated);
|
||||
return Map.fromEntries(sortedCountries.map((countryNameAndCode) {
|
||||
final split = 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);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
class AddressMetadataChangedEvent {}
|
||||
|
|
|
@ -59,14 +59,6 @@ mixin TagMixin on SourceBase {
|
|||
invalidateFilterEntryCounts();
|
||||
eventBus.fire(TagsChangedEvent());
|
||||
}
|
||||
|
||||
Map<String, ImageEntry> getTagEntries() {
|
||||
final entries = sortedEntriesForFilterList;
|
||||
return Map.fromEntries(sortedTags.map((tag) => MapEntry(
|
||||
tag,
|
||||
entries.firstWhere((entry) => entry.xmpSubjects.contains(tag), orElse: () => null),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
class CatalogMetadataChangedEvent {}
|
||||
|
|
|
@ -5,15 +5,11 @@ import 'package:aves/model/source/album.dart';
|
|||
import 'package:aves/model/source/collection_source.dart';
|
||||
import 'package:aves/model/source/enums.dart';
|
||||
import 'package:aves/utils/android_file_utils.dart';
|
||||
import 'package:aves/utils/durations.dart';
|
||||
import 'package:aves/widgets/collection/empty.dart';
|
||||
import 'package:aves/widgets/common/aves_selection_dialog.dart';
|
||||
import 'package:aves/widgets/common/icons.dart';
|
||||
import 'package:aves/widgets/common/menu_row.dart';
|
||||
import 'package:aves/widgets/filter_grids/filter_grid_page.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class AlbumListPage extends StatelessWidget {
|
||||
|
@ -27,51 +23,29 @@ class AlbumListPage extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
return Selector<Settings, ChipSortFactor>(
|
||||
selector: (context, s) => s.albumSortFactor,
|
||||
builder: (context, albumSortFactor, child) {
|
||||
builder: (context, sortFactor, child) {
|
||||
return AnimatedBuilder(
|
||||
animation: androidFileUtils.appNameChangeNotifier,
|
||||
builder: (context, child) => StreamBuilder(
|
||||
stream: source.eventBus.on<AlbumsChangedEvent>(),
|
||||
builder: (context, snapshot) {
|
||||
return FilterNavigationPage(
|
||||
source: source,
|
||||
title: 'Albums',
|
||||
actions: _buildActions(context),
|
||||
filterEntries: getAlbumEntries(source),
|
||||
filterBuilder: (s) => AlbumFilter(s, source.getUniqueAlbumName(s)),
|
||||
emptyBuilder: () => EmptyContent(
|
||||
icon: AIcons.album,
|
||||
text: 'No albums',
|
||||
),
|
||||
);
|
||||
},
|
||||
builder: (context, snapshot) => FilterNavigationPage(
|
||||
source: source,
|
||||
title: 'Albums',
|
||||
onChipActionSelected: _onChipActionSelected,
|
||||
filterEntries: getAlbumEntries(source),
|
||||
filterBuilder: (s) => AlbumFilter(s, source.getUniqueAlbumName(s)),
|
||||
emptyBuilder: () => EmptyContent(
|
||||
icon: AIcons.album,
|
||||
text: 'No albums',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildActions(BuildContext context) {
|
||||
return [
|
||||
PopupMenuButton<ChipAction>(
|
||||
key: Key('appbar-menu-button'),
|
||||
itemBuilder: (context) {
|
||||
return [
|
||||
PopupMenuItem(
|
||||
key: Key('menu-sort'),
|
||||
value: ChipAction.sort,
|
||||
child: MenuRow(text: 'Sort...', icon: AIcons.sort),
|
||||
),
|
||||
];
|
||||
},
|
||||
onSelected: (action) => _onChipActionSelected(context, action),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
void _onChipActionSelected(BuildContext context, ChipAction action) async {
|
||||
// wait for the popup menu to hide before proceeding with the action
|
||||
await Future.delayed(Durations.popupMenuAnimation * timeDilation);
|
||||
switch (action) {
|
||||
case ChipAction.sort:
|
||||
final factor = await showDialog<ChipSortFactor>(
|
||||
|
@ -96,20 +70,17 @@ class AlbumListPage extends StatelessWidget {
|
|||
|
||||
static Map<String, ImageEntry> getAlbumEntries(CollectionSource source) {
|
||||
final entriesByDate = source.sortedEntriesForFilterList;
|
||||
final albumEntries = source.sortedAlbums.map((album) {
|
||||
return MapEntry(
|
||||
album,
|
||||
entriesByDate.firstWhere((entry) => entry.directory == album, orElse: () => null),
|
||||
);
|
||||
}).toList();
|
||||
final albums = source.sortedAlbums
|
||||
.map((album) => MapEntry(
|
||||
album,
|
||||
entriesByDate.firstWhere((entry) => entry.directory == album, orElse: () => null),
|
||||
))
|
||||
.toList();
|
||||
|
||||
switch (settings.albumSortFactor) {
|
||||
case ChipSortFactor.date:
|
||||
albumEntries.sort((a, b) {
|
||||
final c = b.value.bestDate?.compareTo(a.value.bestDate) ?? -1;
|
||||
return c != 0 ? c : compareAsciiUpperCase(a.key, b.key);
|
||||
});
|
||||
return Map.fromEntries(albumEntries);
|
||||
albums.sort(FilterNavigationPage.compareChipByDate);
|
||||
return Map.fromEntries(albums);
|
||||
case ChipSortFactor.name:
|
||||
default:
|
||||
final regularAlbums = <String>[], appAlbums = <String>[], specialAlbums = <String>[];
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
import 'package:aves/model/filters/location.dart';
|
||||
import 'package:aves/model/image_entry.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/model/source/collection_source.dart';
|
||||
import 'package:aves/model/source/enums.dart';
|
||||
import 'package:aves/model/source/location.dart';
|
||||
import 'package:aves/widgets/collection/empty.dart';
|
||||
import 'package:aves/widgets/common/aves_selection_dialog.dart';
|
||||
import 'package:aves/widgets/common/icons.dart';
|
||||
import 'package:aves/widgets/filter_grids/filter_grid_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class CountryListPage extends StatelessWidget {
|
||||
static const routeName = '/countries';
|
||||
|
@ -15,18 +20,67 @@ class CountryListPage extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return StreamBuilder(
|
||||
stream: source.eventBus.on<LocationsChangedEvent>(),
|
||||
builder: (context, snapshot) => FilterNavigationPage(
|
||||
source: source,
|
||||
title: 'Countries',
|
||||
filterEntries: source.getCountryEntries(),
|
||||
filterBuilder: (s) => LocationFilter(LocationLevel.country, s),
|
||||
emptyBuilder: () => EmptyContent(
|
||||
icon: AIcons.location,
|
||||
text: 'No countries',
|
||||
),
|
||||
),
|
||||
return Selector<Settings, ChipSortFactor>(
|
||||
selector: (context, s) => s.countrySortFactor,
|
||||
builder: (context, sortFactor, child) {
|
||||
return StreamBuilder(
|
||||
stream: source.eventBus.on<LocationsChangedEvent>(),
|
||||
builder: (context, snapshot) => FilterNavigationPage(
|
||||
source: source,
|
||||
title: 'Countries',
|
||||
onChipActionSelected: _onChipActionSelected,
|
||||
filterEntries: _getCountryEntries(),
|
||||
filterBuilder: (s) => LocationFilter(LocationLevel.country, s),
|
||||
emptyBuilder: () => EmptyContent(
|
||||
icon: AIcons.location,
|
||||
text: 'No countries',
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _onChipActionSelected(BuildContext context, ChipAction action) async {
|
||||
switch (action) {
|
||||
case ChipAction.sort:
|
||||
final factor = await showDialog<ChipSortFactor>(
|
||||
context: context,
|
||||
builder: (context) => AvesSelectionDialog<ChipSortFactor>(
|
||||
initialValue: settings.countrySortFactor,
|
||||
options: {
|
||||
ChipSortFactor.date: 'By date',
|
||||
ChipSortFactor.name: 'By name',
|
||||
},
|
||||
title: 'Sort',
|
||||
),
|
||||
);
|
||||
if (factor != null) {
|
||||
settings.countrySortFactor = factor;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, ImageEntry> _getCountryEntries() {
|
||||
final entriesByDate = source.sortedEntriesForFilterList;
|
||||
final locatedEntries = entriesByDate.where((entry) => entry.isLocated);
|
||||
final countries = source.sortedCountries.map((countryNameAndCode) {
|
||||
final split = 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);
|
||||
}).toList();
|
||||
|
||||
switch (settings.countrySortFactor) {
|
||||
case ChipSortFactor.date:
|
||||
countries.sort(FilterNavigationPage.compareChipByDate);
|
||||
break;
|
||||
case ChipSortFactor.name:
|
||||
}
|
||||
return Map.fromEntries(countries);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,17 +13,21 @@ import 'package:aves/widgets/common/app_bar_title.dart';
|
|||
import 'package:aves/widgets/common/aves_filter_chip.dart';
|
||||
import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart';
|
||||
import 'package:aves/widgets/common/double_back_pop.dart';
|
||||
import 'package:aves/widgets/common/icons.dart';
|
||||
import 'package:aves/widgets/common/menu_row.dart';
|
||||
import 'package:aves/widgets/drawer/app_drawer.dart';
|
||||
import 'package:aves/widgets/filter_grids/decorated_filter_chip.dart';
|
||||
import 'package:aves/widgets/filter_grids/search_button.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class FilterNavigationPage extends StatelessWidget {
|
||||
final CollectionSource source;
|
||||
final String title;
|
||||
final List<Widget> actions;
|
||||
final void Function(BuildContext context, ChipAction action) onChipActionSelected;
|
||||
final Map<String, ImageEntry> filterEntries;
|
||||
final CollectionFilter Function(String key) filterBuilder;
|
||||
final Widget Function() emptyBuilder;
|
||||
|
@ -31,7 +35,7 @@ class FilterNavigationPage extends StatelessWidget {
|
|||
const FilterNavigationPage({
|
||||
@required this.source,
|
||||
@required this.title,
|
||||
this.actions,
|
||||
@required this.onChipActionSelected,
|
||||
@required this.filterEntries,
|
||||
@required this.filterBuilder,
|
||||
@required this.emptyBuilder,
|
||||
|
@ -49,10 +53,7 @@ class FilterNavigationPage extends StatelessWidget {
|
|||
source: source,
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
SearchButton(source),
|
||||
...(actions ?? []),
|
||||
],
|
||||
actions: _buildActions(context),
|
||||
titleSpacing: 0,
|
||||
floating: true,
|
||||
),
|
||||
|
@ -80,6 +81,29 @@ class FilterNavigationPage extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildActions(BuildContext context) {
|
||||
return [
|
||||
SearchButton(source),
|
||||
PopupMenuButton<ChipAction>(
|
||||
key: Key('appbar-menu-button'),
|
||||
itemBuilder: (context) {
|
||||
return [
|
||||
PopupMenuItem(
|
||||
key: Key('menu-sort'),
|
||||
value: ChipAction.sort,
|
||||
child: MenuRow(text: 'Sort...', icon: AIcons.sort),
|
||||
),
|
||||
];
|
||||
},
|
||||
onSelected: (action) async {
|
||||
// wait for the popup menu to hide before proceeding with the action
|
||||
await Future.delayed(Durations.popupMenuAnimation * timeDilation);
|
||||
onChipActionSelected(context, action);
|
||||
},
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
void _goToSearch(BuildContext context) {
|
||||
Navigator.push(
|
||||
context,
|
||||
|
@ -89,6 +113,11 @@ class FilterNavigationPage extends StatelessWidget {
|
|||
),
|
||||
));
|
||||
}
|
||||
|
||||
static int compareChipByDate(MapEntry<String, ImageEntry> a, MapEntry<String, ImageEntry> b) {
|
||||
final c = b.value.bestDate?.compareTo(a.value.bestDate) ?? -1;
|
||||
return c != 0 ? c : compareAsciiUpperCase(a.key, b.key);
|
||||
}
|
||||
}
|
||||
|
||||
class FilterGridPage extends StatelessWidget {
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
import 'package:aves/model/filters/tag.dart';
|
||||
import 'package:aves/model/image_entry.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/model/source/collection_source.dart';
|
||||
import 'package:aves/model/source/enums.dart';
|
||||
import 'package:aves/model/source/tag.dart';
|
||||
import 'package:aves/widgets/collection/empty.dart';
|
||||
import 'package:aves/widgets/common/aves_selection_dialog.dart';
|
||||
import 'package:aves/widgets/common/icons.dart';
|
||||
import 'package:aves/widgets/filter_grids/filter_grid_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class TagListPage extends StatelessWidget {
|
||||
static const routeName = '/tags';
|
||||
|
@ -15,18 +20,63 @@ class TagListPage extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return StreamBuilder(
|
||||
stream: source.eventBus.on<TagsChangedEvent>(),
|
||||
builder: (context, snapshot) => FilterNavigationPage(
|
||||
source: source,
|
||||
title: 'Tags',
|
||||
filterEntries: source.getTagEntries(),
|
||||
filterBuilder: (s) => TagFilter(s),
|
||||
emptyBuilder: () => EmptyContent(
|
||||
icon: AIcons.tag,
|
||||
text: 'No tags',
|
||||
),
|
||||
),
|
||||
return Selector<Settings, ChipSortFactor>(
|
||||
selector: (context, s) => s.tagSortFactor,
|
||||
builder: (context, sortFactor, child) {
|
||||
return StreamBuilder(
|
||||
stream: source.eventBus.on<TagsChangedEvent>(),
|
||||
builder: (context, snapshot) => FilterNavigationPage(
|
||||
source: source,
|
||||
title: 'Tags',
|
||||
onChipActionSelected: _onChipActionSelected,
|
||||
filterEntries: _getTagEntries(),
|
||||
filterBuilder: (s) => TagFilter(s),
|
||||
emptyBuilder: () => EmptyContent(
|
||||
icon: AIcons.tag,
|
||||
text: 'No tags',
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _onChipActionSelected(BuildContext context, ChipAction action) async {
|
||||
switch (action) {
|
||||
case ChipAction.sort:
|
||||
final factor = await showDialog<ChipSortFactor>(
|
||||
context: context,
|
||||
builder: (context) => AvesSelectionDialog<ChipSortFactor>(
|
||||
initialValue: settings.tagSortFactor,
|
||||
options: {
|
||||
ChipSortFactor.date: 'By date',
|
||||
ChipSortFactor.name: 'By name',
|
||||
},
|
||||
title: 'Sort',
|
||||
),
|
||||
);
|
||||
if (factor != null) {
|
||||
settings.tagSortFactor = factor;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, ImageEntry> _getTagEntries() {
|
||||
final entriesByDate = source.sortedEntriesForFilterList;
|
||||
final tags = source.sortedTags
|
||||
.map((tag) => MapEntry(
|
||||
tag,
|
||||
entriesByDate.firstWhere((entry) => entry.xmpSubjects.contains(tag), orElse: () => null),
|
||||
))
|
||||
.toList();
|
||||
|
||||
switch (settings.tagSortFactor) {
|
||||
case ChipSortFactor.date:
|
||||
tags.sort(FilterNavigationPage.compareChipByDate);
|
||||
break;
|
||||
case ChipSortFactor.name:
|
||||
}
|
||||
return Map.fromEntries(tags);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue