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
|
||||
|
||||
- Collection: view settings allow changing the sort order (aka ascending/descending)
|
||||
- Collection / Info: edit title via IPTC / XMP
|
||||
- Albums / Countries / Tags: size displayed in list view details, sort by size
|
||||
- Search: `undated` and `untitled` filters
|
||||
|
|
|
@ -417,6 +417,7 @@
|
|||
"viewDialogTabSort": "Sort",
|
||||
"viewDialogTabGroup": "Group",
|
||||
"viewDialogTabLayout": "Layout",
|
||||
"viewDialogReverseSortOrder": "Reverse sort order",
|
||||
|
||||
"tileLayoutGrid": "Grid",
|
||||
"tileLayoutList": "List",
|
||||
|
@ -570,6 +571,15 @@
|
|||
"sortByAlbumFileName": "By album & file name",
|
||||
"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",
|
||||
"albumGroupVolume": "By storage volume",
|
||||
"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/naming_pattern.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/countries_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/enums/enums.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/services.dart';
|
||||
import 'package:aves_map/aves_map.dart';
|
||||
|
@ -75,6 +75,7 @@ class Settings extends ChangeNotifier {
|
|||
// collection
|
||||
static const collectionGroupFactorKey = 'collection_group_factor';
|
||||
static const collectionSortFactorKey = 'collection_sort_factor';
|
||||
static const collectionSortReverseKey = 'collection_sort_reverse';
|
||||
static const collectionBrowsingQuickActionsKey = 'collection_browsing_quick_actions';
|
||||
static const collectionSelectionQuickActionsKey = 'collection_selection_quick_actions';
|
||||
static const showThumbnailFavouriteKey = 'show_thumbnail_favourite';
|
||||
|
@ -90,6 +91,9 @@ class Settings extends ChangeNotifier {
|
|||
static const albumSortFactorKey = 'album_sort_factor';
|
||||
static const countrySortFactorKey = 'country_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 hiddenFiltersKey = 'hidden_filters';
|
||||
|
||||
|
@ -388,6 +392,10 @@ class Settings extends ChangeNotifier {
|
|||
|
||||
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);
|
||||
|
||||
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());
|
||||
|
||||
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 pinnedFilters(Set<CollectionFilter> newValue) => setAndNotify(pinnedFiltersKey, newValue.map((filter) => filter.toJson()).toList());
|
||||
|
@ -828,6 +848,7 @@ class Settings extends ChangeNotifier {
|
|||
case confirmMoveUndatedItemsKey:
|
||||
case confirmAfterMoveToBinKey:
|
||||
case setMetadataDateBeforeFileOpKey:
|
||||
case collectionSortReverseKey:
|
||||
case showThumbnailFavouriteKey:
|
||||
case showThumbnailTagKey:
|
||||
case showThumbnailLocationKey:
|
||||
|
@ -835,6 +856,9 @@ class Settings extends ChangeNotifier {
|
|||
case showThumbnailRatingKey:
|
||||
case showThumbnailRawKey:
|
||||
case showThumbnailVideoDurationKey:
|
||||
case albumSortReverseKey:
|
||||
case countrySortReverseKey:
|
||||
case tagSortReverseKey:
|
||||
case showOverlayOnOpeningKey:
|
||||
case showOverlayMinimapKey:
|
||||
case showOverlayInfoKey:
|
||||
|
|
|
@ -22,13 +22,14 @@ import 'package:aves/utils/collection_utils.dart';
|
|||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'enums.dart';
|
||||
import 'enums/enums.dart';
|
||||
|
||||
class CollectionLens with ChangeNotifier {
|
||||
final CollectionSource source;
|
||||
final Set<CollectionFilter> filters;
|
||||
EntryGroupFactor sectionFactor;
|
||||
EntrySortFactor sortFactor;
|
||||
bool sortReverse;
|
||||
final AChangeNotifier filterChangeNotifier = AChangeNotifier(), sortSectionChangeNotifier = AChangeNotifier();
|
||||
final List<StreamSubscription> _subscriptions = [];
|
||||
int? id;
|
||||
|
@ -49,7 +50,8 @@ class CollectionLens with ChangeNotifier {
|
|||
this.fixedSelection,
|
||||
}) : filters = (filters ?? {}).whereNotNull().toSet(),
|
||||
sectionFactor = settings.collectionSectionFactor,
|
||||
sortFactor = settings.collectionSortFactor {
|
||||
sortFactor = settings.collectionSortFactor,
|
||||
sortReverse = settings.collectionSortReverse {
|
||||
id ??= hashCode;
|
||||
if (listenToSource) {
|
||||
final sourceEvents = source.eventBus;
|
||||
|
@ -84,6 +86,7 @@ class CollectionLens with ChangeNotifier {
|
|||
.where((event) => [
|
||||
Settings.collectionSortFactorKey,
|
||||
Settings.collectionGroupFactorKey,
|
||||
Settings.collectionSortReverseKey,
|
||||
].contains(event.key))
|
||||
.listen((_) => _onSettingsChanged()));
|
||||
refresh();
|
||||
|
@ -218,6 +221,9 @@ class CollectionLens with ChangeNotifier {
|
|||
_filteredSortedEntries.sort(AvesEntry.compareBySize);
|
||||
break;
|
||||
}
|
||||
if (sortReverse) {
|
||||
_filteredSortedEntries = _filteredSortedEntries.reversed.toList();
|
||||
}
|
||||
}
|
||||
|
||||
void _applySection() {
|
||||
|
@ -247,7 +253,8 @@ class CollectionLens with ChangeNotifier {
|
|||
break;
|
||||
case EntrySortFactor.name:
|
||||
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;
|
||||
case EntrySortFactor.rating:
|
||||
sections = groupBy<AvesEntry, EntryRatingSectionKey>(_filteredSortedEntries, (entry) => EntryRatingSectionKey(entry.rating));
|
||||
|
@ -281,12 +288,14 @@ class CollectionLens with ChangeNotifier {
|
|||
void _onSettingsChanged() {
|
||||
final newSortFactor = settings.collectionSortFactor;
|
||||
final newSectionFactor = settings.collectionSectionFactor;
|
||||
final newSortReverse = settings.collectionSortReverse;
|
||||
|
||||
final needSort = sortFactor != newSortFactor;
|
||||
final needSort = sortFactor != newSortFactor || sortReverse != newSortReverse;
|
||||
final needSection = needSort || sectionFactor != newSectionFactor;
|
||||
|
||||
if (needSort) {
|
||||
sortFactor = newSortFactor;
|
||||
sortReverse = newSortReverse;
|
||||
_applySort();
|
||||
}
|
||||
if (needSection) {
|
||||
|
|
|
@ -13,7 +13,7 @@ import 'package:aves/model/metadata/trash.dart';
|
|||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/model/source/album.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/location.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/source/analysis_controller.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/utils/collection_utils.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/source/analysis_controller.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/utils/android_file_utils.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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 {
|
||||
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/source/analysis_controller.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/utils/collection_utils.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
|
|
|
@ -4,7 +4,7 @@ import 'dart:ui';
|
|||
import 'package:aves/l10n/l10n.dart';
|
||||
import 'package:aves/model/settings/settings.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/source_state.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 layout = Icons.grid_view_outlined;
|
||||
static const IconData sort = Icons.sort_outlined;
|
||||
static const IconData sortOrder = Icons.swap_vert_outlined;
|
||||
|
||||
// actions
|
||||
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/source/collection_lens.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/icons.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;
|
||||
|
||||
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
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
@ -506,33 +526,22 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
|||
}
|
||||
|
||||
Future<void> _configureView() async {
|
||||
final initialValue = Tuple3(
|
||||
final initialValue = Tuple4(
|
||||
settings.collectionSortFactor,
|
||||
settings.collectionSectionFactor,
|
||||
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,
|
||||
builder: (context) {
|
||||
final l10n = context.l10n;
|
||||
return TileViewDialog<EntrySortFactor, EntryGroupFactor, TileLayout>(
|
||||
initialValue: initialValue,
|
||||
sortOptions: {
|
||||
EntrySortFactor.date: l10n.sortByDate,
|
||||
EntrySortFactor.size: l10n.sortBySize,
|
||||
EntrySortFactor.name: l10n.sortByAlbumFileName,
|
||||
EntrySortFactor.rating: l10n.sortByRating,
|
||||
},
|
||||
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,
|
||||
},
|
||||
sortOptions: Map.fromEntries(_sortOptions.map((v) => MapEntry(v, v.getName(context)))),
|
||||
groupOptions: Map.fromEntries(_groupOptions.map((v) => MapEntry(v, v.getName(context)))),
|
||||
layoutOptions: Map.fromEntries(_layoutOptions.map((v) => MapEntry(v, v.getName(context)))),
|
||||
sortOrder: (factor, reverse) => factor.getOrderName(context, reverse),
|
||||
canGroup: (s, g, l) => s == EntrySortFactor.date,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -542,6 +551,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
|||
settings.collectionSortFactor = value.item1!;
|
||||
settings.collectionSectionFactor = value.item2!;
|
||||
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/settings/settings.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/ref/mime_types.dart';
|
||||
import 'package:aves/theme/durations.dart';
|
||||
|
@ -542,7 +542,7 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> with Widge
|
|||
final oldest = lastKey.date;
|
||||
if (newest != null && oldest != null) {
|
||||
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;
|
||||
sectionLayouts.forEach((section) {
|
||||
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/source/collection_lens.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/widgets/common/extensions/build_context.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/source/collection_lens.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/widgets/collection/grid/headers/album.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/selection.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/widgets/collection/grid/list_details.dart';
|
||||
import 'package:aves/widgets/collection/grid/list_details_theme.dart';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import 'dart:ui';
|
||||
|
||||
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/source_state.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 '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/widgets/common/grid/section_layout.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import 'dart:ui' as ui;
|
||||
|
||||
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/widgets/common/behaviour/eager_scale_gesture_recognizer.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
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/theme/durations.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/icons.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/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||
|
@ -80,13 +81,9 @@ class _EditEntryDateDialogState extends State<EditEntryDateDialog> {
|
|||
scrollableContent: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16, top: 8, right: 16),
|
||||
child: DropdownButton<DateEditAction>(
|
||||
items: DateEditAction.values
|
||||
.map((v) => DropdownMenuItem<DateEditAction>(
|
||||
value: v,
|
||||
child: Text(_actionText(context, v)),
|
||||
))
|
||||
.toList(),
|
||||
child: TextDropdownButton<DateEditAction>(
|
||||
values: DateEditAction.values,
|
||||
valueText: (v) => _actionText(context, v),
|
||||
value: _action,
|
||||
onChanged: (v) => setState(() => _action = v!),
|
||||
isExpanded: true,
|
||||
|
@ -159,23 +156,9 @@ class _EditEntryDateDialogState extends State<EditEntryDateDialog> {
|
|||
Widget _buildCopyFieldContent(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 16, top: 0, right: 16),
|
||||
child: DropdownButton<DateFieldSource>(
|
||||
items: DateFieldSource.values
|
||||
.map((v) => DropdownMenuItem<DateFieldSource>(
|
||||
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(),
|
||||
child: TextDropdownButton<DateFieldSource>(
|
||||
values: DateFieldSource.values,
|
||||
valueText: (v) => _setSourceText(context, v),
|
||||
value: _copyFieldSource,
|
||||
onChanged: (v) => setState(() => _copyFieldSource = v!),
|
||||
isExpanded: true,
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'package:aves/model/entry.dart';
|
|||
import 'package:aves/ref/mime_types.dart';
|
||||
import 'package:aves/services/media/media_edit_service.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:flutter/material.dart';
|
||||
|
||||
|
@ -63,13 +64,9 @@ class _ExportEntryDialogState extends State<ExportEntryDialog> {
|
|||
children: [
|
||||
Text(l10n.exportEntryDialogFormat),
|
||||
const SizedBox(width: AvesDialog.controlCaptionPadding),
|
||||
DropdownButton<String>(
|
||||
items: imageExportFormats.map((mimeType) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: mimeType,
|
||||
child: Text(MimeUtils.displayType(mimeType)),
|
||||
);
|
||||
}).toList(),
|
||||
TextDropdownButton<String>(
|
||||
values: imageExportFormats,
|
||||
valueText: MimeUtils.displayType,
|
||||
value: _mimeType,
|
||||
onChanged: (selected) {
|
||||
if (selected != null) {
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:aves/model/source/enums.dart';
|
||||
import 'package:aves/theme/durations.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/identity/highlight_title.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
import 'aves_dialog.dart';
|
||||
|
||||
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<G, String> groupOptions;
|
||||
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({
|
||||
super.key,
|
||||
|
@ -21,6 +24,8 @@ class TileViewDialog<S, G, L> extends StatefulWidget {
|
|||
this.sortOptions = const {},
|
||||
this.groupOptions = const {},
|
||||
this.layoutOptions = const {},
|
||||
required this.sortOrder,
|
||||
this.canGroup,
|
||||
});
|
||||
|
||||
@override
|
||||
|
@ -31,8 +36,7 @@ class _TileViewDialogState<S, G, L> extends State<TileViewDialog<S, G, L>> with
|
|||
late S? _selectedSort;
|
||||
late G? _selectedGroup;
|
||||
late L? _selectedLayout;
|
||||
late final TabController _tabController;
|
||||
late final String _optionLines;
|
||||
late bool _reverseSort;
|
||||
|
||||
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;
|
||||
|
||||
static const int groupTabIndex = 1;
|
||||
|
||||
double tabBarHeight(BuildContext context) => 64 * max(1, MediaQuery.textScaleFactorOf(context));
|
||||
|
||||
static const double tabIndicatorWeight = 2;
|
||||
bool get canGroup => (widget.canGroup ?? (s, g, l) => true).call(_selectedSort, _selectedGroup, _selectedLayout);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -53,259 +53,140 @@ class _TileViewDialogState<S, G, L> extends State<TileViewDialog<S, G, L>> with
|
|||
_selectedSort = initialValue.item1;
|
||||
_selectedGroup = initialValue.item2;
|
||||
_selectedLayout = initialValue.item3;
|
||||
|
||||
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();
|
||||
_reverseSort = initialValue.item4;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
final tabs = <Tuple2<Tab, Widget>>[
|
||||
if (sortOptions.isNotEmpty)
|
||||
Tuple2(
|
||||
_buildTab(
|
||||
context,
|
||||
const Key('tab-sort'),
|
||||
AIcons.sort,
|
||||
l10n.viewDialogTabSort,
|
||||
),
|
||||
Column(
|
||||
children: sortOptions.entries
|
||||
.map((kv) => _buildRadioListTile<S>(
|
||||
kv.key,
|
||||
kv.value,
|
||||
() => _selectedSort,
|
||||
(v) => _selectedSort = v,
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
if (groupOptions.isNotEmpty)
|
||||
Tuple2(
|
||||
_buildTab(
|
||||
context,
|
||||
const Key('tab-group'),
|
||||
AIcons.group,
|
||||
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(
|
||||
_buildTab(
|
||||
context,
|
||||
const Key('tab-layout'),
|
||||
AIcons.layout,
|
||||
l10n.viewDialogTabLayout,
|
||||
),
|
||||
Column(
|
||||
children: layoutOptions.entries
|
||||
.map((kv) => _buildRadioListTile<L>(
|
||||
kv.key,
|
||||
kv.value,
|
||||
() => _selectedLayout,
|
||||
(v) => _selectedLayout = v,
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
final contentWidget = DecoratedBox(
|
||||
decoration: AvesDialog.contentDecoration(context),
|
||||
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,
|
||||
return AvesDialog(
|
||||
scrollableContent: [
|
||||
_buildSection(
|
||||
icon: AIcons.sort,
|
||||
title: l10n.viewDialogTabSort,
|
||||
trailing: IconButton(
|
||||
icon: const Icon(AIcons.sortOrder),
|
||||
onPressed: () => setState(() => _reverseSort = !_reverseSort),
|
||||
tooltip: l10n.viewDialogReverseSortOrder,
|
||||
),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: TabBar(
|
||||
indicatorWeight: tabIndicatorWeight,
|
||||
tabs: tabs.map((t) => t.item1).toList(),
|
||||
controller: _tabController,
|
||||
options: sortOptions,
|
||||
value: _selectedSort,
|
||||
onChanged: (v) {
|
||||
_selectedSort = v;
|
||||
_reverseSort = false;
|
||||
},
|
||||
bottom: _selectedSort != null
|
||||
? Text(
|
||||
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,
|
||||
),
|
||||
),
|
||||
ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: maxHeight,
|
||||
child: _buildSection(
|
||||
show: canGroup,
|
||||
icon: AIcons.group,
|
||||
title: l10n.viewDialogTabGroup,
|
||||
options: groupOptions,
|
||||
value: _selectedGroup,
|
||||
onChanged: (v) => _selectedGroup = v,
|
||||
),
|
||||
child: TabBarView(
|
||||
controller: _tabController,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
children: tabs
|
||||
.map((t) => SingleChildScrollView(
|
||||
child: t.item2,
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
_buildSection(
|
||||
icon: AIcons.layout,
|
||||
title: l10n.viewDialogTabLayout,
|
||||
options: layoutOptions,
|
||||
value: _selectedLayout,
|
||||
onChanged: (v) => _selectedLayout = v,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
final actionsWidget = Padding(
|
||||
padding: AvesDialog.actionsPadding,
|
||||
child: OverflowBar(
|
||||
alignment: MainAxisAlignment.end,
|
||||
spacing: AvesDialog.buttonPadding.horizontal / 2,
|
||||
overflowAlignment: OverflowBarAlignment.end,
|
||||
children: [
|
||||
actions: [
|
||||
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)),
|
||||
onPressed: () => Navigator.pop(context, Tuple4(_selectedSort, _selectedGroup, _selectedLayout, _reverseSort)),
|
||||
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) {
|
||||
return RadioListTile<T>(
|
||||
// key is expected by test driver
|
||||
key: Key(value.toString()),
|
||||
value: value,
|
||||
groupValue: get(),
|
||||
onChanged: (v) => setState(() => set(v as T)),
|
||||
title: Text(
|
||||
title,
|
||||
softWrap: false,
|
||||
overflow: TextOverflow.fade,
|
||||
maxLines: 1,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// tabs
|
||||
|
||||
Tab _buildTab(
|
||||
BuildContext context,
|
||||
Key key,
|
||||
IconData icon,
|
||||
String text, {
|
||||
Color? color,
|
||||
Widget _buildSection<T>({
|
||||
bool show = true,
|
||||
required IconData icon,
|
||||
required String title,
|
||||
Widget? trailing,
|
||||
required Map<T, String> options,
|
||||
required T value,
|
||||
required ValueChanged<T?> onChanged,
|
||||
Widget? bottom,
|
||||
}) {
|
||||
// cannot use `IconTheme` over `TabBar` to change size,
|
||||
// because `TabBar` does so internally
|
||||
final textScaleFactor = MediaQuery.textScaleFactorOf(context);
|
||||
final iconSize = IconTheme.of(context).size! * textScaleFactor;
|
||||
return Tab(
|
||||
key: key,
|
||||
height: tabBarHeight(context),
|
||||
if (options.isEmpty || !show) return const SizedBox();
|
||||
|
||||
final iconSize = IconTheme.of(context).size! * MediaQuery.textScaleFactorOf(context);
|
||||
return TooltipTheme(
|
||||
data: TooltipTheme.of(context).copyWith(
|
||||
preferBelow: false,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
size: iconSize,
|
||||
color: color,
|
||||
const SizedBox(height: 8),
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
minHeight: kMinInteractiveDimension,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
text,
|
||||
style: TextStyle(color: color),
|
||||
softWrap: false,
|
||||
overflow: TextOverflow.fade,
|
||||
child: Row(
|
||||
children: [
|
||||
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/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/viewer/video/controller.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
|
@ -123,25 +124,6 @@ class _VideoStreamSelectionDialogState extends State<VideoStreamSelectionDialog>
|
|||
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({
|
||||
required IconData icon,
|
||||
required String title,
|
||||
|
@ -162,9 +144,9 @@ class _VideoStreamSelectionDialogState extends State<VideoStreamSelectionDialog>
|
|||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: DropdownButton<StreamSummary>(
|
||||
items: streams.map(_buildMenuItem).toList(),
|
||||
selectedItemBuilder: (context) => streams.map(_buildSelectedItem).toList(),
|
||||
child: TextDropdownButton<StreamSummary>(
|
||||
values: streams.whereNotNull().toList(),
|
||||
valueText: _streamName,
|
||||
value: current,
|
||||
onChanged: streams.length > 1 ? (newValue) => setState(() => setter(newValue)) : null,
|
||||
isExpanded: true,
|
||||
|
|
|
@ -7,7 +7,7 @@ import 'package:aves/model/selection.dart';
|
|||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/model/source/album.dart';
|
||||
import 'package:aves/model/source/collection_source.dart';
|
||||
import 'package:aves/model/source/enums.dart';
|
||||
import 'package:aves/model/source/enums/enums.dart';
|
||||
import 'package:aves/theme/durations.dart';
|
||||
import 'package:aves/theme/icons.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/source/album.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/utils/android_file_utils.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
|
@ -25,12 +25,12 @@ class AlbumListPage extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final source = context.read<CollectionSource>();
|
||||
return Selector<Settings, Tuple3<AlbumChipGroupFactor, ChipSortFactor, Set<CollectionFilter>>>(
|
||||
selector: (context, s) => Tuple3(s.albumGroupFactor, s.albumSortFactor, s.pinnedFilters),
|
||||
return Selector<Settings, Tuple4<AlbumChipGroupFactor, ChipSortFactor, bool, Set<CollectionFilter>>>(
|
||||
selector: (context, s) => Tuple4(s.albumGroupFactor, s.albumSortFactor, s.albumSortReverse, s.pinnedFilters),
|
||||
shouldRebuild: (t1, t2) {
|
||||
// `Selector` by default uses `DeepCollectionEquality`, which does not go deep in collections within `TupleN`
|
||||
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) {
|
||||
return ValueListenableBuilder<bool>(
|
||||
|
@ -75,7 +75,7 @@ class AlbumListPage extends StatelessWidget {
|
|||
static List<FilterGridItem<AlbumFilter>> getAlbumGridItems(BuildContext context, CollectionSource source) {
|
||||
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) {
|
||||
|
|
|
@ -9,7 +9,8 @@ import 'package:aves/model/highlight.dart';
|
|||
import 'package:aves/model/selection.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/model/source/collection_source.dart';
|
||||
import 'package:aves/model/source/enums.dart';
|
||||
import 'package:aves/model/source/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/services.dart';
|
||||
import 'package:aves/services/media/enums.dart';
|
||||
|
@ -44,12 +45,24 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate<AlbumFilter> with
|
|||
@override
|
||||
set sortFactor(ChipSortFactor factor) => settings.albumSortFactor = factor;
|
||||
|
||||
@override
|
||||
bool get sortReverse => settings.albumSortReverse;
|
||||
|
||||
@override
|
||||
set sortReverse(bool value) => settings.albumSortReverse = value;
|
||||
|
||||
@override
|
||||
TileLayout get tileLayout => settings.getTileLayout(AlbumListPage.routeName);
|
||||
|
||||
@override
|
||||
set tileLayout(TileLayout tileLayout) => settings.setTileLayout(AlbumListPage.routeName, tileLayout);
|
||||
|
||||
static const _groupOptions = [
|
||||
AlbumChipGroupFactor.importance,
|
||||
AlbumChipGroupFactor.volume,
|
||||
AlbumChipGroupFactor.none,
|
||||
];
|
||||
|
||||
@override
|
||||
bool isVisible(
|
||||
ChipSetAction action, {
|
||||
|
@ -125,32 +138,21 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate<AlbumFilter> with
|
|||
|
||||
@override
|
||||
Future<void> configureView(BuildContext context) async {
|
||||
final initialValue = Tuple3(
|
||||
final initialValue = Tuple4(
|
||||
sortFactor,
|
||||
settings.albumGroupFactor,
|
||||
tileLayout,
|
||||
sortReverse,
|
||||
);
|
||||
final value = await showDialog<Tuple3<ChipSortFactor?, AlbumChipGroupFactor?, TileLayout?>>(
|
||||
final value = await showDialog<Tuple4<ChipSortFactor?, AlbumChipGroupFactor?, TileLayout?, bool>>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
final l10n = context.l10n;
|
||||
return TileViewDialog<ChipSortFactor, AlbumChipGroupFactor, TileLayout>(
|
||||
initialValue: initialValue,
|
||||
sortOptions: {
|
||||
ChipSortFactor.date: l10n.sortByDate,
|
||||
ChipSortFactor.name: l10n.sortByName,
|
||||
ChipSortFactor.count: l10n.sortByItemCount,
|
||||
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,
|
||||
},
|
||||
sortOptions: Map.fromEntries(ChipSetActionDelegate.sortOptions.map((v) => MapEntry(v, v.getName(context)))),
|
||||
groupOptions: Map.fromEntries(_groupOptions.map((v) => MapEntry(v, v.getName(context)))),
|
||||
layoutOptions: Map.fromEntries(ChipSetActionDelegate.layoutOptions.map((v) => MapEntry(v, v.getName(context)))),
|
||||
sortOrder: (factor, reverse) => factor.getOrderName(context, reverse),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -160,6 +162,7 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate<AlbumFilter> with
|
|||
sortFactor = value.item1!;
|
||||
settings.albumGroupFactor = value.item2!;
|
||||
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/source/collection_lens.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/durations.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);
|
||||
|
||||
bool get sortReverse;
|
||||
|
||||
set sortReverse(bool value);
|
||||
|
||||
TileLayout get 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(
|
||||
ChipSetAction action, {
|
||||
required AppMode appMode,
|
||||
|
@ -194,27 +211,20 @@ abstract class ChipSetActionDelegate<T extends CollectionFilter> with FeedbackMi
|
|||
}
|
||||
|
||||
Future<void> configureView(BuildContext context) async {
|
||||
final initialValue = Tuple3(
|
||||
final initialValue = Tuple4(
|
||||
sortFactor,
|
||||
null,
|
||||
tileLayout,
|
||||
sortReverse,
|
||||
);
|
||||
final value = await showDialog<Tuple3<ChipSortFactor?, void, TileLayout?>>(
|
||||
final value = await showDialog<Tuple4<ChipSortFactor?, void, TileLayout?, bool>>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
final l10n = context.l10n;
|
||||
return TileViewDialog<ChipSortFactor, void, TileLayout>(
|
||||
initialValue: initialValue,
|
||||
sortOptions: {
|
||||
ChipSortFactor.date: l10n.sortByDate,
|
||||
ChipSortFactor.name: l10n.sortByName,
|
||||
ChipSortFactor.count: l10n.sortByItemCount,
|
||||
ChipSortFactor.size: l10n.sortBySize,
|
||||
},
|
||||
layoutOptions: {
|
||||
TileLayout.grid: l10n.tileLayoutGrid,
|
||||
TileLayout.list: l10n.tileLayoutList,
|
||||
},
|
||||
sortOptions: Map.fromEntries(sortOptions.map((v) => MapEntry(v, v.getName(context)))),
|
||||
layoutOptions: Map.fromEntries(layoutOptions.map((v) => MapEntry(v, v.getName(context)))),
|
||||
sortOrder: (factor, reverse) => factor.getOrderName(context, reverse),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -223,6 +233,7 @@ abstract class ChipSetActionDelegate<T extends CollectionFilter> with FeedbackMi
|
|||
if (value != null && initialValue != value) {
|
||||
sortFactor = value.item1!;
|
||||
tileLayout = value.item3!;
|
||||
sortReverse = value.item4;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/model/filters/location.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/countries_page.dart';
|
||||
|
||||
|
@ -19,6 +19,12 @@ class CountryChipSetActionDelegate extends ChipSetActionDelegate<LocationFilter>
|
|||
@override
|
||||
set sortFactor(ChipSortFactor factor) => settings.countrySortFactor = factor;
|
||||
|
||||
@override
|
||||
bool get sortReverse => settings.countrySortReverse;
|
||||
|
||||
@override
|
||||
set sortReverse(bool value) => settings.countrySortReverse = value;
|
||||
|
||||
@override
|
||||
TileLayout get tileLayout => settings.getTileLayout(CountryListPage.routeName);
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/model/filters/tag.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/tags_page.dart';
|
||||
|
||||
|
@ -19,6 +19,12 @@ class TagChipSetActionDelegate extends ChipSetActionDelegate<TagFilter> {
|
|||
@override
|
||||
set sortFactor(ChipSortFactor factor) => settings.tagSortFactor = factor;
|
||||
|
||||
@override
|
||||
bool get sortReverse => settings.tagSortReverse;
|
||||
|
||||
@override
|
||||
set sortReverse(bool value) => settings.tagSortReverse = value;
|
||||
|
||||
@override
|
||||
TileLayout get tileLayout => settings.getTileLayout(TagListPage.routeName);
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:aves/model/filters/filters.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/widgets/common/extensions/build_context.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/selection.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/widgets/common/basic/draggable_scrollbar.dart';
|
||||
import 'package:aves/widgets/common/basic/insets.dart';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:aves/model/filters/filters.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/widgets/common/identity/aves_filter_chip.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);
|
||||
}
|
||||
|
||||
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) {
|
||||
return filters
|
||||
.map((filter) => FilterGridItem(
|
||||
|
@ -87,6 +92,9 @@ class FilterNavigationPage<T extends CollectionFilter, CSAD extends ChipSetActio
|
|||
allMapEntries = toGridItem(source, filters);
|
||||
break;
|
||||
}
|
||||
if (reverse) {
|
||||
allMapEntries = allMapEntries.reversed.toList();
|
||||
}
|
||||
return allMapEntries;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import 'package:aves/model/filters/filters.dart';
|
|||
import 'package:aves/model/selection.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/model/source/collection_source.dart';
|
||||
import 'package:aves/model/source/enums.dart';
|
||||
import 'package:aves/model/source/enums/enums.dart';
|
||||
import 'package:aves/widgets/collection/collection_page.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.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/settings/settings.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/theme/icons.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
|
@ -23,12 +23,12 @@ class CountryListPage extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final source = context.read<CollectionSource>();
|
||||
return Selector<Settings, Tuple2<ChipSortFactor, Set<CollectionFilter>>>(
|
||||
selector: (context, s) => Tuple2(s.countrySortFactor, s.pinnedFilters),
|
||||
return Selector<Settings, Tuple3<ChipSortFactor, bool, Set<CollectionFilter>>>(
|
||||
selector: (context, s) => Tuple3(s.countrySortFactor, s.countrySortReverse, s.pinnedFilters),
|
||||
shouldRebuild: (t1, t2) {
|
||||
// `Selector` by default uses `DeepCollectionEquality`, which does not go deep in collections within `TupleN`
|
||||
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) {
|
||||
return StreamBuilder(
|
||||
|
@ -60,7 +60,7 @@ class CountryListPage extends StatelessWidget {
|
|||
List<FilterGridItem<LocationFilter>> _getGridItems(CollectionSource source) {
|
||||
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) {
|
||||
|
|
|
@ -2,7 +2,7 @@ import 'package:aves/model/filters/filters.dart';
|
|||
import 'package:aves/model/filters/tag.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/model/source/collection_source.dart';
|
||||
import 'package:aves/model/source/enums.dart';
|
||||
import 'package:aves/model/source/enums/enums.dart';
|
||||
import 'package:aves/model/source/tag.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
|
@ -23,12 +23,12 @@ class TagListPage extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final source = context.read<CollectionSource>();
|
||||
return Selector<Settings, Tuple2<ChipSortFactor, Set<CollectionFilter>>>(
|
||||
selector: (context, s) => Tuple2(s.tagSortFactor, s.pinnedFilters),
|
||||
return Selector<Settings, Tuple3<ChipSortFactor, bool, Set<CollectionFilter>>>(
|
||||
selector: (context, s) => Tuple3(s.tagSortFactor, s.tagSortReverse, s.pinnedFilters),
|
||||
shouldRebuild: (t1, t2) {
|
||||
// `Selector` by default uses `DeepCollectionEquality`, which does not go deep in collections within `TupleN`
|
||||
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) {
|
||||
return StreamBuilder(
|
||||
|
@ -60,7 +60,7 @@ class TagListPage extends StatelessWidget {
|
|||
List<FilterGridItem<TagFilter>> _getGridItems(CollectionSource source) {
|
||||
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) {
|
||||
|
|
|
@ -9,7 +9,7 @@ import 'package:aves/model/settings/enums/home_page.dart';
|
|||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/model/source/collection_lens.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/common/services.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/enums/enums.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:flutter_driver/driver_extension.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// ignore_for_file: avoid_print
|
||||
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:path/path.dart' as p;
|
||||
import 'package:test/test.dart';
|
||||
|
|
|
@ -3,6 +3,15 @@
|
|||
"entryInfoActionEditTitleDescription",
|
||||
"filterNoDateLabel",
|
||||
"filterNoTitleLabel",
|
||||
"viewDialogReverseSortOrder",
|
||||
"sortOrderNewestFirst",
|
||||
"sortOrderOldestFirst",
|
||||
"sortOrderAtoZ",
|
||||
"sortOrderZtoA",
|
||||
"sortOrderHighestFirst",
|
||||
"sortOrderLowestFirst",
|
||||
"sortOrderLargestFirst",
|
||||
"sortOrderSmallestFirst",
|
||||
"searchMetadataSectionTitle"
|
||||
],
|
||||
|
||||
|
@ -10,6 +19,15 @@
|
|||
"entryInfoActionEditTitleDescription",
|
||||
"filterNoDateLabel",
|
||||
"filterNoTitleLabel",
|
||||
"viewDialogReverseSortOrder",
|
||||
"sortOrderNewestFirst",
|
||||
"sortOrderOldestFirst",
|
||||
"sortOrderAtoZ",
|
||||
"sortOrderZtoA",
|
||||
"sortOrderHighestFirst",
|
||||
"sortOrderLowestFirst",
|
||||
"sortOrderLargestFirst",
|
||||
"sortOrderSmallestFirst",
|
||||
"searchMetadataSectionTitle"
|
||||
],
|
||||
|
||||
|
@ -18,16 +36,46 @@
|
|||
"filterNoDateLabel",
|
||||
"filterNoTitleLabel",
|
||||
"filterRecentlyAddedLabel",
|
||||
"viewDialogReverseSortOrder",
|
||||
"sortOrderNewestFirst",
|
||||
"sortOrderOldestFirst",
|
||||
"sortOrderAtoZ",
|
||||
"sortOrderZtoA",
|
||||
"sortOrderHighestFirst",
|
||||
"sortOrderLowestFirst",
|
||||
"sortOrderLargestFirst",
|
||||
"sortOrderSmallestFirst",
|
||||
"searchMetadataSectionTitle",
|
||||
"settingsConfirmationAfterMoveToBinItems",
|
||||
"viewerInfoLabelDescription"
|
||||
],
|
||||
|
||||
"fr": [
|
||||
"viewDialogReverseSortOrder",
|
||||
"sortOrderNewestFirst",
|
||||
"sortOrderOldestFirst",
|
||||
"sortOrderAtoZ",
|
||||
"sortOrderZtoA",
|
||||
"sortOrderHighestFirst",
|
||||
"sortOrderLowestFirst",
|
||||
"sortOrderLargestFirst",
|
||||
"sortOrderSmallestFirst"
|
||||
],
|
||||
|
||||
"id": [
|
||||
"entryInfoActionEditTitleDescription",
|
||||
"filterNoDateLabel",
|
||||
"filterNoTitleLabel",
|
||||
"filterRecentlyAddedLabel",
|
||||
"viewDialogReverseSortOrder",
|
||||
"sortOrderNewestFirst",
|
||||
"sortOrderOldestFirst",
|
||||
"sortOrderAtoZ",
|
||||
"sortOrderZtoA",
|
||||
"sortOrderHighestFirst",
|
||||
"sortOrderLowestFirst",
|
||||
"sortOrderLargestFirst",
|
||||
"sortOrderSmallestFirst",
|
||||
"searchMetadataSectionTitle",
|
||||
"settingsConfirmationAfterMoveToBinItems",
|
||||
"settingsViewerGestureSideTapNext",
|
||||
|
@ -38,6 +86,15 @@
|
|||
"entryInfoActionEditTitleDescription",
|
||||
"filterNoDateLabel",
|
||||
"filterNoTitleLabel",
|
||||
"viewDialogReverseSortOrder",
|
||||
"sortOrderNewestFirst",
|
||||
"sortOrderOldestFirst",
|
||||
"sortOrderAtoZ",
|
||||
"sortOrderZtoA",
|
||||
"sortOrderHighestFirst",
|
||||
"sortOrderLowestFirst",
|
||||
"sortOrderLargestFirst",
|
||||
"sortOrderSmallestFirst",
|
||||
"searchMetadataSectionTitle"
|
||||
],
|
||||
|
||||
|
@ -46,16 +103,46 @@
|
|||
"filterNoDateLabel",
|
||||
"filterNoTitleLabel",
|
||||
"filterRecentlyAddedLabel",
|
||||
"viewDialogReverseSortOrder",
|
||||
"sortOrderNewestFirst",
|
||||
"sortOrderOldestFirst",
|
||||
"sortOrderAtoZ",
|
||||
"sortOrderZtoA",
|
||||
"sortOrderHighestFirst",
|
||||
"sortOrderLowestFirst",
|
||||
"sortOrderLargestFirst",
|
||||
"sortOrderSmallestFirst",
|
||||
"searchMetadataSectionTitle",
|
||||
"settingsConfirmationAfterMoveToBinItems",
|
||||
"settingsViewerGestureSideTapNext",
|
||||
"viewerInfoLabelDescription"
|
||||
],
|
||||
|
||||
"ko": [
|
||||
"viewDialogReverseSortOrder",
|
||||
"sortOrderNewestFirst",
|
||||
"sortOrderOldestFirst",
|
||||
"sortOrderAtoZ",
|
||||
"sortOrderZtoA",
|
||||
"sortOrderHighestFirst",
|
||||
"sortOrderLowestFirst",
|
||||
"sortOrderLargestFirst",
|
||||
"sortOrderSmallestFirst"
|
||||
],
|
||||
|
||||
"nl": [
|
||||
"entryInfoActionEditTitleDescription",
|
||||
"filterNoDateLabel",
|
||||
"filterNoTitleLabel",
|
||||
"viewDialogReverseSortOrder",
|
||||
"sortOrderNewestFirst",
|
||||
"sortOrderOldestFirst",
|
||||
"sortOrderAtoZ",
|
||||
"sortOrderZtoA",
|
||||
"sortOrderHighestFirst",
|
||||
"sortOrderLowestFirst",
|
||||
"sortOrderLargestFirst",
|
||||
"sortOrderSmallestFirst",
|
||||
"searchMetadataSectionTitle"
|
||||
],
|
||||
|
||||
|
@ -63,6 +150,15 @@
|
|||
"entryInfoActionEditTitleDescription",
|
||||
"filterNoDateLabel",
|
||||
"filterNoTitleLabel",
|
||||
"viewDialogReverseSortOrder",
|
||||
"sortOrderNewestFirst",
|
||||
"sortOrderOldestFirst",
|
||||
"sortOrderAtoZ",
|
||||
"sortOrderZtoA",
|
||||
"sortOrderHighestFirst",
|
||||
"sortOrderLowestFirst",
|
||||
"sortOrderLargestFirst",
|
||||
"sortOrderSmallestFirst",
|
||||
"searchMetadataSectionTitle"
|
||||
],
|
||||
|
||||
|
@ -72,6 +168,15 @@
|
|||
"filterNoTitleLabel",
|
||||
"filterOnThisDayLabel",
|
||||
"filterRecentlyAddedLabel",
|
||||
"viewDialogReverseSortOrder",
|
||||
"sortOrderNewestFirst",
|
||||
"sortOrderOldestFirst",
|
||||
"sortOrderAtoZ",
|
||||
"sortOrderZtoA",
|
||||
"sortOrderHighestFirst",
|
||||
"sortOrderLowestFirst",
|
||||
"sortOrderLargestFirst",
|
||||
"sortOrderSmallestFirst",
|
||||
"searchMetadataSectionTitle",
|
||||
"settingsConfirmationAfterMoveToBinItems",
|
||||
"settingsViewerGestureSideTapNext",
|
||||
|
@ -100,6 +205,15 @@
|
|||
"wallpaperTargetLock",
|
||||
"wallpaperTargetHomeLock",
|
||||
"menuActionSlideshow",
|
||||
"viewDialogReverseSortOrder",
|
||||
"sortOrderNewestFirst",
|
||||
"sortOrderOldestFirst",
|
||||
"sortOrderAtoZ",
|
||||
"sortOrderZtoA",
|
||||
"sortOrderHighestFirst",
|
||||
"sortOrderLowestFirst",
|
||||
"sortOrderLargestFirst",
|
||||
"sortOrderSmallestFirst",
|
||||
"searchMetadataSectionTitle",
|
||||
"settingsConfirmationAfterMoveToBinItems",
|
||||
"settingsViewerGestureSideTapNext",
|
||||
|
@ -124,6 +238,15 @@
|
|||
"entryInfoActionEditTitleDescription",
|
||||
"filterNoDateLabel",
|
||||
"filterNoTitleLabel",
|
||||
"viewDialogReverseSortOrder",
|
||||
"sortOrderNewestFirst",
|
||||
"sortOrderOldestFirst",
|
||||
"sortOrderAtoZ",
|
||||
"sortOrderZtoA",
|
||||
"sortOrderHighestFirst",
|
||||
"sortOrderLowestFirst",
|
||||
"sortOrderLargestFirst",
|
||||
"sortOrderSmallestFirst",
|
||||
"searchMetadataSectionTitle"
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue