view settings allow changing the sort order
This commit is contained in:
parent
d926d943fd
commit
f6d8680a17
44 changed files with 615 additions and 403 deletions
|
@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
- Collection: view settings allow changing the sort order (aka ascending/descending)
|
||||||
- Collection / Info: edit title via IPTC / XMP
|
- Collection / Info: edit title via IPTC / XMP
|
||||||
- Albums / Countries / Tags: size displayed in list view details, sort by size
|
- Albums / Countries / Tags: size displayed in list view details, sort by size
|
||||||
- Search: `undated` and `untitled` filters
|
- Search: `undated` and `untitled` filters
|
||||||
|
|
|
@ -417,6 +417,7 @@
|
||||||
"viewDialogTabSort": "Sort",
|
"viewDialogTabSort": "Sort",
|
||||||
"viewDialogTabGroup": "Group",
|
"viewDialogTabGroup": "Group",
|
||||||
"viewDialogTabLayout": "Layout",
|
"viewDialogTabLayout": "Layout",
|
||||||
|
"viewDialogReverseSortOrder": "Reverse sort order",
|
||||||
|
|
||||||
"tileLayoutGrid": "Grid",
|
"tileLayoutGrid": "Grid",
|
||||||
"tileLayoutList": "List",
|
"tileLayoutList": "List",
|
||||||
|
@ -570,6 +571,15 @@
|
||||||
"sortByAlbumFileName": "By album & file name",
|
"sortByAlbumFileName": "By album & file name",
|
||||||
"sortByRating": "By rating",
|
"sortByRating": "By rating",
|
||||||
|
|
||||||
|
"sortOrderNewestFirst": "Newest first",
|
||||||
|
"sortOrderOldestFirst": "Oldest first",
|
||||||
|
"sortOrderAtoZ": "A to Z",
|
||||||
|
"sortOrderZtoA": "Z to A",
|
||||||
|
"sortOrderHighestFirst": "Highest first",
|
||||||
|
"sortOrderLowestFirst": "Lowest first",
|
||||||
|
"sortOrderLargestFirst": "Largest first",
|
||||||
|
"sortOrderSmallestFirst": "Smallest first",
|
||||||
|
|
||||||
"albumGroupTier": "By tier",
|
"albumGroupTier": "By tier",
|
||||||
"albumGroupVolume": "By storage volume",
|
"albumGroupVolume": "By storage volume",
|
||||||
"albumGroupNone": "Do not group",
|
"albumGroupNone": "Do not group",
|
||||||
|
|
|
@ -5,7 +5,7 @@ import 'package:aves/model/filters/mime.dart';
|
||||||
import 'package:aves/model/filters/recent.dart';
|
import 'package:aves/model/filters/recent.dart';
|
||||||
import 'package:aves/model/naming_pattern.dart';
|
import 'package:aves/model/naming_pattern.dart';
|
||||||
import 'package:aves/model/settings/enums/enums.dart';
|
import 'package:aves/model/settings/enums/enums.dart';
|
||||||
import 'package:aves/model/source/enums.dart';
|
import 'package:aves/model/source/enums/enums.dart';
|
||||||
import 'package:aves/widgets/filter_grids/albums_page.dart';
|
import 'package:aves/widgets/filter_grids/albums_page.dart';
|
||||||
import 'package:aves/widgets/filter_grids/countries_page.dart';
|
import 'package:aves/widgets/filter_grids/countries_page.dart';
|
||||||
import 'package:aves/widgets/filter_grids/tags_page.dart';
|
import 'package:aves/widgets/filter_grids/tags_page.dart';
|
||||||
|
|
|
@ -9,7 +9,7 @@ import 'package:aves/model/filters/filters.dart';
|
||||||
import 'package:aves/model/settings/defaults.dart';
|
import 'package:aves/model/settings/defaults.dart';
|
||||||
import 'package:aves/model/settings/enums/enums.dart';
|
import 'package:aves/model/settings/enums/enums.dart';
|
||||||
import 'package:aves/model/settings/enums/map_style.dart';
|
import 'package:aves/model/settings/enums/map_style.dart';
|
||||||
import 'package:aves/model/source/enums.dart';
|
import 'package:aves/model/source/enums/enums.dart';
|
||||||
import 'package:aves/services/common/optional_event_channel.dart';
|
import 'package:aves/services/common/optional_event_channel.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
import 'package:aves_map/aves_map.dart';
|
import 'package:aves_map/aves_map.dart';
|
||||||
|
@ -75,6 +75,7 @@ class Settings extends ChangeNotifier {
|
||||||
// collection
|
// collection
|
||||||
static const collectionGroupFactorKey = 'collection_group_factor';
|
static const collectionGroupFactorKey = 'collection_group_factor';
|
||||||
static const collectionSortFactorKey = 'collection_sort_factor';
|
static const collectionSortFactorKey = 'collection_sort_factor';
|
||||||
|
static const collectionSortReverseKey = 'collection_sort_reverse';
|
||||||
static const collectionBrowsingQuickActionsKey = 'collection_browsing_quick_actions';
|
static const collectionBrowsingQuickActionsKey = 'collection_browsing_quick_actions';
|
||||||
static const collectionSelectionQuickActionsKey = 'collection_selection_quick_actions';
|
static const collectionSelectionQuickActionsKey = 'collection_selection_quick_actions';
|
||||||
static const showThumbnailFavouriteKey = 'show_thumbnail_favourite';
|
static const showThumbnailFavouriteKey = 'show_thumbnail_favourite';
|
||||||
|
@ -90,6 +91,9 @@ class Settings extends ChangeNotifier {
|
||||||
static const albumSortFactorKey = 'album_sort_factor';
|
static const albumSortFactorKey = 'album_sort_factor';
|
||||||
static const countrySortFactorKey = 'country_sort_factor';
|
static const countrySortFactorKey = 'country_sort_factor';
|
||||||
static const tagSortFactorKey = 'tag_sort_factor';
|
static const tagSortFactorKey = 'tag_sort_factor';
|
||||||
|
static const albumSortReverseKey = 'album_sort_reverse';
|
||||||
|
static const countrySortReverseKey = 'country_sort_reverse';
|
||||||
|
static const tagSortReverseKey = 'tag_sort_reverse';
|
||||||
static const pinnedFiltersKey = 'pinned_filters';
|
static const pinnedFiltersKey = 'pinned_filters';
|
||||||
static const hiddenFiltersKey = 'hidden_filters';
|
static const hiddenFiltersKey = 'hidden_filters';
|
||||||
|
|
||||||
|
@ -388,6 +392,10 @@ class Settings extends ChangeNotifier {
|
||||||
|
|
||||||
set collectionSortFactor(EntrySortFactor newValue) => setAndNotify(collectionSortFactorKey, newValue.toString());
|
set collectionSortFactor(EntrySortFactor newValue) => setAndNotify(collectionSortFactorKey, newValue.toString());
|
||||||
|
|
||||||
|
bool get collectionSortReverse => getBoolOrDefault(collectionSortReverseKey, false);
|
||||||
|
|
||||||
|
set collectionSortReverse(bool newValue) => setAndNotify(collectionSortReverseKey, newValue);
|
||||||
|
|
||||||
List<EntrySetAction> get collectionBrowsingQuickActions => getEnumListOrDefault(collectionBrowsingQuickActionsKey, SettingsDefaults.collectionBrowsingQuickActions, EntrySetAction.values);
|
List<EntrySetAction> get collectionBrowsingQuickActions => getEnumListOrDefault(collectionBrowsingQuickActionsKey, SettingsDefaults.collectionBrowsingQuickActions, EntrySetAction.values);
|
||||||
|
|
||||||
set collectionBrowsingQuickActions(List<EntrySetAction> newValue) => setAndNotify(collectionBrowsingQuickActionsKey, newValue.map((v) => v.toString()).toList());
|
set collectionBrowsingQuickActions(List<EntrySetAction> newValue) => setAndNotify(collectionBrowsingQuickActionsKey, newValue.map((v) => v.toString()).toList());
|
||||||
|
@ -442,6 +450,18 @@ class Settings extends ChangeNotifier {
|
||||||
|
|
||||||
set tagSortFactor(ChipSortFactor newValue) => setAndNotify(tagSortFactorKey, newValue.toString());
|
set tagSortFactor(ChipSortFactor newValue) => setAndNotify(tagSortFactorKey, newValue.toString());
|
||||||
|
|
||||||
|
bool get albumSortReverse => getBoolOrDefault(albumSortReverseKey, false);
|
||||||
|
|
||||||
|
set albumSortReverse(bool newValue) => setAndNotify(albumSortReverseKey, newValue);
|
||||||
|
|
||||||
|
bool get countrySortReverse => getBoolOrDefault(countrySortReverseKey, false);
|
||||||
|
|
||||||
|
set countrySortReverse(bool newValue) => setAndNotify(countrySortReverseKey, newValue);
|
||||||
|
|
||||||
|
bool get tagSortReverse => getBoolOrDefault(tagSortReverseKey, false);
|
||||||
|
|
||||||
|
set tagSortReverse(bool newValue) => setAndNotify(tagSortReverseKey, newValue);
|
||||||
|
|
||||||
Set<CollectionFilter> get pinnedFilters => (getStringList(pinnedFiltersKey) ?? []).map(CollectionFilter.fromJson).whereNotNull().toSet();
|
Set<CollectionFilter> get pinnedFilters => (getStringList(pinnedFiltersKey) ?? []).map(CollectionFilter.fromJson).whereNotNull().toSet();
|
||||||
|
|
||||||
set pinnedFilters(Set<CollectionFilter> newValue) => setAndNotify(pinnedFiltersKey, newValue.map((filter) => filter.toJson()).toList());
|
set pinnedFilters(Set<CollectionFilter> newValue) => setAndNotify(pinnedFiltersKey, newValue.map((filter) => filter.toJson()).toList());
|
||||||
|
@ -828,6 +848,7 @@ class Settings extends ChangeNotifier {
|
||||||
case confirmMoveUndatedItemsKey:
|
case confirmMoveUndatedItemsKey:
|
||||||
case confirmAfterMoveToBinKey:
|
case confirmAfterMoveToBinKey:
|
||||||
case setMetadataDateBeforeFileOpKey:
|
case setMetadataDateBeforeFileOpKey:
|
||||||
|
case collectionSortReverseKey:
|
||||||
case showThumbnailFavouriteKey:
|
case showThumbnailFavouriteKey:
|
||||||
case showThumbnailTagKey:
|
case showThumbnailTagKey:
|
||||||
case showThumbnailLocationKey:
|
case showThumbnailLocationKey:
|
||||||
|
@ -835,6 +856,9 @@ class Settings extends ChangeNotifier {
|
||||||
case showThumbnailRatingKey:
|
case showThumbnailRatingKey:
|
||||||
case showThumbnailRawKey:
|
case showThumbnailRawKey:
|
||||||
case showThumbnailVideoDurationKey:
|
case showThumbnailVideoDurationKey:
|
||||||
|
case albumSortReverseKey:
|
||||||
|
case countrySortReverseKey:
|
||||||
|
case tagSortReverseKey:
|
||||||
case showOverlayOnOpeningKey:
|
case showOverlayOnOpeningKey:
|
||||||
case showOverlayMinimapKey:
|
case showOverlayMinimapKey:
|
||||||
case showOverlayInfoKey:
|
case showOverlayInfoKey:
|
||||||
|
|
|
@ -22,13 +22,14 @@ import 'package:aves/utils/collection_utils.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
import 'enums.dart';
|
import 'enums/enums.dart';
|
||||||
|
|
||||||
class CollectionLens with ChangeNotifier {
|
class CollectionLens with ChangeNotifier {
|
||||||
final CollectionSource source;
|
final CollectionSource source;
|
||||||
final Set<CollectionFilter> filters;
|
final Set<CollectionFilter> filters;
|
||||||
EntryGroupFactor sectionFactor;
|
EntryGroupFactor sectionFactor;
|
||||||
EntrySortFactor sortFactor;
|
EntrySortFactor sortFactor;
|
||||||
|
bool sortReverse;
|
||||||
final AChangeNotifier filterChangeNotifier = AChangeNotifier(), sortSectionChangeNotifier = AChangeNotifier();
|
final AChangeNotifier filterChangeNotifier = AChangeNotifier(), sortSectionChangeNotifier = AChangeNotifier();
|
||||||
final List<StreamSubscription> _subscriptions = [];
|
final List<StreamSubscription> _subscriptions = [];
|
||||||
int? id;
|
int? id;
|
||||||
|
@ -49,7 +50,8 @@ class CollectionLens with ChangeNotifier {
|
||||||
this.fixedSelection,
|
this.fixedSelection,
|
||||||
}) : filters = (filters ?? {}).whereNotNull().toSet(),
|
}) : filters = (filters ?? {}).whereNotNull().toSet(),
|
||||||
sectionFactor = settings.collectionSectionFactor,
|
sectionFactor = settings.collectionSectionFactor,
|
||||||
sortFactor = settings.collectionSortFactor {
|
sortFactor = settings.collectionSortFactor,
|
||||||
|
sortReverse = settings.collectionSortReverse {
|
||||||
id ??= hashCode;
|
id ??= hashCode;
|
||||||
if (listenToSource) {
|
if (listenToSource) {
|
||||||
final sourceEvents = source.eventBus;
|
final sourceEvents = source.eventBus;
|
||||||
|
@ -84,6 +86,7 @@ class CollectionLens with ChangeNotifier {
|
||||||
.where((event) => [
|
.where((event) => [
|
||||||
Settings.collectionSortFactorKey,
|
Settings.collectionSortFactorKey,
|
||||||
Settings.collectionGroupFactorKey,
|
Settings.collectionGroupFactorKey,
|
||||||
|
Settings.collectionSortReverseKey,
|
||||||
].contains(event.key))
|
].contains(event.key))
|
||||||
.listen((_) => _onSettingsChanged()));
|
.listen((_) => _onSettingsChanged()));
|
||||||
refresh();
|
refresh();
|
||||||
|
@ -218,6 +221,9 @@ class CollectionLens with ChangeNotifier {
|
||||||
_filteredSortedEntries.sort(AvesEntry.compareBySize);
|
_filteredSortedEntries.sort(AvesEntry.compareBySize);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (sortReverse) {
|
||||||
|
_filteredSortedEntries = _filteredSortedEntries.reversed.toList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _applySection() {
|
void _applySection() {
|
||||||
|
@ -247,7 +253,8 @@ class CollectionLens with ChangeNotifier {
|
||||||
break;
|
break;
|
||||||
case EntrySortFactor.name:
|
case EntrySortFactor.name:
|
||||||
final byAlbum = groupBy<AvesEntry, EntryAlbumSectionKey>(_filteredSortedEntries, (entry) => EntryAlbumSectionKey(entry.directory));
|
final byAlbum = groupBy<AvesEntry, EntryAlbumSectionKey>(_filteredSortedEntries, (entry) => EntryAlbumSectionKey(entry.directory));
|
||||||
sections = SplayTreeMap<EntryAlbumSectionKey, List<AvesEntry>>.of(byAlbum, (a, b) => source.compareAlbumsByName(a.directory!, b.directory!));
|
final compare = sortReverse ? (a, b) => source.compareAlbumsByName(b.directory!, a.directory!) : (a, b) => source.compareAlbumsByName(a.directory!, b.directory!);
|
||||||
|
sections = SplayTreeMap<EntryAlbumSectionKey, List<AvesEntry>>.of(byAlbum, compare);
|
||||||
break;
|
break;
|
||||||
case EntrySortFactor.rating:
|
case EntrySortFactor.rating:
|
||||||
sections = groupBy<AvesEntry, EntryRatingSectionKey>(_filteredSortedEntries, (entry) => EntryRatingSectionKey(entry.rating));
|
sections = groupBy<AvesEntry, EntryRatingSectionKey>(_filteredSortedEntries, (entry) => EntryRatingSectionKey(entry.rating));
|
||||||
|
@ -281,12 +288,14 @@ class CollectionLens with ChangeNotifier {
|
||||||
void _onSettingsChanged() {
|
void _onSettingsChanged() {
|
||||||
final newSortFactor = settings.collectionSortFactor;
|
final newSortFactor = settings.collectionSortFactor;
|
||||||
final newSectionFactor = settings.collectionSectionFactor;
|
final newSectionFactor = settings.collectionSectionFactor;
|
||||||
|
final newSortReverse = settings.collectionSortReverse;
|
||||||
|
|
||||||
final needSort = sortFactor != newSortFactor;
|
final needSort = sortFactor != newSortFactor || sortReverse != newSortReverse;
|
||||||
final needSection = needSort || sectionFactor != newSectionFactor;
|
final needSection = needSort || sectionFactor != newSectionFactor;
|
||||||
|
|
||||||
if (needSort) {
|
if (needSort) {
|
||||||
sortFactor = newSortFactor;
|
sortFactor = newSortFactor;
|
||||||
|
sortReverse = newSortReverse;
|
||||||
_applySort();
|
_applySort();
|
||||||
}
|
}
|
||||||
if (needSection) {
|
if (needSection) {
|
||||||
|
|
|
@ -13,7 +13,7 @@ import 'package:aves/model/metadata/trash.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/model/source/album.dart';
|
import 'package:aves/model/source/album.dart';
|
||||||
import 'package:aves/model/source/analysis_controller.dart';
|
import 'package:aves/model/source/analysis_controller.dart';
|
||||||
import 'package:aves/model/source/enums.dart';
|
import 'package:aves/model/source/enums/enums.dart';
|
||||||
import 'package:aves/model/source/events.dart';
|
import 'package:aves/model/source/events.dart';
|
||||||
import 'package:aves/model/source/location.dart';
|
import 'package:aves/model/source/location.dart';
|
||||||
import 'package:aves/model/source/tag.dart';
|
import 'package:aves/model/source/tag.dart';
|
||||||
|
|
105
lib/model/source/enums/view.dart
Normal file
105
lib/model/source/enums/view.dart
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
import 'enums.dart';
|
||||||
|
|
||||||
|
extension ExtraEntrySortFactor on EntrySortFactor {
|
||||||
|
String getName(BuildContext context) {
|
||||||
|
final l10n = context.l10n;
|
||||||
|
switch (this) {
|
||||||
|
case EntrySortFactor.date:
|
||||||
|
return l10n.sortByDate;
|
||||||
|
case EntrySortFactor.name:
|
||||||
|
return l10n.sortByAlbumFileName;
|
||||||
|
case EntrySortFactor.rating:
|
||||||
|
return l10n.sortByRating;
|
||||||
|
case EntrySortFactor.size:
|
||||||
|
return l10n.sortBySize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String getOrderName(BuildContext context, bool reverse) {
|
||||||
|
final l10n = context.l10n;
|
||||||
|
switch (this) {
|
||||||
|
case EntrySortFactor.date:
|
||||||
|
return reverse ? l10n.sortOrderOldestFirst : l10n.sortOrderNewestFirst;
|
||||||
|
case EntrySortFactor.name:
|
||||||
|
return reverse ? l10n.sortOrderZtoA : l10n.sortOrderAtoZ;
|
||||||
|
case EntrySortFactor.rating:
|
||||||
|
return reverse ? l10n.sortOrderLowestFirst : l10n.sortOrderHighestFirst;
|
||||||
|
case EntrySortFactor.size:
|
||||||
|
return reverse ? l10n.sortOrderSmallestFirst : l10n.sortOrderLargestFirst;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ExtraChipSortFactor on ChipSortFactor {
|
||||||
|
String getName(BuildContext context) {
|
||||||
|
final l10n = context.l10n;
|
||||||
|
switch (this) {
|
||||||
|
case ChipSortFactor.date:
|
||||||
|
return l10n.sortByDate;
|
||||||
|
case ChipSortFactor.name:
|
||||||
|
return l10n.sortByName;
|
||||||
|
case ChipSortFactor.count:
|
||||||
|
return l10n.sortByItemCount;
|
||||||
|
case ChipSortFactor.size:
|
||||||
|
return l10n.sortBySize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String getOrderName(BuildContext context, bool reverse) {
|
||||||
|
final l10n = context.l10n;
|
||||||
|
switch (this) {
|
||||||
|
case ChipSortFactor.date:
|
||||||
|
return reverse ? l10n.sortOrderOldestFirst : l10n.sortOrderNewestFirst;
|
||||||
|
case ChipSortFactor.name:
|
||||||
|
return reverse ? l10n.sortOrderZtoA : l10n.sortOrderAtoZ;
|
||||||
|
case ChipSortFactor.count:
|
||||||
|
case ChipSortFactor.size:
|
||||||
|
return reverse ? l10n.sortOrderSmallestFirst : l10n.sortOrderLargestFirst;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ExtraEntryGroupFactor on EntryGroupFactor {
|
||||||
|
String getName(BuildContext context) {
|
||||||
|
final l10n = context.l10n;
|
||||||
|
switch (this) {
|
||||||
|
case EntryGroupFactor.album:
|
||||||
|
return l10n.collectionGroupAlbum;
|
||||||
|
case EntryGroupFactor.month:
|
||||||
|
return l10n.collectionGroupMonth;
|
||||||
|
case EntryGroupFactor.day:
|
||||||
|
return l10n.collectionGroupDay;
|
||||||
|
case EntryGroupFactor.none:
|
||||||
|
return l10n.collectionGroupNone;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ExtraAlbumChipGroupFactor on AlbumChipGroupFactor {
|
||||||
|
String getName(BuildContext context) {
|
||||||
|
final l10n = context.l10n;
|
||||||
|
switch (this) {
|
||||||
|
case AlbumChipGroupFactor.importance:
|
||||||
|
return l10n.albumGroupTier;
|
||||||
|
case AlbumChipGroupFactor.volume:
|
||||||
|
return l10n.albumGroupVolume;
|
||||||
|
case AlbumChipGroupFactor.none:
|
||||||
|
return l10n.albumGroupNone;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ExtraTileLayout on TileLayout {
|
||||||
|
String getName(BuildContext context) {
|
||||||
|
final l10n = context.l10n;
|
||||||
|
switch (this) {
|
||||||
|
case TileLayout.grid:
|
||||||
|
return l10n.tileLayoutGrid;
|
||||||
|
case TileLayout.list:
|
||||||
|
return l10n.tileLayoutList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ import 'package:aves/model/metadata/address.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/model/source/analysis_controller.dart';
|
import 'package:aves/model/source/analysis_controller.dart';
|
||||||
import 'package:aves/model/source/collection_source.dart';
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
import 'package:aves/model/source/enums.dart';
|
import 'package:aves/model/source/enums/enums.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
import 'package:aves/utils/collection_utils.dart';
|
import 'package:aves/utils/collection_utils.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
|
|
@ -7,7 +7,7 @@ import 'package:aves/model/favourites.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/model/source/analysis_controller.dart';
|
import 'package:aves/model/source/analysis_controller.dart';
|
||||||
import 'package:aves/model/source/collection_source.dart';
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
import 'package:aves/model/source/enums.dart';
|
import 'package:aves/model/source/enums/enums.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
import 'package:aves/utils/android_file_utils.dart';
|
import 'package:aves/utils/android_file_utils.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:aves/l10n/l10n.dart';
|
import 'package:aves/l10n/l10n.dart';
|
||||||
import 'package:aves/model/source/enums.dart';
|
import 'package:aves/model/source/enums/enums.dart';
|
||||||
|
|
||||||
extension ExtraSourceState on SourceState {
|
extension ExtraSourceState on SourceState {
|
||||||
String? getName(AppLocalizations l10n) {
|
String? getName(AppLocalizations l10n) {
|
||||||
|
|
|
@ -3,7 +3,7 @@ import 'package:aves/model/filters/tag.dart';
|
||||||
import 'package:aves/model/metadata/catalog.dart';
|
import 'package:aves/model/metadata/catalog.dart';
|
||||||
import 'package:aves/model/source/analysis_controller.dart';
|
import 'package:aves/model/source/analysis_controller.dart';
|
||||||
import 'package:aves/model/source/collection_source.dart';
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
import 'package:aves/model/source/enums.dart';
|
import 'package:aves/model/source/enums/enums.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
import 'package:aves/utils/collection_utils.dart';
|
import 'package:aves/utils/collection_utils.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
|
|
@ -4,7 +4,7 @@ import 'dart:ui';
|
||||||
import 'package:aves/l10n/l10n.dart';
|
import 'package:aves/l10n/l10n.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/model/source/analysis_controller.dart';
|
import 'package:aves/model/source/analysis_controller.dart';
|
||||||
import 'package:aves/model/source/enums.dart';
|
import 'package:aves/model/source/enums/enums.dart';
|
||||||
import 'package:aves/model/source/media_store_source.dart';
|
import 'package:aves/model/source/media_store_source.dart';
|
||||||
import 'package:aves/model/source/source_state.dart';
|
import 'package:aves/model/source/source_state.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
|
|
|
@ -51,6 +51,7 @@ class AIcons {
|
||||||
static const IconData group = Icons.group_work_outlined;
|
static const IconData group = Icons.group_work_outlined;
|
||||||
static const IconData layout = Icons.grid_view_outlined;
|
static const IconData layout = Icons.grid_view_outlined;
|
||||||
static const IconData sort = Icons.sort_outlined;
|
static const IconData sort = Icons.sort_outlined;
|
||||||
|
static const IconData sortOrder = Icons.swap_vert_outlined;
|
||||||
|
|
||||||
// actions
|
// actions
|
||||||
static const IconData add = Icons.add_circle_outline;
|
static const IconData add = Icons.add_circle_outline;
|
||||||
|
|
|
@ -12,7 +12,8 @@ import 'package:aves/model/selection.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/model/source/collection_lens.dart';
|
import 'package:aves/model/source/collection_lens.dart';
|
||||||
import 'package:aves/model/source/collection_source.dart';
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
import 'package:aves/model/source/enums.dart';
|
import 'package:aves/model/source/enums/enums.dart';
|
||||||
|
import 'package:aves/model/source/enums/view.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/widgets/collection/collection_page.dart';
|
import 'package:aves/widgets/collection/collection_page.dart';
|
||||||
|
@ -67,6 +68,25 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
||||||
|
|
||||||
bool get showFilterBar => visibleFilters.isNotEmpty;
|
bool get showFilterBar => visibleFilters.isNotEmpty;
|
||||||
|
|
||||||
|
static const _sortOptions = [
|
||||||
|
EntrySortFactor.date,
|
||||||
|
EntrySortFactor.size,
|
||||||
|
EntrySortFactor.name,
|
||||||
|
EntrySortFactor.rating,
|
||||||
|
];
|
||||||
|
|
||||||
|
static const _groupOptions = [
|
||||||
|
EntryGroupFactor.album,
|
||||||
|
EntryGroupFactor.month,
|
||||||
|
EntryGroupFactor.day,
|
||||||
|
EntryGroupFactor.none,
|
||||||
|
];
|
||||||
|
|
||||||
|
static const _layoutOptions = [
|
||||||
|
TileLayout.grid,
|
||||||
|
TileLayout.list,
|
||||||
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
@ -506,33 +526,22 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _configureView() async {
|
Future<void> _configureView() async {
|
||||||
final initialValue = Tuple3(
|
final initialValue = Tuple4(
|
||||||
settings.collectionSortFactor,
|
settings.collectionSortFactor,
|
||||||
settings.collectionSectionFactor,
|
settings.collectionSectionFactor,
|
||||||
settings.getTileLayout(CollectionPage.routeName),
|
settings.getTileLayout(CollectionPage.routeName),
|
||||||
|
settings.collectionSortReverse,
|
||||||
);
|
);
|
||||||
final value = await showDialog<Tuple3<EntrySortFactor?, EntryGroupFactor?, TileLayout?>>(
|
final value = await showDialog<Tuple4<EntrySortFactor?, EntryGroupFactor?, TileLayout?, bool>>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
final l10n = context.l10n;
|
|
||||||
return TileViewDialog<EntrySortFactor, EntryGroupFactor, TileLayout>(
|
return TileViewDialog<EntrySortFactor, EntryGroupFactor, TileLayout>(
|
||||||
initialValue: initialValue,
|
initialValue: initialValue,
|
||||||
sortOptions: {
|
sortOptions: Map.fromEntries(_sortOptions.map((v) => MapEntry(v, v.getName(context)))),
|
||||||
EntrySortFactor.date: l10n.sortByDate,
|
groupOptions: Map.fromEntries(_groupOptions.map((v) => MapEntry(v, v.getName(context)))),
|
||||||
EntrySortFactor.size: l10n.sortBySize,
|
layoutOptions: Map.fromEntries(_layoutOptions.map((v) => MapEntry(v, v.getName(context)))),
|
||||||
EntrySortFactor.name: l10n.sortByAlbumFileName,
|
sortOrder: (factor, reverse) => factor.getOrderName(context, reverse),
|
||||||
EntrySortFactor.rating: l10n.sortByRating,
|
canGroup: (s, g, l) => s == EntrySortFactor.date,
|
||||||
},
|
|
||||||
groupOptions: {
|
|
||||||
EntryGroupFactor.album: l10n.collectionGroupAlbum,
|
|
||||||
EntryGroupFactor.month: l10n.collectionGroupMonth,
|
|
||||||
EntryGroupFactor.day: l10n.collectionGroupDay,
|
|
||||||
EntryGroupFactor.none: l10n.collectionGroupNone,
|
|
||||||
},
|
|
||||||
layoutOptions: {
|
|
||||||
TileLayout.grid: l10n.tileLayoutGrid,
|
|
||||||
TileLayout.list: l10n.tileLayoutList,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -542,6 +551,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
||||||
settings.collectionSortFactor = value.item1!;
|
settings.collectionSortFactor = value.item1!;
|
||||||
settings.collectionSectionFactor = value.item2!;
|
settings.collectionSectionFactor = value.item2!;
|
||||||
settings.setTileLayout(CollectionPage.routeName, value.item3!);
|
settings.setTileLayout(CollectionPage.routeName, value.item3!);
|
||||||
|
settings.collectionSortReverse = value.item4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import 'package:aves/model/filters/favourite.dart';
|
||||||
import 'package:aves/model/filters/mime.dart';
|
import 'package:aves/model/filters/mime.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/model/source/collection_lens.dart';
|
import 'package:aves/model/source/collection_lens.dart';
|
||||||
import 'package:aves/model/source/enums.dart';
|
import 'package:aves/model/source/enums/enums.dart';
|
||||||
import 'package:aves/model/source/section_keys.dart';
|
import 'package:aves/model/source/section_keys.dart';
|
||||||
import 'package:aves/ref/mime_types.dart';
|
import 'package:aves/ref/mime_types.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
|
@ -542,7 +542,7 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> with Widge
|
||||||
final oldest = lastKey.date;
|
final oldest = lastKey.date;
|
||||||
if (newest != null && oldest != null) {
|
if (newest != null && oldest != null) {
|
||||||
final localeName = context.l10n.localeName;
|
final localeName = context.l10n.localeName;
|
||||||
final dateFormat = newest.difference(oldest).inDays > 365 ? DateFormat.y(localeName) : DateFormat.MMM(localeName);
|
final dateFormat = (newest.difference(oldest).inDays).abs() > 365 ? DateFormat.y(localeName) : DateFormat.MMM(localeName);
|
||||||
String? lastLabel;
|
String? lastLabel;
|
||||||
sectionLayouts.forEach((section) {
|
sectionLayouts.forEach((section) {
|
||||||
final date = (section.sectionKey as EntryDateSectionKey).date;
|
final date = (section.sectionKey as EntryDateSectionKey).date;
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'package:aves/model/entry.dart';
|
||||||
import 'package:aves/model/filters/rating.dart';
|
import 'package:aves/model/filters/rating.dart';
|
||||||
import 'package:aves/model/source/collection_lens.dart';
|
import 'package:aves/model/source/collection_lens.dart';
|
||||||
import 'package:aves/model/source/collection_source.dart';
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
import 'package:aves/model/source/enums.dart';
|
import 'package:aves/model/source/enums/enums.dart';
|
||||||
import 'package:aves/utils/file_utils.dart';
|
import 'package:aves/utils/file_utils.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/grid/draggable_thumb_label.dart';
|
import 'package:aves/widgets/common/grid/draggable_thumb_label.dart';
|
||||||
|
|
|
@ -3,7 +3,7 @@ import 'dart:math';
|
||||||
import 'package:aves/model/entry.dart';
|
import 'package:aves/model/entry.dart';
|
||||||
import 'package:aves/model/source/collection_lens.dart';
|
import 'package:aves/model/source/collection_lens.dart';
|
||||||
import 'package:aves/model/source/collection_source.dart';
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
import 'package:aves/model/source/enums.dart';
|
import 'package:aves/model/source/enums/enums.dart';
|
||||||
import 'package:aves/model/source/section_keys.dart';
|
import 'package:aves/model/source/section_keys.dart';
|
||||||
import 'package:aves/widgets/collection/grid/headers/album.dart';
|
import 'package:aves/widgets/collection/grid/headers/album.dart';
|
||||||
import 'package:aves/widgets/collection/grid/headers/date.dart';
|
import 'package:aves/widgets/collection/grid/headers/date.dart';
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'package:aves/app_mode.dart';
|
||||||
import 'package:aves/model/entry.dart';
|
import 'package:aves/model/entry.dart';
|
||||||
import 'package:aves/model/selection.dart';
|
import 'package:aves/model/selection.dart';
|
||||||
import 'package:aves/model/source/collection_lens.dart';
|
import 'package:aves/model/source/collection_lens.dart';
|
||||||
import 'package:aves/model/source/enums.dart';
|
import 'package:aves/model/source/enums/enums.dart';
|
||||||
import 'package:aves/services/intent_service.dart';
|
import 'package:aves/services/intent_service.dart';
|
||||||
import 'package:aves/widgets/collection/grid/list_details.dart';
|
import 'package:aves/widgets/collection/grid/list_details.dart';
|
||||||
import 'package:aves/widgets/collection/grid/list_details_theme.dart';
|
import 'package:aves/widgets/collection/grid/list_details_theme.dart';
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:aves/model/source/collection_source.dart';
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
import 'package:aves/model/source/enums.dart';
|
import 'package:aves/model/source/enums/enums.dart';
|
||||||
import 'package:aves/model/source/events.dart';
|
import 'package:aves/model/source/events.dart';
|
||||||
import 'package:aves/model/source/source_state.dart';
|
import 'package:aves/model/source/source_state.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
|
|
52
lib/widgets/common/basic/text_dropdown_button.dart
Normal file
52
lib/widgets/common/basic/text_dropdown_button.dart
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class TextDropdownButton<T> extends DropdownButton<T> {
|
||||||
|
TextDropdownButton({
|
||||||
|
super.key,
|
||||||
|
required List<T> values,
|
||||||
|
required String Function(T value) valueText,
|
||||||
|
super.value,
|
||||||
|
super.hint,
|
||||||
|
super.disabledHint,
|
||||||
|
required super.onChanged,
|
||||||
|
super.onTap,
|
||||||
|
super.elevation,
|
||||||
|
super.style,
|
||||||
|
super.underline,
|
||||||
|
super.icon,
|
||||||
|
super.iconDisabledColor,
|
||||||
|
super.iconEnabledColor,
|
||||||
|
super.iconSize,
|
||||||
|
super.isDense,
|
||||||
|
super.isExpanded,
|
||||||
|
super.itemHeight,
|
||||||
|
super.focusColor,
|
||||||
|
super.focusNode,
|
||||||
|
super.autofocus,
|
||||||
|
super.dropdownColor,
|
||||||
|
super.menuMaxHeight,
|
||||||
|
super.enableFeedback,
|
||||||
|
super.alignment,
|
||||||
|
super.borderRadius,
|
||||||
|
}) : super(
|
||||||
|
items: values
|
||||||
|
.map((v) => DropdownMenuItem<T>(
|
||||||
|
value: v,
|
||||||
|
child: Text(valueText(v)),
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
selectedItemBuilder: (context) => values
|
||||||
|
.map((v) => DropdownMenuItem<T>(
|
||||||
|
value: v,
|
||||||
|
child: Align(
|
||||||
|
alignment: AlignmentDirectional.centerStart,
|
||||||
|
child: Text(
|
||||||
|
valueText(v),
|
||||||
|
softWrap: false,
|
||||||
|
overflow: TextOverflow.fade,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
}
|
|
@ -2,7 +2,7 @@ import 'dart:async';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:aves/model/highlight.dart';
|
import 'package:aves/model/highlight.dart';
|
||||||
import 'package:aves/model/source/enums.dart';
|
import 'package:aves/model/source/enums/enums.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/widgets/common/grid/section_layout.dart';
|
import 'package:aves/widgets/common/grid/section_layout.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'dart:ui' as ui;
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
import 'package:aves/model/highlight.dart';
|
import 'package:aves/model/highlight.dart';
|
||||||
import 'package:aves/model/source/enums.dart';
|
import 'package:aves/model/source/enums/enums.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/widgets/common/behaviour/eager_scale_gesture_recognizer.dart';
|
import 'package:aves/widgets/common/behaviour/eager_scale_gesture_recognizer.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:aves/model/source/enums.dart';
|
import 'package:aves/model/source/enums/enums.dart';
|
||||||
import 'package:aves/model/source/section_keys.dart';
|
import 'package:aves/model/source/section_keys.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
|
|
@ -7,6 +7,7 @@ import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/theme/format.dart';
|
import 'package:aves/theme/format.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/theme/themes.dart';
|
import 'package:aves/theme/themes.dart';
|
||||||
|
import 'package:aves/widgets/common/basic/text_dropdown_button.dart';
|
||||||
import 'package:aves/widgets/common/basic/wheel.dart';
|
import 'package:aves/widgets/common/basic/wheel.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||||
|
@ -80,13 +81,9 @@ class _EditEntryDateDialogState extends State<EditEntryDateDialog> {
|
||||||
scrollableContent: [
|
scrollableContent: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left: 16, top: 8, right: 16),
|
padding: const EdgeInsets.only(left: 16, top: 8, right: 16),
|
||||||
child: DropdownButton<DateEditAction>(
|
child: TextDropdownButton<DateEditAction>(
|
||||||
items: DateEditAction.values
|
values: DateEditAction.values,
|
||||||
.map((v) => DropdownMenuItem<DateEditAction>(
|
valueText: (v) => _actionText(context, v),
|
||||||
value: v,
|
|
||||||
child: Text(_actionText(context, v)),
|
|
||||||
))
|
|
||||||
.toList(),
|
|
||||||
value: _action,
|
value: _action,
|
||||||
onChanged: (v) => setState(() => _action = v!),
|
onChanged: (v) => setState(() => _action = v!),
|
||||||
isExpanded: true,
|
isExpanded: true,
|
||||||
|
@ -159,23 +156,9 @@ class _EditEntryDateDialogState extends State<EditEntryDateDialog> {
|
||||||
Widget _buildCopyFieldContent(BuildContext context) {
|
Widget _buildCopyFieldContent(BuildContext context) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(left: 16, top: 0, right: 16),
|
padding: const EdgeInsets.only(left: 16, top: 0, right: 16),
|
||||||
child: DropdownButton<DateFieldSource>(
|
child: TextDropdownButton<DateFieldSource>(
|
||||||
items: DateFieldSource.values
|
values: DateFieldSource.values,
|
||||||
.map((v) => DropdownMenuItem<DateFieldSource>(
|
valueText: (v) => _setSourceText(context, v),
|
||||||
value: v,
|
|
||||||
child: Text(_setSourceText(context, v)),
|
|
||||||
))
|
|
||||||
.toList(),
|
|
||||||
selectedItemBuilder: (context) => DateFieldSource.values
|
|
||||||
.map((v) => DropdownMenuItem<DateFieldSource>(
|
|
||||||
value: v,
|
|
||||||
child: Text(
|
|
||||||
_setSourceText(context, v),
|
|
||||||
softWrap: false,
|
|
||||||
overflow: TextOverflow.fade,
|
|
||||||
),
|
|
||||||
))
|
|
||||||
.toList(),
|
|
||||||
value: _copyFieldSource,
|
value: _copyFieldSource,
|
||||||
onChanged: (v) => setState(() => _copyFieldSource = v!),
|
onChanged: (v) => setState(() => _copyFieldSource = v!),
|
||||||
isExpanded: true,
|
isExpanded: true,
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'package:aves/model/entry.dart';
|
||||||
import 'package:aves/ref/mime_types.dart';
|
import 'package:aves/ref/mime_types.dart';
|
||||||
import 'package:aves/services/media/media_edit_service.dart';
|
import 'package:aves/services/media/media_edit_service.dart';
|
||||||
import 'package:aves/utils/mime_utils.dart';
|
import 'package:aves/utils/mime_utils.dart';
|
||||||
|
import 'package:aves/widgets/common/basic/text_dropdown_button.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
@ -63,13 +64,9 @@ class _ExportEntryDialogState extends State<ExportEntryDialog> {
|
||||||
children: [
|
children: [
|
||||||
Text(l10n.exportEntryDialogFormat),
|
Text(l10n.exportEntryDialogFormat),
|
||||||
const SizedBox(width: AvesDialog.controlCaptionPadding),
|
const SizedBox(width: AvesDialog.controlCaptionPadding),
|
||||||
DropdownButton<String>(
|
TextDropdownButton<String>(
|
||||||
items: imageExportFormats.map((mimeType) {
|
values: imageExportFormats,
|
||||||
return DropdownMenuItem<String>(
|
valueText: MimeUtils.displayType,
|
||||||
value: mimeType,
|
|
||||||
child: Text(MimeUtils.displayType(mimeType)),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
value: _mimeType,
|
value: _mimeType,
|
||||||
onChanged: (selected) {
|
onChanged: (selected) {
|
||||||
if (selected != null) {
|
if (selected != null) {
|
||||||
|
|
|
@ -1,19 +1,22 @@
|
||||||
import 'dart:math';
|
import 'package:aves/theme/durations.dart';
|
||||||
|
|
||||||
import 'package:aves/model/source/enums.dart';
|
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
|
import 'package:aves/theme/themes.dart';
|
||||||
|
import 'package:aves/widgets/common/basic/text_dropdown_button.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
|
import 'package:aves/widgets/common/identity/highlight_title.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
import 'aves_dialog.dart';
|
import 'aves_dialog.dart';
|
||||||
|
|
||||||
class TileViewDialog<S, G, L> extends StatefulWidget {
|
class TileViewDialog<S, G, L> extends StatefulWidget {
|
||||||
final Tuple3<S?, G?, L?> initialValue;
|
final Tuple4<S?, G?, L?, bool> initialValue;
|
||||||
final Map<S, String> sortOptions;
|
final Map<S, String> sortOptions;
|
||||||
final Map<G, String> groupOptions;
|
final Map<G, String> groupOptions;
|
||||||
final Map<L, String> layoutOptions;
|
final Map<L, String> layoutOptions;
|
||||||
|
final String Function(S sort, bool reverse) sortOrder;
|
||||||
|
final bool Function(S? sort, G? group, L? layout)? canGroup;
|
||||||
|
|
||||||
const TileViewDialog({
|
const TileViewDialog({
|
||||||
super.key,
|
super.key,
|
||||||
|
@ -21,6 +24,8 @@ class TileViewDialog<S, G, L> extends StatefulWidget {
|
||||||
this.sortOptions = const {},
|
this.sortOptions = const {},
|
||||||
this.groupOptions = const {},
|
this.groupOptions = const {},
|
||||||
this.layoutOptions = const {},
|
this.layoutOptions = const {},
|
||||||
|
required this.sortOrder,
|
||||||
|
this.canGroup,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -31,8 +36,7 @@ class _TileViewDialogState<S, G, L> extends State<TileViewDialog<S, G, L>> with
|
||||||
late S? _selectedSort;
|
late S? _selectedSort;
|
||||||
late G? _selectedGroup;
|
late G? _selectedGroup;
|
||||||
late L? _selectedLayout;
|
late L? _selectedLayout;
|
||||||
late final TabController _tabController;
|
late bool _reverseSort;
|
||||||
late final String _optionLines;
|
|
||||||
|
|
||||||
Map<S, String> get sortOptions => widget.sortOptions;
|
Map<S, String> get sortOptions => widget.sortOptions;
|
||||||
|
|
||||||
|
@ -40,11 +44,7 @@ class _TileViewDialogState<S, G, L> extends State<TileViewDialog<S, G, L>> with
|
||||||
|
|
||||||
Map<L, String> get layoutOptions => widget.layoutOptions;
|
Map<L, String> get layoutOptions => widget.layoutOptions;
|
||||||
|
|
||||||
static const int groupTabIndex = 1;
|
bool get canGroup => (widget.canGroup ?? (s, g, l) => true).call(_selectedSort, _selectedGroup, _selectedLayout);
|
||||||
|
|
||||||
double tabBarHeight(BuildContext context) => 64 * max(1, MediaQuery.textScaleFactorOf(context));
|
|
||||||
|
|
||||||
static const double tabIndicatorWeight = 2;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
@ -53,259 +53,140 @@ class _TileViewDialogState<S, G, L> extends State<TileViewDialog<S, G, L>> with
|
||||||
_selectedSort = initialValue.item1;
|
_selectedSort = initialValue.item1;
|
||||||
_selectedGroup = initialValue.item2;
|
_selectedGroup = initialValue.item2;
|
||||||
_selectedLayout = initialValue.item3;
|
_selectedLayout = initialValue.item3;
|
||||||
|
_reverseSort = initialValue.item4;
|
||||||
final allOptions = [
|
|
||||||
sortOptions,
|
|
||||||
groupOptions,
|
|
||||||
layoutOptions,
|
|
||||||
];
|
|
||||||
|
|
||||||
final tabCount = allOptions.where((options) => options.isNotEmpty).length;
|
|
||||||
_tabController = TabController(length: tabCount, vsync: this);
|
|
||||||
_tabController.addListener(_onTabChange);
|
|
||||||
|
|
||||||
_optionLines = allOptions.expand((v) => v.values).fold('', (previousValue, element) => '$previousValue\n$element');
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_tabController.removeListener(_onTabChange);
|
|
||||||
_tabController.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final l10n = context.l10n;
|
final l10n = context.l10n;
|
||||||
final tabs = <Tuple2<Tab, Widget>>[
|
|
||||||
if (sortOptions.isNotEmpty)
|
return AvesDialog(
|
||||||
Tuple2(
|
scrollableContent: [
|
||||||
_buildTab(
|
_buildSection(
|
||||||
context,
|
icon: AIcons.sort,
|
||||||
const Key('tab-sort'),
|
title: l10n.viewDialogTabSort,
|
||||||
AIcons.sort,
|
trailing: IconButton(
|
||||||
l10n.viewDialogTabSort,
|
icon: const Icon(AIcons.sortOrder),
|
||||||
|
onPressed: () => setState(() => _reverseSort = !_reverseSort),
|
||||||
|
tooltip: l10n.viewDialogReverseSortOrder,
|
||||||
),
|
),
|
||||||
Column(
|
options: sortOptions,
|
||||||
children: sortOptions.entries
|
value: _selectedSort,
|
||||||
.map((kv) => _buildRadioListTile<S>(
|
onChanged: (v) {
|
||||||
kv.key,
|
_selectedSort = v;
|
||||||
kv.value,
|
_reverseSort = false;
|
||||||
() => _selectedSort,
|
},
|
||||||
(v) => _selectedSort = v,
|
bottom: _selectedSort != null
|
||||||
))
|
? Text(
|
||||||
.toList(),
|
widget.sortOrder(_selectedSort as S, _reverseSort),
|
||||||
|
style: Theme.of(context).textTheme.caption,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
AnimatedSwitcher(
|
||||||
|
duration: context.read<DurationsData>().formTransition,
|
||||||
|
switchInCurve: Curves.easeInOutCubic,
|
||||||
|
switchOutCurve: Curves.easeInOutCubic,
|
||||||
|
transitionBuilder: (child, animation) => FadeTransition(
|
||||||
|
opacity: animation,
|
||||||
|
child: SizeTransition(
|
||||||
|
sizeFactor: animation,
|
||||||
|
axisAlignment: -1,
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: _buildSection(
|
||||||
|
show: canGroup,
|
||||||
|
icon: AIcons.group,
|
||||||
|
title: l10n.viewDialogTabGroup,
|
||||||
|
options: groupOptions,
|
||||||
|
value: _selectedGroup,
|
||||||
|
onChanged: (v) => _selectedGroup = v,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (groupOptions.isNotEmpty)
|
_buildSection(
|
||||||
Tuple2(
|
icon: AIcons.layout,
|
||||||
_buildTab(
|
title: l10n.viewDialogTabLayout,
|
||||||
context,
|
options: layoutOptions,
|
||||||
const Key('tab-group'),
|
value: _selectedLayout,
|
||||||
AIcons.group,
|
onChanged: (v) => _selectedLayout = v,
|
||||||
l10n.viewDialogTabGroup,
|
|
||||||
color: canGroup ? null : Theme.of(context).disabledColor,
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
children: groupOptions.entries
|
|
||||||
.map((kv) => _buildRadioListTile<G>(
|
|
||||||
kv.key,
|
|
||||||
kv.value,
|
|
||||||
() => _selectedGroup,
|
|
||||||
(v) => _selectedGroup = v,
|
|
||||||
))
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
if (layoutOptions.isNotEmpty)
|
],
|
||||||
Tuple2(
|
actions: [
|
||||||
_buildTab(
|
TextButton(
|
||||||
context,
|
onPressed: () => Navigator.pop(context),
|
||||||
const Key('tab-layout'),
|
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||||
AIcons.layout,
|
|
||||||
l10n.viewDialogTabLayout,
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
children: layoutOptions.entries
|
|
||||||
.map((kv) => _buildRadioListTile<L>(
|
|
||||||
kv.key,
|
|
||||||
kv.value,
|
|
||||||
() => _selectedLayout,
|
|
||||||
(v) => _selectedLayout = v,
|
|
||||||
))
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
];
|
TextButton(
|
||||||
|
key: const Key('button-apply'),
|
||||||
final contentWidget = DecoratedBox(
|
onPressed: () => Navigator.pop(context, Tuple4(_selectedSort, _selectedGroup, _selectedLayout, _reverseSort)),
|
||||||
decoration: AvesDialog.contentDecoration(context),
|
child: Text(l10n.applyButtonLabel),
|
||||||
child: LayoutBuilder(
|
)
|
||||||
builder: (context, constraints) {
|
],
|
||||||
final availableBodyHeight = constraints.maxHeight - tabBarHeight(context) - tabIndicatorWeight;
|
|
||||||
final maxHeight = min(availableBodyHeight, tabBodyMaxHeight(context));
|
|
||||||
return Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Material(
|
|
||||||
borderRadius: const BorderRadius.vertical(
|
|
||||||
top: AvesDialog.cornerRadius,
|
|
||||||
),
|
|
||||||
clipBehavior: Clip.antiAlias,
|
|
||||||
child: TabBar(
|
|
||||||
indicatorWeight: tabIndicatorWeight,
|
|
||||||
tabs: tabs.map((t) => t.item1).toList(),
|
|
||||||
controller: _tabController,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
ConstrainedBox(
|
|
||||||
constraints: BoxConstraints(
|
|
||||||
maxHeight: maxHeight,
|
|
||||||
),
|
|
||||||
child: TabBarView(
|
|
||||||
controller: _tabController,
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
|
||||||
children: tabs
|
|
||||||
.map((t) => SingleChildScrollView(
|
|
||||||
child: t.item2,
|
|
||||||
))
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
final actionsWidget = Padding(
|
|
||||||
padding: AvesDialog.actionsPadding,
|
|
||||||
child: OverflowBar(
|
|
||||||
alignment: MainAxisAlignment.end,
|
|
||||||
spacing: AvesDialog.buttonPadding.horizontal / 2,
|
|
||||||
overflowAlignment: OverflowBarAlignment.end,
|
|
||||||
children: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.pop(context),
|
|
||||||
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
key: const Key('button-apply'),
|
|
||||||
onPressed: () => Navigator.pop(context, Tuple3(_selectedSort, _selectedGroup, _selectedLayout)),
|
|
||||||
child: Text(l10n.applyButtonLabel),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
Widget dialogChild = LayoutBuilder(
|
|
||||||
builder: (context, constraints) {
|
|
||||||
final availableBodyWidth = constraints.maxWidth;
|
|
||||||
final maxWidth = min(availableBodyWidth, tabBodyMaxWidth(context));
|
|
||||||
return ConstrainedBox(
|
|
||||||
constraints: BoxConstraints(
|
|
||||||
maxWidth: maxWidth,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
Flexible(child: contentWidget),
|
|
||||||
actionsWidget,
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return Dialog(
|
|
||||||
shape: AvesDialog.shape(context),
|
|
||||||
child: dialogChild,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildRadioListTile<T>(T value, String title, T? Function() get, void Function(T value) set) {
|
Widget _buildSection<T>({
|
||||||
return RadioListTile<T>(
|
bool show = true,
|
||||||
// key is expected by test driver
|
required IconData icon,
|
||||||
key: Key(value.toString()),
|
required String title,
|
||||||
value: value,
|
Widget? trailing,
|
||||||
groupValue: get(),
|
required Map<T, String> options,
|
||||||
onChanged: (v) => setState(() => set(v as T)),
|
required T value,
|
||||||
title: Text(
|
required ValueChanged<T?> onChanged,
|
||||||
title,
|
Widget? bottom,
|
||||||
softWrap: false,
|
|
||||||
overflow: TextOverflow.fade,
|
|
||||||
maxLines: 1,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// tabs
|
|
||||||
|
|
||||||
Tab _buildTab(
|
|
||||||
BuildContext context,
|
|
||||||
Key key,
|
|
||||||
IconData icon,
|
|
||||||
String text, {
|
|
||||||
Color? color,
|
|
||||||
}) {
|
}) {
|
||||||
// cannot use `IconTheme` over `TabBar` to change size,
|
if (options.isEmpty || !show) return const SizedBox();
|
||||||
// because `TabBar` does so internally
|
|
||||||
final textScaleFactor = MediaQuery.textScaleFactorOf(context);
|
final iconSize = IconTheme.of(context).size! * MediaQuery.textScaleFactorOf(context);
|
||||||
final iconSize = IconTheme.of(context).size! * textScaleFactor;
|
return TooltipTheme(
|
||||||
return Tab(
|
data: TooltipTheme.of(context).copyWith(
|
||||||
key: key,
|
preferBelow: false,
|
||||||
height: tabBarHeight(context),
|
),
|
||||||
child: Column(
|
child: Padding(
|
||||||
mainAxisSize: MainAxisSize.min,
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
children: [
|
child: Column(
|
||||||
Icon(
|
mainAxisSize: MainAxisSize.min,
|
||||||
icon,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
size: iconSize,
|
children: [
|
||||||
color: color,
|
const SizedBox(height: 8),
|
||||||
),
|
ConstrainedBox(
|
||||||
const SizedBox(height: 4),
|
constraints: const BoxConstraints(
|
||||||
Text(
|
minHeight: kMinInteractiveDimension,
|
||||||
text,
|
),
|
||||||
style: TextStyle(color: color),
|
child: Row(
|
||||||
softWrap: false,
|
children: [
|
||||||
overflow: TextOverflow.fade,
|
Icon(icon),
|
||||||
),
|
const SizedBox(width: 16),
|
||||||
],
|
Expanded(
|
||||||
|
child: HighlightTitle(
|
||||||
|
title: title,
|
||||||
|
showHighlight: false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (trailing != null) trailing,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsetsDirectional.only(start: iconSize + 16, end: 12),
|
||||||
|
child: TextDropdownButton<T>(
|
||||||
|
values: options.keys.toList(),
|
||||||
|
valueText: (v) => options[v] ?? v.toString(),
|
||||||
|
value: value,
|
||||||
|
onChanged: (v) => setState(() => onChanged(v)),
|
||||||
|
isExpanded: true,
|
||||||
|
dropdownColor: Themes.thirdLayerColor(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (bottom != null)
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsetsDirectional.only(start: iconSize + 16),
|
||||||
|
child: bottom,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get canGroup => _selectedSort == EntrySortFactor.date || _selectedSort is ChipSortFactor;
|
|
||||||
|
|
||||||
void _onTabChange() {
|
|
||||||
if (!canGroup && _tabController.index == groupTabIndex) {
|
|
||||||
_tabController.index = _tabController.previousIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// based on `ListTile` height computation (one line, no subtitle, not dense)
|
|
||||||
double singleOptionTileHeight(BuildContext context) => 56.0 + Theme.of(context).visualDensity.baseSizeAdjustment.dy;
|
|
||||||
|
|
||||||
double tabBodyMaxWidth(BuildContext context) {
|
|
||||||
final para = RenderParagraph(
|
|
||||||
TextSpan(text: _optionLines, style: Theme.of(context).textTheme.subtitle1!),
|
|
||||||
textDirection: TextDirection.ltr,
|
|
||||||
textScaleFactor: MediaQuery.textScaleFactorOf(context),
|
|
||||||
)..layout(const BoxConstraints(), parentUsesSize: true);
|
|
||||||
final textWidth = para.getMaxIntrinsicWidth(double.infinity);
|
|
||||||
|
|
||||||
// from `RadioListTile` layout
|
|
||||||
const contentPadding = 32;
|
|
||||||
const leadingWidth = kMinInteractiveDimension + 8;
|
|
||||||
return contentPadding + leadingWidth + textWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
double tabBodyMaxHeight(BuildContext context) =>
|
|
||||||
[
|
|
||||||
sortOptions,
|
|
||||||
groupOptions,
|
|
||||||
layoutOptions,
|
|
||||||
].map((v) => v.length).fold(0, max) *
|
|
||||||
singleOptionTileHeight(context);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'package:aves/model/entry.dart';
|
||||||
import 'package:aves/ref/languages.dart';
|
import 'package:aves/ref/languages.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/theme/themes.dart';
|
import 'package:aves/theme/themes.dart';
|
||||||
|
import 'package:aves/widgets/common/basic/text_dropdown_button.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/viewer/video/controller.dart';
|
import 'package:aves/widgets/viewer/video/controller.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
@ -123,25 +124,6 @@ class _VideoStreamSelectionDialogState extends State<VideoStreamSelectionDialog>
|
||||||
return common;
|
return common;
|
||||||
}
|
}
|
||||||
|
|
||||||
DropdownMenuItem<StreamSummary> _buildMenuItem(StreamSummary? value) {
|
|
||||||
return DropdownMenuItem(
|
|
||||||
value: value,
|
|
||||||
child: Text(_streamName(value)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildSelectedItem(StreamSummary? v) {
|
|
||||||
return Align(
|
|
||||||
alignment: AlignmentDirectional.centerStart,
|
|
||||||
child: Text(
|
|
||||||
_streamName(v),
|
|
||||||
softWrap: false,
|
|
||||||
overflow: TextOverflow.fade,
|
|
||||||
maxLines: 1,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Widget> _buildSection({
|
List<Widget> _buildSection({
|
||||||
required IconData icon,
|
required IconData icon,
|
||||||
required String title,
|
required String title,
|
||||||
|
@ -162,9 +144,9 @@ class _VideoStreamSelectionDialogState extends State<VideoStreamSelectionDialog>
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
child: DropdownButton<StreamSummary>(
|
child: TextDropdownButton<StreamSummary>(
|
||||||
items: streams.map(_buildMenuItem).toList(),
|
values: streams.whereNotNull().toList(),
|
||||||
selectedItemBuilder: (context) => streams.map(_buildSelectedItem).toList(),
|
valueText: _streamName,
|
||||||
value: current,
|
value: current,
|
||||||
onChanged: streams.length > 1 ? (newValue) => setState(() => setter(newValue)) : null,
|
onChanged: streams.length > 1 ? (newValue) => setState(() => setter(newValue)) : null,
|
||||||
isExpanded: true,
|
isExpanded: true,
|
||||||
|
|
|
@ -7,7 +7,7 @@ import 'package:aves/model/selection.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/model/source/album.dart';
|
import 'package:aves/model/source/album.dart';
|
||||||
import 'package:aves/model/source/collection_source.dart';
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
import 'package:aves/model/source/enums.dart';
|
import 'package:aves/model/source/enums/enums.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/widgets/common/basic/menu.dart';
|
import 'package:aves/widgets/common/basic/menu.dart';
|
||||||
|
|
|
@ -4,7 +4,7 @@ import 'package:aves/model/filters/filters.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/model/source/album.dart';
|
import 'package:aves/model/source/album.dart';
|
||||||
import 'package:aves/model/source/collection_source.dart';
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
import 'package:aves/model/source/enums.dart';
|
import 'package:aves/model/source/enums/enums.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/utils/android_file_utils.dart';
|
import 'package:aves/utils/android_file_utils.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
|
@ -25,12 +25,12 @@ class AlbumListPage extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final source = context.read<CollectionSource>();
|
final source = context.read<CollectionSource>();
|
||||||
return Selector<Settings, Tuple3<AlbumChipGroupFactor, ChipSortFactor, Set<CollectionFilter>>>(
|
return Selector<Settings, Tuple4<AlbumChipGroupFactor, ChipSortFactor, bool, Set<CollectionFilter>>>(
|
||||||
selector: (context, s) => Tuple3(s.albumGroupFactor, s.albumSortFactor, s.pinnedFilters),
|
selector: (context, s) => Tuple4(s.albumGroupFactor, s.albumSortFactor, s.albumSortReverse, s.pinnedFilters),
|
||||||
shouldRebuild: (t1, t2) {
|
shouldRebuild: (t1, t2) {
|
||||||
// `Selector` by default uses `DeepCollectionEquality`, which does not go deep in collections within `TupleN`
|
// `Selector` by default uses `DeepCollectionEquality`, which does not go deep in collections within `TupleN`
|
||||||
const eq = DeepCollectionEquality();
|
const eq = DeepCollectionEquality();
|
||||||
return !(eq.equals(t1.item1, t2.item1) && eq.equals(t1.item2, t2.item2) && eq.equals(t1.item3, t2.item3));
|
return !(eq.equals(t1.item1, t2.item1) && eq.equals(t1.item2, t2.item2) && eq.equals(t1.item3, t2.item3) && eq.equals(t1.item4, t2.item4));
|
||||||
},
|
},
|
||||||
builder: (context, s, child) {
|
builder: (context, s, child) {
|
||||||
return ValueListenableBuilder<bool>(
|
return ValueListenableBuilder<bool>(
|
||||||
|
@ -75,7 +75,7 @@ class AlbumListPage extends StatelessWidget {
|
||||||
static List<FilterGridItem<AlbumFilter>> getAlbumGridItems(BuildContext context, CollectionSource source) {
|
static List<FilterGridItem<AlbumFilter>> getAlbumGridItems(BuildContext context, CollectionSource source) {
|
||||||
final filters = source.rawAlbums.map((album) => AlbumFilter(album, source.getAlbumDisplayName(context, album))).toSet();
|
final filters = source.rawAlbums.map((album) => AlbumFilter(album, source.getAlbumDisplayName(context, album))).toSet();
|
||||||
|
|
||||||
return FilterNavigationPage.sort(settings.albumSortFactor, source, filters);
|
return FilterNavigationPage.sort(settings.albumSortFactor, settings.albumSortReverse, source, filters);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Map<ChipSectionKey, List<FilterGridItem<AlbumFilter>>> groupToSections(BuildContext context, CollectionSource source, Iterable<FilterGridItem<AlbumFilter>> sortedMapEntries) {
|
static Map<ChipSectionKey, List<FilterGridItem<AlbumFilter>>> groupToSections(BuildContext context, CollectionSource source, Iterable<FilterGridItem<AlbumFilter>> sortedMapEntries) {
|
||||||
|
|
|
@ -9,7 +9,8 @@ import 'package:aves/model/highlight.dart';
|
||||||
import 'package:aves/model/selection.dart';
|
import 'package:aves/model/selection.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/model/source/collection_source.dart';
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
import 'package:aves/model/source/enums.dart';
|
import 'package:aves/model/source/enums/enums.dart';
|
||||||
|
import 'package:aves/model/source/enums/view.dart';
|
||||||
import 'package:aves/services/common/image_op_events.dart';
|
import 'package:aves/services/common/image_op_events.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
import 'package:aves/services/media/enums.dart';
|
import 'package:aves/services/media/enums.dart';
|
||||||
|
@ -44,12 +45,24 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate<AlbumFilter> with
|
||||||
@override
|
@override
|
||||||
set sortFactor(ChipSortFactor factor) => settings.albumSortFactor = factor;
|
set sortFactor(ChipSortFactor factor) => settings.albumSortFactor = factor;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get sortReverse => settings.albumSortReverse;
|
||||||
|
|
||||||
|
@override
|
||||||
|
set sortReverse(bool value) => settings.albumSortReverse = value;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
TileLayout get tileLayout => settings.getTileLayout(AlbumListPage.routeName);
|
TileLayout get tileLayout => settings.getTileLayout(AlbumListPage.routeName);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
set tileLayout(TileLayout tileLayout) => settings.setTileLayout(AlbumListPage.routeName, tileLayout);
|
set tileLayout(TileLayout tileLayout) => settings.setTileLayout(AlbumListPage.routeName, tileLayout);
|
||||||
|
|
||||||
|
static const _groupOptions = [
|
||||||
|
AlbumChipGroupFactor.importance,
|
||||||
|
AlbumChipGroupFactor.volume,
|
||||||
|
AlbumChipGroupFactor.none,
|
||||||
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool isVisible(
|
bool isVisible(
|
||||||
ChipSetAction action, {
|
ChipSetAction action, {
|
||||||
|
@ -125,32 +138,21 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate<AlbumFilter> with
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> configureView(BuildContext context) async {
|
Future<void> configureView(BuildContext context) async {
|
||||||
final initialValue = Tuple3(
|
final initialValue = Tuple4(
|
||||||
sortFactor,
|
sortFactor,
|
||||||
settings.albumGroupFactor,
|
settings.albumGroupFactor,
|
||||||
tileLayout,
|
tileLayout,
|
||||||
|
sortReverse,
|
||||||
);
|
);
|
||||||
final value = await showDialog<Tuple3<ChipSortFactor?, AlbumChipGroupFactor?, TileLayout?>>(
|
final value = await showDialog<Tuple4<ChipSortFactor?, AlbumChipGroupFactor?, TileLayout?, bool>>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
final l10n = context.l10n;
|
|
||||||
return TileViewDialog<ChipSortFactor, AlbumChipGroupFactor, TileLayout>(
|
return TileViewDialog<ChipSortFactor, AlbumChipGroupFactor, TileLayout>(
|
||||||
initialValue: initialValue,
|
initialValue: initialValue,
|
||||||
sortOptions: {
|
sortOptions: Map.fromEntries(ChipSetActionDelegate.sortOptions.map((v) => MapEntry(v, v.getName(context)))),
|
||||||
ChipSortFactor.date: l10n.sortByDate,
|
groupOptions: Map.fromEntries(_groupOptions.map((v) => MapEntry(v, v.getName(context)))),
|
||||||
ChipSortFactor.name: l10n.sortByName,
|
layoutOptions: Map.fromEntries(ChipSetActionDelegate.layoutOptions.map((v) => MapEntry(v, v.getName(context)))),
|
||||||
ChipSortFactor.count: l10n.sortByItemCount,
|
sortOrder: (factor, reverse) => factor.getOrderName(context, reverse),
|
||||||
ChipSortFactor.size: l10n.sortBySize,
|
|
||||||
},
|
|
||||||
groupOptions: {
|
|
||||||
AlbumChipGroupFactor.importance: l10n.albumGroupTier,
|
|
||||||
AlbumChipGroupFactor.volume: l10n.albumGroupVolume,
|
|
||||||
AlbumChipGroupFactor.none: l10n.albumGroupNone,
|
|
||||||
},
|
|
||||||
layoutOptions: {
|
|
||||||
TileLayout.grid: l10n.tileLayoutGrid,
|
|
||||||
TileLayout.list: l10n.tileLayoutList,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -160,6 +162,7 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate<AlbumFilter> with
|
||||||
sortFactor = value.item1!;
|
sortFactor = value.item1!;
|
||||||
settings.albumGroupFactor = value.item2!;
|
settings.albumGroupFactor = value.item2!;
|
||||||
tileLayout = value.item3!;
|
tileLayout = value.item3!;
|
||||||
|
sortReverse = value.item4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,8 @@ import 'package:aves/model/selection.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/model/source/collection_lens.dart';
|
import 'package:aves/model/source/collection_lens.dart';
|
||||||
import 'package:aves/model/source/collection_source.dart';
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
import 'package:aves/model/source/enums.dart';
|
import 'package:aves/model/source/enums/enums.dart';
|
||||||
|
import 'package:aves/model/source/enums/view.dart';
|
||||||
import 'package:aves/theme/colors.dart';
|
import 'package:aves/theme/colors.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/widgets/common/action_mixins/feedback.dart';
|
import 'package:aves/widgets/common/action_mixins/feedback.dart';
|
||||||
|
@ -37,10 +38,26 @@ abstract class ChipSetActionDelegate<T extends CollectionFilter> with FeedbackMi
|
||||||
|
|
||||||
set sortFactor(ChipSortFactor factor);
|
set sortFactor(ChipSortFactor factor);
|
||||||
|
|
||||||
|
bool get sortReverse;
|
||||||
|
|
||||||
|
set sortReverse(bool value);
|
||||||
|
|
||||||
TileLayout get tileLayout;
|
TileLayout get tileLayout;
|
||||||
|
|
||||||
set tileLayout(TileLayout tileLayout);
|
set tileLayout(TileLayout tileLayout);
|
||||||
|
|
||||||
|
static const sortOptions = [
|
||||||
|
ChipSortFactor.date,
|
||||||
|
ChipSortFactor.name,
|
||||||
|
ChipSortFactor.count,
|
||||||
|
ChipSortFactor.size,
|
||||||
|
];
|
||||||
|
|
||||||
|
static const layoutOptions = [
|
||||||
|
TileLayout.grid,
|
||||||
|
TileLayout.list,
|
||||||
|
];
|
||||||
|
|
||||||
bool isVisible(
|
bool isVisible(
|
||||||
ChipSetAction action, {
|
ChipSetAction action, {
|
||||||
required AppMode appMode,
|
required AppMode appMode,
|
||||||
|
@ -194,27 +211,20 @@ abstract class ChipSetActionDelegate<T extends CollectionFilter> with FeedbackMi
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> configureView(BuildContext context) async {
|
Future<void> configureView(BuildContext context) async {
|
||||||
final initialValue = Tuple3(
|
final initialValue = Tuple4(
|
||||||
sortFactor,
|
sortFactor,
|
||||||
null,
|
null,
|
||||||
tileLayout,
|
tileLayout,
|
||||||
|
sortReverse,
|
||||||
);
|
);
|
||||||
final value = await showDialog<Tuple3<ChipSortFactor?, void, TileLayout?>>(
|
final value = await showDialog<Tuple4<ChipSortFactor?, void, TileLayout?, bool>>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
final l10n = context.l10n;
|
|
||||||
return TileViewDialog<ChipSortFactor, void, TileLayout>(
|
return TileViewDialog<ChipSortFactor, void, TileLayout>(
|
||||||
initialValue: initialValue,
|
initialValue: initialValue,
|
||||||
sortOptions: {
|
sortOptions: Map.fromEntries(sortOptions.map((v) => MapEntry(v, v.getName(context)))),
|
||||||
ChipSortFactor.date: l10n.sortByDate,
|
layoutOptions: Map.fromEntries(layoutOptions.map((v) => MapEntry(v, v.getName(context)))),
|
||||||
ChipSortFactor.name: l10n.sortByName,
|
sortOrder: (factor, reverse) => factor.getOrderName(context, reverse),
|
||||||
ChipSortFactor.count: l10n.sortByItemCount,
|
|
||||||
ChipSortFactor.size: l10n.sortBySize,
|
|
||||||
},
|
|
||||||
layoutOptions: {
|
|
||||||
TileLayout.grid: l10n.tileLayoutGrid,
|
|
||||||
TileLayout.list: l10n.tileLayoutList,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -223,6 +233,7 @@ abstract class ChipSetActionDelegate<T extends CollectionFilter> with FeedbackMi
|
||||||
if (value != null && initialValue != value) {
|
if (value != null && initialValue != value) {
|
||||||
sortFactor = value.item1!;
|
sortFactor = value.item1!;
|
||||||
tileLayout = value.item3!;
|
tileLayout = value.item3!;
|
||||||
|
sortReverse = value.item4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:aves/model/filters/filters.dart';
|
import 'package:aves/model/filters/filters.dart';
|
||||||
import 'package:aves/model/filters/location.dart';
|
import 'package:aves/model/filters/location.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/model/source/enums.dart';
|
import 'package:aves/model/source/enums/enums.dart';
|
||||||
import 'package:aves/widgets/filter_grids/common/action_delegates/chip_set.dart';
|
import 'package:aves/widgets/filter_grids/common/action_delegates/chip_set.dart';
|
||||||
import 'package:aves/widgets/filter_grids/countries_page.dart';
|
import 'package:aves/widgets/filter_grids/countries_page.dart';
|
||||||
|
|
||||||
|
@ -19,6 +19,12 @@ class CountryChipSetActionDelegate extends ChipSetActionDelegate<LocationFilter>
|
||||||
@override
|
@override
|
||||||
set sortFactor(ChipSortFactor factor) => settings.countrySortFactor = factor;
|
set sortFactor(ChipSortFactor factor) => settings.countrySortFactor = factor;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get sortReverse => settings.countrySortReverse;
|
||||||
|
|
||||||
|
@override
|
||||||
|
set sortReverse(bool value) => settings.countrySortReverse = value;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
TileLayout get tileLayout => settings.getTileLayout(CountryListPage.routeName);
|
TileLayout get tileLayout => settings.getTileLayout(CountryListPage.routeName);
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:aves/model/filters/filters.dart';
|
import 'package:aves/model/filters/filters.dart';
|
||||||
import 'package:aves/model/filters/tag.dart';
|
import 'package:aves/model/filters/tag.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/model/source/enums.dart';
|
import 'package:aves/model/source/enums/enums.dart';
|
||||||
import 'package:aves/widgets/filter_grids/common/action_delegates/chip_set.dart';
|
import 'package:aves/widgets/filter_grids/common/action_delegates/chip_set.dart';
|
||||||
import 'package:aves/widgets/filter_grids/tags_page.dart';
|
import 'package:aves/widgets/filter_grids/tags_page.dart';
|
||||||
|
|
||||||
|
@ -19,6 +19,12 @@ class TagChipSetActionDelegate extends ChipSetActionDelegate<TagFilter> {
|
||||||
@override
|
@override
|
||||||
set sortFactor(ChipSortFactor factor) => settings.tagSortFactor = factor;
|
set sortFactor(ChipSortFactor factor) => settings.tagSortFactor = factor;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get sortReverse => settings.tagSortReverse;
|
||||||
|
|
||||||
|
@override
|
||||||
|
set sortReverse(bool value) => settings.tagSortReverse = value;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
TileLayout get tileLayout => settings.getTileLayout(TagListPage.routeName);
|
TileLayout get tileLayout => settings.getTileLayout(TagListPage.routeName);
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:aves/model/filters/filters.dart';
|
import 'package:aves/model/filters/filters.dart';
|
||||||
import 'package:aves/model/source/collection_source.dart';
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
import 'package:aves/model/source/enums.dart';
|
import 'package:aves/model/source/enums/enums.dart';
|
||||||
import 'package:aves/utils/file_utils.dart';
|
import 'package:aves/utils/file_utils.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/grid/draggable_thumb_label.dart';
|
import 'package:aves/widgets/common/grid/draggable_thumb_label.dart';
|
||||||
|
|
|
@ -6,7 +6,7 @@ import 'package:aves/model/highlight.dart';
|
||||||
import 'package:aves/model/query.dart';
|
import 'package:aves/model/query.dart';
|
||||||
import 'package:aves/model/selection.dart';
|
import 'package:aves/model/selection.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/model/source/enums.dart';
|
import 'package:aves/model/source/enums/enums.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/widgets/common/basic/draggable_scrollbar.dart';
|
import 'package:aves/widgets/common/basic/draggable_scrollbar.dart';
|
||||||
import 'package:aves/widgets/common/basic/insets.dart';
|
import 'package:aves/widgets/common/basic/insets.dart';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:aves/model/filters/filters.dart';
|
import 'package:aves/model/filters/filters.dart';
|
||||||
import 'package:aves/model/source/collection_source.dart';
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
import 'package:aves/model/source/enums.dart';
|
import 'package:aves/model/source/enums/enums.dart';
|
||||||
import 'package:aves/utils/time_utils.dart';
|
import 'package:aves/utils/time_utils.dart';
|
||||||
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
|
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
|
||||||
import 'package:aves/widgets/common/providers/selection_provider.dart';
|
import 'package:aves/widgets/common/providers/selection_provider.dart';
|
||||||
|
@ -56,7 +56,12 @@ class FilterNavigationPage<T extends CollectionFilter, CSAD extends ChipSetActio
|
||||||
return a.filter.compareTo(b.filter);
|
return a.filter.compareTo(b.filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
static List<FilterGridItem<T>> sort<T extends CollectionFilter, CSAD extends ChipSetActionDelegate<T>>(ChipSortFactor sortFactor, CollectionSource source, Set<T> filters) {
|
static List<FilterGridItem<T>> sort<T extends CollectionFilter, CSAD extends ChipSetActionDelegate<T>>(
|
||||||
|
ChipSortFactor sortFactor,
|
||||||
|
bool reverse,
|
||||||
|
CollectionSource source,
|
||||||
|
Set<T> filters,
|
||||||
|
) {
|
||||||
List<FilterGridItem<T>> toGridItem(CollectionSource source, Set<T> filters) {
|
List<FilterGridItem<T>> toGridItem(CollectionSource source, Set<T> filters) {
|
||||||
return filters
|
return filters
|
||||||
.map((filter) => FilterGridItem(
|
.map((filter) => FilterGridItem(
|
||||||
|
@ -87,6 +92,9 @@ class FilterNavigationPage<T extends CollectionFilter, CSAD extends ChipSetActio
|
||||||
allMapEntries = toGridItem(source, filters);
|
allMapEntries = toGridItem(source, filters);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (reverse) {
|
||||||
|
allMapEntries = allMapEntries.reversed.toList();
|
||||||
|
}
|
||||||
return allMapEntries;
|
return allMapEntries;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import 'package:aves/model/filters/filters.dart';
|
||||||
import 'package:aves/model/selection.dart';
|
import 'package:aves/model/selection.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/model/source/collection_source.dart';
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
import 'package:aves/model/source/enums.dart';
|
import 'package:aves/model/source/enums/enums.dart';
|
||||||
import 'package:aves/widgets/collection/collection_page.dart';
|
import 'package:aves/widgets/collection/collection_page.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/grid/scaling.dart';
|
import 'package:aves/widgets/common/grid/scaling.dart';
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'package:aves/model/filters/filters.dart';
|
||||||
import 'package:aves/model/filters/location.dart';
|
import 'package:aves/model/filters/location.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/model/source/collection_source.dart';
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
import 'package:aves/model/source/enums.dart';
|
import 'package:aves/model/source/enums/enums.dart';
|
||||||
import 'package:aves/model/source/location.dart';
|
import 'package:aves/model/source/location.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
|
@ -23,12 +23,12 @@ class CountryListPage extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final source = context.read<CollectionSource>();
|
final source = context.read<CollectionSource>();
|
||||||
return Selector<Settings, Tuple2<ChipSortFactor, Set<CollectionFilter>>>(
|
return Selector<Settings, Tuple3<ChipSortFactor, bool, Set<CollectionFilter>>>(
|
||||||
selector: (context, s) => Tuple2(s.countrySortFactor, s.pinnedFilters),
|
selector: (context, s) => Tuple3(s.countrySortFactor, s.countrySortReverse, s.pinnedFilters),
|
||||||
shouldRebuild: (t1, t2) {
|
shouldRebuild: (t1, t2) {
|
||||||
// `Selector` by default uses `DeepCollectionEquality`, which does not go deep in collections within `TupleN`
|
// `Selector` by default uses `DeepCollectionEquality`, which does not go deep in collections within `TupleN`
|
||||||
const eq = DeepCollectionEquality();
|
const eq = DeepCollectionEquality();
|
||||||
return !(eq.equals(t1.item1, t2.item1) && eq.equals(t1.item2, t2.item2));
|
return !(eq.equals(t1.item1, t2.item1) && eq.equals(t1.item2, t2.item2) && eq.equals(t1.item3, t2.item3));
|
||||||
},
|
},
|
||||||
builder: (context, s, child) {
|
builder: (context, s, child) {
|
||||||
return StreamBuilder(
|
return StreamBuilder(
|
||||||
|
@ -60,7 +60,7 @@ class CountryListPage extends StatelessWidget {
|
||||||
List<FilterGridItem<LocationFilter>> _getGridItems(CollectionSource source) {
|
List<FilterGridItem<LocationFilter>> _getGridItems(CollectionSource source) {
|
||||||
final filters = source.sortedCountries.map((location) => LocationFilter(LocationLevel.country, location)).toSet();
|
final filters = source.sortedCountries.map((location) => LocationFilter(LocationLevel.country, location)).toSet();
|
||||||
|
|
||||||
return FilterNavigationPage.sort(settings.countrySortFactor, source, filters);
|
return FilterNavigationPage.sort(settings.countrySortFactor, settings.countrySortReverse, source, filters);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Map<ChipSectionKey, List<FilterGridItem<LocationFilter>>> _groupToSections(Iterable<FilterGridItem<LocationFilter>> sortedMapEntries) {
|
static Map<ChipSectionKey, List<FilterGridItem<LocationFilter>>> _groupToSections(Iterable<FilterGridItem<LocationFilter>> sortedMapEntries) {
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'package:aves/model/filters/filters.dart';
|
||||||
import 'package:aves/model/filters/tag.dart';
|
import 'package:aves/model/filters/tag.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/model/source/collection_source.dart';
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
import 'package:aves/model/source/enums.dart';
|
import 'package:aves/model/source/enums/enums.dart';
|
||||||
import 'package:aves/model/source/tag.dart';
|
import 'package:aves/model/source/tag.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
|
@ -23,12 +23,12 @@ class TagListPage extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final source = context.read<CollectionSource>();
|
final source = context.read<CollectionSource>();
|
||||||
return Selector<Settings, Tuple2<ChipSortFactor, Set<CollectionFilter>>>(
|
return Selector<Settings, Tuple3<ChipSortFactor, bool, Set<CollectionFilter>>>(
|
||||||
selector: (context, s) => Tuple2(s.tagSortFactor, s.pinnedFilters),
|
selector: (context, s) => Tuple3(s.tagSortFactor, s.tagSortReverse, s.pinnedFilters),
|
||||||
shouldRebuild: (t1, t2) {
|
shouldRebuild: (t1, t2) {
|
||||||
// `Selector` by default uses `DeepCollectionEquality`, which does not go deep in collections within `TupleN`
|
// `Selector` by default uses `DeepCollectionEquality`, which does not go deep in collections within `TupleN`
|
||||||
const eq = DeepCollectionEquality();
|
const eq = DeepCollectionEquality();
|
||||||
return !(eq.equals(t1.item1, t2.item1) && eq.equals(t1.item2, t2.item2));
|
return !(eq.equals(t1.item1, t2.item1) && eq.equals(t1.item2, t2.item2) && eq.equals(t1.item3, t2.item3));
|
||||||
},
|
},
|
||||||
builder: (context, s, child) {
|
builder: (context, s, child) {
|
||||||
return StreamBuilder(
|
return StreamBuilder(
|
||||||
|
@ -60,7 +60,7 @@ class TagListPage extends StatelessWidget {
|
||||||
List<FilterGridItem<TagFilter>> _getGridItems(CollectionSource source) {
|
List<FilterGridItem<TagFilter>> _getGridItems(CollectionSource source) {
|
||||||
final filters = source.sortedTags.map(TagFilter.new).toSet();
|
final filters = source.sortedTags.map(TagFilter.new).toSet();
|
||||||
|
|
||||||
return FilterNavigationPage.sort(settings.tagSortFactor, source, filters);
|
return FilterNavigationPage.sort(settings.tagSortFactor, settings.tagSortReverse, source, filters);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Map<ChipSectionKey, List<FilterGridItem<TagFilter>>> _groupToSections(Iterable<FilterGridItem<TagFilter>> sortedMapEntries) {
|
static Map<ChipSectionKey, List<FilterGridItem<TagFilter>>> _groupToSections(Iterable<FilterGridItem<TagFilter>> sortedMapEntries) {
|
||||||
|
|
|
@ -9,7 +9,7 @@ import 'package:aves/model/settings/enums/home_page.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/model/source/collection_lens.dart';
|
import 'package:aves/model/source/collection_lens.dart';
|
||||||
import 'package:aves/model/source/collection_source.dart';
|
import 'package:aves/model/source/collection_source.dart';
|
||||||
import 'package:aves/model/source/enums.dart';
|
import 'package:aves/model/source/enums/enums.dart';
|
||||||
import 'package:aves/services/analysis_service.dart';
|
import 'package:aves/services/analysis_service.dart';
|
||||||
import 'package:aves/services/common/services.dart';
|
import 'package:aves/services/common/services.dart';
|
||||||
import 'package:aves/services/global_search.dart';
|
import 'package:aves/services/global_search.dart';
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'package:aves/main_play.dart' as app;
|
||||||
import 'package:aves/model/settings/defaults.dart';
|
import 'package:aves/model/settings/defaults.dart';
|
||||||
import 'package:aves/model/settings/enums/enums.dart';
|
import 'package:aves/model/settings/enums/enums.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/model/source/enums.dart';
|
import 'package:aves/model/source/enums/enums.dart';
|
||||||
import 'package:aves/widgets/filter_grids/countries_page.dart';
|
import 'package:aves/widgets/filter_grids/countries_page.dart';
|
||||||
import 'package:flutter_driver/driver_extension.dart';
|
import 'package:flutter_driver/driver_extension.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// ignore_for_file: avoid_print
|
// ignore_for_file: avoid_print
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:aves/model/source/enums.dart';
|
import 'package:aves/model/source/enums/enums.dart';
|
||||||
import 'package:flutter_driver/flutter_driver.dart';
|
import 'package:flutter_driver/flutter_driver.dart';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
|
@ -3,6 +3,15 @@
|
||||||
"entryInfoActionEditTitleDescription",
|
"entryInfoActionEditTitleDescription",
|
||||||
"filterNoDateLabel",
|
"filterNoDateLabel",
|
||||||
"filterNoTitleLabel",
|
"filterNoTitleLabel",
|
||||||
|
"viewDialogReverseSortOrder",
|
||||||
|
"sortOrderNewestFirst",
|
||||||
|
"sortOrderOldestFirst",
|
||||||
|
"sortOrderAtoZ",
|
||||||
|
"sortOrderZtoA",
|
||||||
|
"sortOrderHighestFirst",
|
||||||
|
"sortOrderLowestFirst",
|
||||||
|
"sortOrderLargestFirst",
|
||||||
|
"sortOrderSmallestFirst",
|
||||||
"searchMetadataSectionTitle"
|
"searchMetadataSectionTitle"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -10,6 +19,15 @@
|
||||||
"entryInfoActionEditTitleDescription",
|
"entryInfoActionEditTitleDescription",
|
||||||
"filterNoDateLabel",
|
"filterNoDateLabel",
|
||||||
"filterNoTitleLabel",
|
"filterNoTitleLabel",
|
||||||
|
"viewDialogReverseSortOrder",
|
||||||
|
"sortOrderNewestFirst",
|
||||||
|
"sortOrderOldestFirst",
|
||||||
|
"sortOrderAtoZ",
|
||||||
|
"sortOrderZtoA",
|
||||||
|
"sortOrderHighestFirst",
|
||||||
|
"sortOrderLowestFirst",
|
||||||
|
"sortOrderLargestFirst",
|
||||||
|
"sortOrderSmallestFirst",
|
||||||
"searchMetadataSectionTitle"
|
"searchMetadataSectionTitle"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -18,16 +36,46 @@
|
||||||
"filterNoDateLabel",
|
"filterNoDateLabel",
|
||||||
"filterNoTitleLabel",
|
"filterNoTitleLabel",
|
||||||
"filterRecentlyAddedLabel",
|
"filterRecentlyAddedLabel",
|
||||||
|
"viewDialogReverseSortOrder",
|
||||||
|
"sortOrderNewestFirst",
|
||||||
|
"sortOrderOldestFirst",
|
||||||
|
"sortOrderAtoZ",
|
||||||
|
"sortOrderZtoA",
|
||||||
|
"sortOrderHighestFirst",
|
||||||
|
"sortOrderLowestFirst",
|
||||||
|
"sortOrderLargestFirst",
|
||||||
|
"sortOrderSmallestFirst",
|
||||||
"searchMetadataSectionTitle",
|
"searchMetadataSectionTitle",
|
||||||
"settingsConfirmationAfterMoveToBinItems",
|
"settingsConfirmationAfterMoveToBinItems",
|
||||||
"viewerInfoLabelDescription"
|
"viewerInfoLabelDescription"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
"fr": [
|
||||||
|
"viewDialogReverseSortOrder",
|
||||||
|
"sortOrderNewestFirst",
|
||||||
|
"sortOrderOldestFirst",
|
||||||
|
"sortOrderAtoZ",
|
||||||
|
"sortOrderZtoA",
|
||||||
|
"sortOrderHighestFirst",
|
||||||
|
"sortOrderLowestFirst",
|
||||||
|
"sortOrderLargestFirst",
|
||||||
|
"sortOrderSmallestFirst"
|
||||||
|
],
|
||||||
|
|
||||||
"id": [
|
"id": [
|
||||||
"entryInfoActionEditTitleDescription",
|
"entryInfoActionEditTitleDescription",
|
||||||
"filterNoDateLabel",
|
"filterNoDateLabel",
|
||||||
"filterNoTitleLabel",
|
"filterNoTitleLabel",
|
||||||
"filterRecentlyAddedLabel",
|
"filterRecentlyAddedLabel",
|
||||||
|
"viewDialogReverseSortOrder",
|
||||||
|
"sortOrderNewestFirst",
|
||||||
|
"sortOrderOldestFirst",
|
||||||
|
"sortOrderAtoZ",
|
||||||
|
"sortOrderZtoA",
|
||||||
|
"sortOrderHighestFirst",
|
||||||
|
"sortOrderLowestFirst",
|
||||||
|
"sortOrderLargestFirst",
|
||||||
|
"sortOrderSmallestFirst",
|
||||||
"searchMetadataSectionTitle",
|
"searchMetadataSectionTitle",
|
||||||
"settingsConfirmationAfterMoveToBinItems",
|
"settingsConfirmationAfterMoveToBinItems",
|
||||||
"settingsViewerGestureSideTapNext",
|
"settingsViewerGestureSideTapNext",
|
||||||
|
@ -38,6 +86,15 @@
|
||||||
"entryInfoActionEditTitleDescription",
|
"entryInfoActionEditTitleDescription",
|
||||||
"filterNoDateLabel",
|
"filterNoDateLabel",
|
||||||
"filterNoTitleLabel",
|
"filterNoTitleLabel",
|
||||||
|
"viewDialogReverseSortOrder",
|
||||||
|
"sortOrderNewestFirst",
|
||||||
|
"sortOrderOldestFirst",
|
||||||
|
"sortOrderAtoZ",
|
||||||
|
"sortOrderZtoA",
|
||||||
|
"sortOrderHighestFirst",
|
||||||
|
"sortOrderLowestFirst",
|
||||||
|
"sortOrderLargestFirst",
|
||||||
|
"sortOrderSmallestFirst",
|
||||||
"searchMetadataSectionTitle"
|
"searchMetadataSectionTitle"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -46,16 +103,46 @@
|
||||||
"filterNoDateLabel",
|
"filterNoDateLabel",
|
||||||
"filterNoTitleLabel",
|
"filterNoTitleLabel",
|
||||||
"filterRecentlyAddedLabel",
|
"filterRecentlyAddedLabel",
|
||||||
|
"viewDialogReverseSortOrder",
|
||||||
|
"sortOrderNewestFirst",
|
||||||
|
"sortOrderOldestFirst",
|
||||||
|
"sortOrderAtoZ",
|
||||||
|
"sortOrderZtoA",
|
||||||
|
"sortOrderHighestFirst",
|
||||||
|
"sortOrderLowestFirst",
|
||||||
|
"sortOrderLargestFirst",
|
||||||
|
"sortOrderSmallestFirst",
|
||||||
"searchMetadataSectionTitle",
|
"searchMetadataSectionTitle",
|
||||||
"settingsConfirmationAfterMoveToBinItems",
|
"settingsConfirmationAfterMoveToBinItems",
|
||||||
"settingsViewerGestureSideTapNext",
|
"settingsViewerGestureSideTapNext",
|
||||||
"viewerInfoLabelDescription"
|
"viewerInfoLabelDescription"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
"ko": [
|
||||||
|
"viewDialogReverseSortOrder",
|
||||||
|
"sortOrderNewestFirst",
|
||||||
|
"sortOrderOldestFirst",
|
||||||
|
"sortOrderAtoZ",
|
||||||
|
"sortOrderZtoA",
|
||||||
|
"sortOrderHighestFirst",
|
||||||
|
"sortOrderLowestFirst",
|
||||||
|
"sortOrderLargestFirst",
|
||||||
|
"sortOrderSmallestFirst"
|
||||||
|
],
|
||||||
|
|
||||||
"nl": [
|
"nl": [
|
||||||
"entryInfoActionEditTitleDescription",
|
"entryInfoActionEditTitleDescription",
|
||||||
"filterNoDateLabel",
|
"filterNoDateLabel",
|
||||||
"filterNoTitleLabel",
|
"filterNoTitleLabel",
|
||||||
|
"viewDialogReverseSortOrder",
|
||||||
|
"sortOrderNewestFirst",
|
||||||
|
"sortOrderOldestFirst",
|
||||||
|
"sortOrderAtoZ",
|
||||||
|
"sortOrderZtoA",
|
||||||
|
"sortOrderHighestFirst",
|
||||||
|
"sortOrderLowestFirst",
|
||||||
|
"sortOrderLargestFirst",
|
||||||
|
"sortOrderSmallestFirst",
|
||||||
"searchMetadataSectionTitle"
|
"searchMetadataSectionTitle"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -63,6 +150,15 @@
|
||||||
"entryInfoActionEditTitleDescription",
|
"entryInfoActionEditTitleDescription",
|
||||||
"filterNoDateLabel",
|
"filterNoDateLabel",
|
||||||
"filterNoTitleLabel",
|
"filterNoTitleLabel",
|
||||||
|
"viewDialogReverseSortOrder",
|
||||||
|
"sortOrderNewestFirst",
|
||||||
|
"sortOrderOldestFirst",
|
||||||
|
"sortOrderAtoZ",
|
||||||
|
"sortOrderZtoA",
|
||||||
|
"sortOrderHighestFirst",
|
||||||
|
"sortOrderLowestFirst",
|
||||||
|
"sortOrderLargestFirst",
|
||||||
|
"sortOrderSmallestFirst",
|
||||||
"searchMetadataSectionTitle"
|
"searchMetadataSectionTitle"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -72,6 +168,15 @@
|
||||||
"filterNoTitleLabel",
|
"filterNoTitleLabel",
|
||||||
"filterOnThisDayLabel",
|
"filterOnThisDayLabel",
|
||||||
"filterRecentlyAddedLabel",
|
"filterRecentlyAddedLabel",
|
||||||
|
"viewDialogReverseSortOrder",
|
||||||
|
"sortOrderNewestFirst",
|
||||||
|
"sortOrderOldestFirst",
|
||||||
|
"sortOrderAtoZ",
|
||||||
|
"sortOrderZtoA",
|
||||||
|
"sortOrderHighestFirst",
|
||||||
|
"sortOrderLowestFirst",
|
||||||
|
"sortOrderLargestFirst",
|
||||||
|
"sortOrderSmallestFirst",
|
||||||
"searchMetadataSectionTitle",
|
"searchMetadataSectionTitle",
|
||||||
"settingsConfirmationAfterMoveToBinItems",
|
"settingsConfirmationAfterMoveToBinItems",
|
||||||
"settingsViewerGestureSideTapNext",
|
"settingsViewerGestureSideTapNext",
|
||||||
|
@ -100,6 +205,15 @@
|
||||||
"wallpaperTargetLock",
|
"wallpaperTargetLock",
|
||||||
"wallpaperTargetHomeLock",
|
"wallpaperTargetHomeLock",
|
||||||
"menuActionSlideshow",
|
"menuActionSlideshow",
|
||||||
|
"viewDialogReverseSortOrder",
|
||||||
|
"sortOrderNewestFirst",
|
||||||
|
"sortOrderOldestFirst",
|
||||||
|
"sortOrderAtoZ",
|
||||||
|
"sortOrderZtoA",
|
||||||
|
"sortOrderHighestFirst",
|
||||||
|
"sortOrderLowestFirst",
|
||||||
|
"sortOrderLargestFirst",
|
||||||
|
"sortOrderSmallestFirst",
|
||||||
"searchMetadataSectionTitle",
|
"searchMetadataSectionTitle",
|
||||||
"settingsConfirmationAfterMoveToBinItems",
|
"settingsConfirmationAfterMoveToBinItems",
|
||||||
"settingsViewerGestureSideTapNext",
|
"settingsViewerGestureSideTapNext",
|
||||||
|
@ -124,6 +238,15 @@
|
||||||
"entryInfoActionEditTitleDescription",
|
"entryInfoActionEditTitleDescription",
|
||||||
"filterNoDateLabel",
|
"filterNoDateLabel",
|
||||||
"filterNoTitleLabel",
|
"filterNoTitleLabel",
|
||||||
|
"viewDialogReverseSortOrder",
|
||||||
|
"sortOrderNewestFirst",
|
||||||
|
"sortOrderOldestFirst",
|
||||||
|
"sortOrderAtoZ",
|
||||||
|
"sortOrderZtoA",
|
||||||
|
"sortOrderHighestFirst",
|
||||||
|
"sortOrderLowestFirst",
|
||||||
|
"sortOrderLargestFirst",
|
||||||
|
"sortOrderSmallestFirst",
|
||||||
"searchMetadataSectionTitle"
|
"searchMetadataSectionTitle"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue