albums: pin to top
This commit is contained in:
parent
15c50d5cef
commit
055cad333f
5 changed files with 137 additions and 39 deletions
|
@ -1,3 +1,4 @@
|
|||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/model/settings/coordinate_format.dart';
|
||||
import 'package:aves/model/settings/home_page.dart';
|
||||
import 'package:aves/model/settings/screen_on.dart';
|
||||
|
@ -37,6 +38,7 @@ class Settings extends ChangeNotifier {
|
|||
static const albumSortFactorKey = 'album_sort_factor';
|
||||
static const countrySortFactorKey = 'country_sort_factor';
|
||||
static const tagSortFactorKey = 'tag_sort_factor';
|
||||
static const pinnedFiltersKey = 'pinned_filters';
|
||||
|
||||
// info
|
||||
static const infoMapStyleKey = 'info_map_style';
|
||||
|
@ -120,6 +122,10 @@ class Settings extends ChangeNotifier {
|
|||
|
||||
set tagSortFactor(ChipSortFactor newValue) => setAndNotify(tagSortFactorKey, newValue.toString());
|
||||
|
||||
Set<CollectionFilter> get pinnedFilters => (_prefs.getStringList(pinnedFiltersKey) ?? []).map(CollectionFilter.fromJson).toSet();
|
||||
|
||||
set pinnedFilters(Set<CollectionFilter> newValue) => setAndNotify(pinnedFiltersKey, newValue.map((filter) => filter.toJson()).toList());
|
||||
|
||||
// info
|
||||
|
||||
EntryMapStyle get infoMapStyle => getEnumOrDefault(infoMapStyleKey, EntryMapStyle.stamenWatercolor, EntryMapStyle.values);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:aves/model/filters/album.dart';
|
||||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/model/image_entry.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/model/source/album.dart';
|
||||
|
@ -7,10 +8,14 @@ import 'package:aves/model/source/enums.dart';
|
|||
import 'package:aves/utils/android_file_utils.dart';
|
||||
import 'package:aves/widgets/collection/empty.dart';
|
||||
import 'package:aves/widgets/common/icons.dart';
|
||||
import 'package:aves/widgets/common/menu_row.dart';
|
||||
import 'package:aves/widgets/filter_grids/chip_action_delegate.dart';
|
||||
import 'package:aves/widgets/filter_grids/chip_actions.dart';
|
||||
import 'package:aves/widgets/filter_grids/filter_grid_page.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
class AlbumListPage extends StatelessWidget {
|
||||
static const routeName = '/albums';
|
||||
|
@ -23,9 +28,9 @@ class AlbumListPage extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector<Settings, ChipSortFactor>(
|
||||
selector: (context, s) => s.albumSortFactor,
|
||||
builder: (context, sortFactor, child) {
|
||||
return Selector<Settings, Tuple2<ChipSortFactor, Set<CollectionFilter>>>(
|
||||
selector: (context, s) => Tuple2(s.albumSortFactor, s.pinnedFilters),
|
||||
builder: (context, s, child) {
|
||||
return AnimatedBuilder(
|
||||
animation: androidFileUtils.appNameChangeNotifier,
|
||||
builder: (context, child) => StreamBuilder(
|
||||
|
@ -35,11 +40,12 @@ class AlbumListPage extends StatelessWidget {
|
|||
title: 'Albums',
|
||||
actionDelegate: actionDelegate,
|
||||
filterEntries: getAlbumEntries(source),
|
||||
filterBuilder: (s) => AlbumFilter(s, source.getUniqueAlbumName(s)),
|
||||
filterBuilder: (album) => AlbumFilter(album, source.getUniqueAlbumName(album)),
|
||||
emptyBuilder: () => EmptyContent(
|
||||
icon: AIcons.album,
|
||||
text: 'No albums',
|
||||
),
|
||||
onLongPress: (filter, tapPosition) => _showMenu(context, filter, tapPosition),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -47,38 +53,75 @@ class AlbumListPage extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
Future<void> _showMenu(BuildContext context, CollectionFilter filter, Offset tapPosition) async {
|
||||
final RenderBox overlay = Overlay.of(context).context.findRenderObject();
|
||||
final touchArea = Size(40, 40);
|
||||
final selectedAction = await showMenu<AlbumAction>(
|
||||
context: context,
|
||||
position: RelativeRect.fromRect(tapPosition & touchArea, Offset.zero & overlay.size),
|
||||
items: [settings.pinnedFilters.contains(filter) ? AlbumAction.unpin : AlbumAction.pin, AlbumAction.rename]
|
||||
.map((action) => PopupMenuItem(
|
||||
value: action,
|
||||
child: MenuRow(text: action.getText(), icon: action.getIcon()),
|
||||
))
|
||||
.toList(),
|
||||
);
|
||||
if (selectedAction != null) {
|
||||
switch (selectedAction) {
|
||||
case AlbumAction.pin:
|
||||
final pinnedFilters = settings.pinnedFilters..add(filter);
|
||||
settings.pinnedFilters = pinnedFilters;
|
||||
break;
|
||||
case AlbumAction.unpin:
|
||||
final pinnedFilters = settings.pinnedFilters..remove(filter);
|
||||
settings.pinnedFilters = pinnedFilters;
|
||||
break;
|
||||
case AlbumAction.rename:
|
||||
// TODO TLAD rename album
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// common with album selection page to move/copy entries
|
||||
|
||||
static Map<String, ImageEntry> getAlbumEntries(CollectionSource source) {
|
||||
final pinned = settings.pinnedFilters.whereType<AlbumFilter>().map((f) => f.album);
|
||||
final entriesByDate = source.sortedEntriesForFilterList;
|
||||
final albums = source.sortedAlbums
|
||||
.map((album) => MapEntry(
|
||||
album,
|
||||
entriesByDate.firstWhere((entry) => entry.directory == album, orElse: () => null),
|
||||
))
|
||||
.toList();
|
||||
|
||||
switch (settings.albumSortFactor) {
|
||||
case ChipSortFactor.date:
|
||||
albums.sort(FilterNavigationPage.compareChipByDate);
|
||||
return Map.fromEntries(albums);
|
||||
final allAlbumMapEntries = source.sortedAlbums.map((album) => MapEntry(
|
||||
album,
|
||||
entriesByDate.firstWhere((entry) => entry.directory == album, orElse: () => null),
|
||||
));
|
||||
final byPin = groupBy<MapEntry<String, ImageEntry>, bool>(allAlbumMapEntries, (e) => pinned.contains(e.key));
|
||||
final pinnedAlbumMapEntries = (byPin[true] ?? [])..sort(FilterNavigationPage.compareChipByDate);
|
||||
final unpinnedAlbumMapEntries = (byPin[false] ?? [])..sort(FilterNavigationPage.compareChipByDate);
|
||||
return Map.fromEntries([...pinnedAlbumMapEntries, ...unpinnedAlbumMapEntries]);
|
||||
case ChipSortFactor.name:
|
||||
default:
|
||||
final regularAlbums = <String>[], appAlbums = <String>[], specialAlbums = <String>[];
|
||||
final pinnedAlbums = <String>[], regularAlbums = <String>[], appAlbums = <String>[], specialAlbums = <String>[];
|
||||
for (var album in source.sortedAlbums) {
|
||||
switch (androidFileUtils.getAlbumType(album)) {
|
||||
case AlbumType.regular:
|
||||
regularAlbums.add(album);
|
||||
break;
|
||||
case AlbumType.app:
|
||||
appAlbums.add(album);
|
||||
break;
|
||||
default:
|
||||
specialAlbums.add(album);
|
||||
break;
|
||||
if (pinned.contains(album)) {
|
||||
pinnedAlbums.add(album);
|
||||
} else {
|
||||
switch (androidFileUtils.getAlbumType(album)) {
|
||||
case AlbumType.regular:
|
||||
regularAlbums.add(album);
|
||||
break;
|
||||
case AlbumType.app:
|
||||
appAlbums.add(album);
|
||||
break;
|
||||
default:
|
||||
specialAlbums.add(album);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Map.fromEntries([...specialAlbums, ...appAlbums, ...regularAlbums].map((album) {
|
||||
return Map.fromEntries([...pinnedAlbums, ...specialAlbums, ...appAlbums, ...regularAlbums].map((album) {
|
||||
return MapEntry(
|
||||
album,
|
||||
entriesByDate.firstWhere((entry) => entry.directory == album, orElse: () => null),
|
||||
|
|
|
@ -1,3 +1,37 @@
|
|||
import 'package:aves/widgets/common/icons.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
enum ChipAction {
|
||||
sort,
|
||||
}
|
||||
|
||||
enum AlbumAction {
|
||||
pin,
|
||||
unpin,
|
||||
rename,
|
||||
}
|
||||
|
||||
extension ExtraAlbumAction on AlbumAction {
|
||||
String getText() {
|
||||
switch (this) {
|
||||
case AlbumAction.pin:
|
||||
return 'Pin to top';
|
||||
case AlbumAction.unpin:
|
||||
return 'Unpin from top';
|
||||
case AlbumAction.rename:
|
||||
return 'Rename';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
IconData getIcon() {
|
||||
switch (this) {
|
||||
case AlbumAction.pin:
|
||||
case AlbumAction.unpin:
|
||||
return AIcons.pin;
|
||||
case AlbumAction.rename:
|
||||
return AIcons.rename;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ class DecoratedFilterChip extends StatelessWidget {
|
|||
final CollectionSource source;
|
||||
final CollectionFilter filter;
|
||||
final ImageEntry entry;
|
||||
final bool pinned;
|
||||
final FilterCallback onTap;
|
||||
final OffsetFilterCallback onLongPress;
|
||||
|
||||
|
@ -24,6 +25,7 @@ class DecoratedFilterChip extends StatelessWidget {
|
|||
@required this.source,
|
||||
@required this.filter,
|
||||
@required this.entry,
|
||||
this.pinned = false,
|
||||
@required this.onTap,
|
||||
this.onLongPress,
|
||||
}) : super(key: key);
|
||||
|
@ -57,19 +59,29 @@ class DecoratedFilterChip extends StatelessWidget {
|
|||
'${source.count(filter)}',
|
||||
style: TextStyle(color: FilterGridPage.detailColor),
|
||||
);
|
||||
return filter is AlbumFilter && androidFileUtils.isOnRemovableStorage(filter.album)
|
||||
? Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
AIcons.removableStorage,
|
||||
size: 16,
|
||||
color: FilterGridPage.detailColor,
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
count,
|
||||
],
|
||||
)
|
||||
: count;
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (pinned)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: 8),
|
||||
child: Icon(
|
||||
AIcons.pin,
|
||||
size: 16,
|
||||
color: FilterGridPage.detailColor,
|
||||
),
|
||||
),
|
||||
if (filter is AlbumFilter && androidFileUtils.isOnRemovableStorage(filter.album))
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: 8),
|
||||
child: Icon(
|
||||
AIcons.removableStorage,
|
||||
size: 16,
|
||||
color: FilterGridPage.detailColor,
|
||||
),
|
||||
),
|
||||
count,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -146,6 +146,7 @@ class FilterGridPage extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final pinnedFilters = settings.pinnedFilters;
|
||||
return MediaQueryDataProvider(
|
||||
child: Scaffold(
|
||||
body: DoubleBackPopScope(
|
||||
|
@ -169,11 +170,13 @@ class FilterGridPage extends StatelessWidget {
|
|||
delegate: SliverChildBuilderDelegate(
|
||||
(context, i) {
|
||||
final key = filterKeys[i];
|
||||
final filter = filterBuilder(key);
|
||||
final child = DecoratedFilterChip(
|
||||
key: Key(key),
|
||||
source: source,
|
||||
filter: filterBuilder(key),
|
||||
filter: filter,
|
||||
entry: filterEntries[key],
|
||||
pinned: pinnedFilters.contains(filter),
|
||||
onTap: onTap,
|
||||
onLongPress: onLongPress,
|
||||
);
|
||||
|
|
Loading…
Reference in a new issue