countries/tags: pin to top
This commit is contained in:
parent
055cad333f
commit
acf26677af
12 changed files with 168 additions and 93 deletions
|
@ -27,9 +27,11 @@ class LocationFilter extends CollectionFilter {
|
||||||
Map<String, dynamic> toMap() => {
|
Map<String, dynamic> toMap() => {
|
||||||
'type': type,
|
'type': type,
|
||||||
'level': level.toString(),
|
'level': level.toString(),
|
||||||
'location': _countryCode != null ? '$_location$locationSeparator$_countryCode' : _location,
|
'location': _countryCode != null ? countryNameAndCode : _location,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
String get countryNameAndCode => '$_location$locationSeparator$_countryCode';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool filter(ImageEntry entry) => entry.isLocated && ((level == LocationLevel.country && entry.addressDetails.countryCode == _countryCode) || (level == LocationLevel.place && entry.addressDetails.place == _location));
|
bool filter(ImageEntry entry) => entry.isLocated && ((level == LocationLevel.country && entry.addressDetails.countryCode == _countryCode) || (level == LocationLevel.place && entry.addressDetails.place == _location));
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ import 'package:aves/widgets/common/data_providers/media_store_collection_provid
|
||||||
import 'package:aves/widgets/common/entry_actions.dart';
|
import 'package:aves/widgets/common/entry_actions.dart';
|
||||||
import 'package:aves/widgets/common/icons.dart';
|
import 'package:aves/widgets/common/icons.dart';
|
||||||
import 'package:aves/widgets/common/menu_row.dart';
|
import 'package:aves/widgets/common/menu_row.dart';
|
||||||
import 'package:aves/widgets/filter_grids/search_button.dart';
|
import 'package:aves/widgets/common/search_button.dart';
|
||||||
import 'package:aves/widgets/stats/stats.dart';
|
import 'package:aves/widgets/stats/stats.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
|
@ -18,7 +18,7 @@ import 'package:aves/widgets/common/aves_dialog.dart';
|
||||||
import 'package:aves/widgets/common/entry_actions.dart';
|
import 'package:aves/widgets/common/entry_actions.dart';
|
||||||
import 'package:aves/widgets/common/icons.dart';
|
import 'package:aves/widgets/common/icons.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/filter_grid_page.dart';
|
import 'package:aves/widgets/filter_grids/common/filter_grid_page.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
|
|
|
@ -8,10 +8,10 @@ import 'package:aves/model/source/enums.dart';
|
||||||
import 'package:aves/utils/android_file_utils.dart';
|
import 'package:aves/utils/android_file_utils.dart';
|
||||||
import 'package:aves/widgets/collection/empty.dart';
|
import 'package:aves/widgets/collection/empty.dart';
|
||||||
import 'package:aves/widgets/common/icons.dart';
|
import 'package:aves/widgets/common/icons.dart';
|
||||||
import 'package:aves/widgets/common/menu_row.dart';
|
import 'package:aves/widgets/filter_grids/common/chip_action_delegate.dart';
|
||||||
import 'package:aves/widgets/filter_grids/chip_action_delegate.dart';
|
import 'package:aves/widgets/filter_grids/common/chip_actions.dart';
|
||||||
import 'package:aves/widgets/filter_grids/chip_actions.dart';
|
import 'package:aves/widgets/filter_grids/common/chip_set_action_delegate.dart';
|
||||||
import 'package:aves/widgets/filter_grids/filter_grid_page.dart';
|
import 'package:aves/widgets/filter_grids/common/filter_grid_page.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
@ -22,6 +22,7 @@ class AlbumListPage extends StatelessWidget {
|
||||||
|
|
||||||
final CollectionSource source;
|
final CollectionSource source;
|
||||||
|
|
||||||
|
static final ChipSetActionDelegate setActionDelegate = AlbumChipSetActionDelegate();
|
||||||
static final ChipActionDelegate actionDelegate = AlbumChipActionDelegate();
|
static final ChipActionDelegate actionDelegate = AlbumChipActionDelegate();
|
||||||
|
|
||||||
const AlbumListPage({@required this.source});
|
const AlbumListPage({@required this.source});
|
||||||
|
@ -38,14 +39,18 @@ class AlbumListPage extends StatelessWidget {
|
||||||
builder: (context, snapshot) => FilterNavigationPage(
|
builder: (context, snapshot) => FilterNavigationPage(
|
||||||
source: source,
|
source: source,
|
||||||
title: 'Albums',
|
title: 'Albums',
|
||||||
actionDelegate: actionDelegate,
|
chipSetActionDelegate: setActionDelegate,
|
||||||
|
chipActionDelegate: actionDelegate,
|
||||||
|
chipActionsBuilder: (filter) => [
|
||||||
|
settings.pinnedFilters.contains(filter) ? ChipAction.unpin : ChipAction.pin,
|
||||||
|
ChipAction.rename,
|
||||||
|
],
|
||||||
filterEntries: getAlbumEntries(source),
|
filterEntries: getAlbumEntries(source),
|
||||||
filterBuilder: (album) => AlbumFilter(album, source.getUniqueAlbumName(album)),
|
filterBuilder: (album) => AlbumFilter(album, source.getUniqueAlbumName(album)),
|
||||||
emptyBuilder: () => EmptyContent(
|
emptyBuilder: () => EmptyContent(
|
||||||
icon: AIcons.album,
|
icon: AIcons.album,
|
||||||
text: 'No albums',
|
text: 'No albums',
|
||||||
),
|
),
|
||||||
onLongPress: (filter, tapPosition) => _showMenu(context, filter, tapPosition),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -53,38 +58,6 @@ 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
|
// common with album selection page to move/copy entries
|
||||||
|
|
||||||
static Map<String, ImageEntry> getAlbumEntries(CollectionSource source) {
|
static Map<String, ImageEntry> getAlbumEntries(CollectionSource source) {
|
||||||
|
@ -98,9 +71,9 @@ class AlbumListPage extends StatelessWidget {
|
||||||
entriesByDate.firstWhere((entry) => entry.directory == album, orElse: () => null),
|
entriesByDate.firstWhere((entry) => entry.directory == album, orElse: () => null),
|
||||||
));
|
));
|
||||||
final byPin = groupBy<MapEntry<String, ImageEntry>, bool>(allAlbumMapEntries, (e) => pinned.contains(e.key));
|
final byPin = groupBy<MapEntry<String, ImageEntry>, bool>(allAlbumMapEntries, (e) => pinned.contains(e.key));
|
||||||
final pinnedAlbumMapEntries = (byPin[true] ?? [])..sort(FilterNavigationPage.compareChipByDate);
|
final pinnedMapEntries = (byPin[true] ?? [])..sort(FilterNavigationPage.compareChipByDate);
|
||||||
final unpinnedAlbumMapEntries = (byPin[false] ?? [])..sort(FilterNavigationPage.compareChipByDate);
|
final unpinnedMapEntries = (byPin[false] ?? [])..sort(FilterNavigationPage.compareChipByDate);
|
||||||
return Map.fromEntries([...pinnedAlbumMapEntries, ...unpinnedAlbumMapEntries]);
|
return Map.fromEntries([...pinnedMapEntries, ...unpinnedMapEntries]);
|
||||||
case ChipSortFactor.name:
|
case ChipSortFactor.name:
|
||||||
default:
|
default:
|
||||||
final pinnedAlbums = <String>[], regularAlbums = <String>[], appAlbums = <String>[], specialAlbums = <String>[];
|
final pinnedAlbums = <String>[], regularAlbums = <String>[], appAlbums = <String>[], specialAlbums = <String>[];
|
||||||
|
|
40
lib/widgets/filter_grids/common/chip_action_delegate.dart
Normal file
40
lib/widgets/filter_grids/common/chip_action_delegate.dart
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import 'package:aves/model/filters/filters.dart';
|
||||||
|
import 'package:aves/model/settings/settings.dart';
|
||||||
|
import 'package:aves/utils/durations.dart';
|
||||||
|
import 'package:aves/widgets/filter_grids/common/chip_actions.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/scheduler.dart';
|
||||||
|
|
||||||
|
class ChipActionDelegate {
|
||||||
|
Future<void> onActionSelected(BuildContext context, CollectionFilter filter, ChipAction action) async {
|
||||||
|
// wait for the popup menu to hide before proceeding with the action
|
||||||
|
await Future.delayed(Durations.popupMenuAnimation * timeDilation);
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case ChipAction.pin:
|
||||||
|
final pinnedFilters = settings.pinnedFilters..add(filter);
|
||||||
|
settings.pinnedFilters = pinnedFilters;
|
||||||
|
break;
|
||||||
|
case ChipAction.unpin:
|
||||||
|
final pinnedFilters = settings.pinnedFilters..remove(filter);
|
||||||
|
settings.pinnedFilters = pinnedFilters;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AlbumChipActionDelegate extends ChipActionDelegate {
|
||||||
|
@override
|
||||||
|
Future<void> onActionSelected(BuildContext context, CollectionFilter filter, ChipAction action) async {
|
||||||
|
await super.onActionSelected(context, filter, action);
|
||||||
|
switch (action) {
|
||||||
|
case ChipAction.rename:
|
||||||
|
// TODO TLAD rename album
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,24 +1,24 @@
|
||||||
import 'package:aves/widgets/common/icons.dart';
|
import 'package:aves/widgets/common/icons.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
enum ChipAction {
|
enum ChipSetAction {
|
||||||
sort,
|
sort,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum AlbumAction {
|
enum ChipAction {
|
||||||
pin,
|
pin,
|
||||||
unpin,
|
unpin,
|
||||||
rename,
|
rename,
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ExtraAlbumAction on AlbumAction {
|
extension ExtraChipAction on ChipAction {
|
||||||
String getText() {
|
String getText() {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case AlbumAction.pin:
|
case ChipAction.pin:
|
||||||
return 'Pin to top';
|
return 'Pin to top';
|
||||||
case AlbumAction.unpin:
|
case ChipAction.unpin:
|
||||||
return 'Unpin from top';
|
return 'Unpin from top';
|
||||||
case AlbumAction.rename:
|
case ChipAction.rename:
|
||||||
return 'Rename';
|
return 'Rename';
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -26,10 +26,10 @@ extension ExtraAlbumAction on AlbumAction {
|
||||||
|
|
||||||
IconData getIcon() {
|
IconData getIcon() {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case AlbumAction.pin:
|
case ChipAction.pin:
|
||||||
case AlbumAction.unpin:
|
case ChipAction.unpin:
|
||||||
return AIcons.pin;
|
return AIcons.pin;
|
||||||
case AlbumAction.rename:
|
case ChipAction.rename:
|
||||||
return AIcons.rename;
|
return AIcons.rename;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
|
@ -2,21 +2,21 @@ import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/model/source/enums.dart';
|
import 'package:aves/model/source/enums.dart';
|
||||||
import 'package:aves/utils/durations.dart';
|
import 'package:aves/utils/durations.dart';
|
||||||
import 'package:aves/widgets/common/aves_selection_dialog.dart';
|
import 'package:aves/widgets/common/aves_selection_dialog.dart';
|
||||||
import 'package:aves/widgets/filter_grids/chip_actions.dart';
|
import 'package:aves/widgets/filter_grids/common/chip_actions.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
|
|
||||||
abstract class ChipActionDelegate {
|
abstract class ChipSetActionDelegate {
|
||||||
ChipSortFactor get sortFactor;
|
ChipSortFactor get sortFactor;
|
||||||
|
|
||||||
set sortFactor(ChipSortFactor factor);
|
set sortFactor(ChipSortFactor factor);
|
||||||
|
|
||||||
Future<void> onChipActionSelected(BuildContext context, ChipAction action) async {
|
Future<void> onActionSelected(BuildContext context, ChipSetAction action) async {
|
||||||
// wait for the popup menu to hide before proceeding with the action
|
// wait for the popup menu to hide before proceeding with the action
|
||||||
await Future.delayed(Durations.popupMenuAnimation * timeDilation);
|
await Future.delayed(Durations.popupMenuAnimation * timeDilation);
|
||||||
|
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case ChipAction.sort:
|
case ChipSetAction.sort:
|
||||||
await _showSortDialog(context);
|
await _showSortDialog(context);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ abstract class ChipActionDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AlbumChipActionDelegate extends ChipActionDelegate {
|
class AlbumChipSetActionDelegate extends ChipSetActionDelegate {
|
||||||
@override
|
@override
|
||||||
ChipSortFactor get sortFactor => settings.albumSortFactor;
|
ChipSortFactor get sortFactor => settings.albumSortFactor;
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ class AlbumChipActionDelegate extends ChipActionDelegate {
|
||||||
set sortFactor(ChipSortFactor factor) => settings.albumSortFactor = factor;
|
set sortFactor(ChipSortFactor factor) => settings.albumSortFactor = factor;
|
||||||
}
|
}
|
||||||
|
|
||||||
class CountryChipActionDelegate extends ChipActionDelegate {
|
class CountryChipSetActionDelegate extends ChipSetActionDelegate {
|
||||||
@override
|
@override
|
||||||
ChipSortFactor get sortFactor => settings.countrySortFactor;
|
ChipSortFactor get sortFactor => settings.countrySortFactor;
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ class CountryChipActionDelegate extends ChipActionDelegate {
|
||||||
set sortFactor(ChipSortFactor factor) => settings.countrySortFactor = factor;
|
set sortFactor(ChipSortFactor factor) => settings.countrySortFactor = factor;
|
||||||
}
|
}
|
||||||
|
|
||||||
class TagChipActionDelegate extends ChipActionDelegate {
|
class TagChipSetActionDelegate extends ChipSetActionDelegate {
|
||||||
@override
|
@override
|
||||||
ChipSortFactor get sortFactor => settings.tagSortFactor;
|
ChipSortFactor get sortFactor => settings.tagSortFactor;
|
||||||
|
|
|
@ -9,7 +9,7 @@ import 'package:aves/widgets/collection/thumbnail/raster.dart';
|
||||||
import 'package:aves/widgets/collection/thumbnail/vector.dart';
|
import 'package:aves/widgets/collection/thumbnail/vector.dart';
|
||||||
import 'package:aves/widgets/common/aves_filter_chip.dart';
|
import 'package:aves/widgets/common/aves_filter_chip.dart';
|
||||||
import 'package:aves/widgets/common/icons.dart';
|
import 'package:aves/widgets/common/icons.dart';
|
||||||
import 'package:aves/widgets/filter_grids/filter_grid_page.dart';
|
import 'package:aves/widgets/filter_grids/common/filter_grid_page.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class DecoratedFilterChip extends StatelessWidget {
|
class DecoratedFilterChip extends StatelessWidget {
|
|
@ -15,33 +15,37 @@ import 'package:aves/widgets/common/data_providers/media_query_data_provider.dar
|
||||||
import 'package:aves/widgets/common/double_back_pop.dart';
|
import 'package:aves/widgets/common/double_back_pop.dart';
|
||||||
import 'package:aves/widgets/common/icons.dart';
|
import 'package:aves/widgets/common/icons.dart';
|
||||||
import 'package:aves/widgets/common/menu_row.dart';
|
import 'package:aves/widgets/common/menu_row.dart';
|
||||||
|
import 'package:aves/widgets/common/search_button.dart';
|
||||||
import 'package:aves/widgets/drawer/app_drawer.dart';
|
import 'package:aves/widgets/drawer/app_drawer.dart';
|
||||||
import 'package:aves/widgets/filter_grids/chip_action_delegate.dart';
|
import 'package:aves/widgets/filter_grids/common/chip_action_delegate.dart';
|
||||||
import 'package:aves/widgets/filter_grids/chip_actions.dart';
|
import 'package:aves/widgets/filter_grids/common/chip_actions.dart';
|
||||||
import 'package:aves/widgets/filter_grids/decorated_filter_chip.dart';
|
import 'package:aves/widgets/filter_grids/common/chip_set_action_delegate.dart';
|
||||||
import 'package:aves/widgets/filter_grids/search_button.dart';
|
import 'package:aves/widgets/filter_grids/common/decorated_filter_chip.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
||||||
|
import 'package:pedantic/pedantic.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class FilterNavigationPage extends StatelessWidget {
|
class FilterNavigationPage extends StatelessWidget {
|
||||||
final CollectionSource source;
|
final CollectionSource source;
|
||||||
final String title;
|
final String title;
|
||||||
final ChipActionDelegate actionDelegate;
|
final ChipSetActionDelegate chipSetActionDelegate;
|
||||||
|
final ChipActionDelegate chipActionDelegate;
|
||||||
final Map<String, ImageEntry> filterEntries;
|
final Map<String, ImageEntry> filterEntries;
|
||||||
final CollectionFilter Function(String key) filterBuilder;
|
final CollectionFilter Function(String key) filterBuilder;
|
||||||
final Widget Function() emptyBuilder;
|
final Widget Function() emptyBuilder;
|
||||||
final OffsetFilterCallback onLongPress;
|
final List<ChipAction> Function(CollectionFilter filter) chipActionsBuilder;
|
||||||
|
|
||||||
const FilterNavigationPage({
|
const FilterNavigationPage({
|
||||||
@required this.source,
|
@required this.source,
|
||||||
@required this.title,
|
@required this.title,
|
||||||
@required this.actionDelegate,
|
@required this.chipSetActionDelegate,
|
||||||
|
@required this.chipActionDelegate,
|
||||||
|
@required this.chipActionsBuilder,
|
||||||
@required this.filterEntries,
|
@required this.filterEntries,
|
||||||
@required this.filterBuilder,
|
@required this.filterBuilder,
|
||||||
@required this.emptyBuilder,
|
@required this.emptyBuilder,
|
||||||
this.onLongPress,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -81,25 +85,43 @@ class FilterNavigationPage extends StatelessWidget {
|
||||||
),
|
),
|
||||||
settings.navRemoveRoutePredicate(CollectionPage.routeName),
|
settings.navRemoveRoutePredicate(CollectionPage.routeName),
|
||||||
),
|
),
|
||||||
onLongPress: onLongPress,
|
onLongPress: (filter, tapPosition) => _showMenu(context, filter, tapPosition),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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<ChipAction>(
|
||||||
|
context: context,
|
||||||
|
position: RelativeRect.fromRect(tapPosition & touchArea, Offset.zero & overlay.size),
|
||||||
|
items: chipActionsBuilder(filter)
|
||||||
|
.map((action) => PopupMenuItem(
|
||||||
|
value: action,
|
||||||
|
child: MenuRow(text: action.getText(), icon: action.getIcon()),
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
if (selectedAction != null) {
|
||||||
|
unawaited(chipActionDelegate.onActionSelected(context, filter, selectedAction));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
List<Widget> _buildActions(BuildContext context) {
|
List<Widget> _buildActions(BuildContext context) {
|
||||||
return [
|
return [
|
||||||
SearchButton(source),
|
SearchButton(source),
|
||||||
PopupMenuButton<ChipAction>(
|
PopupMenuButton<ChipSetAction>(
|
||||||
key: Key('appbar-menu-button'),
|
key: Key('appbar-menu-button'),
|
||||||
itemBuilder: (context) {
|
itemBuilder: (context) {
|
||||||
return [
|
return [
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
key: Key('menu-sort'),
|
key: Key('menu-sort'),
|
||||||
value: ChipAction.sort,
|
value: ChipSetAction.sort,
|
||||||
child: MenuRow(text: 'Sort...', icon: AIcons.sort),
|
child: MenuRow(text: 'Sort...', icon: AIcons.sort),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
onSelected: (action) => actionDelegate.onChipActionSelected(context, action),
|
onSelected: (action) => chipSetActionDelegate.onActionSelected(context, action),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
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/image_entry.dart';
|
import 'package:aves/model/image_entry.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
|
@ -6,31 +7,40 @@ import 'package:aves/model/source/enums.dart';
|
||||||
import 'package:aves/model/source/location.dart';
|
import 'package:aves/model/source/location.dart';
|
||||||
import 'package:aves/widgets/collection/empty.dart';
|
import 'package:aves/widgets/collection/empty.dart';
|
||||||
import 'package:aves/widgets/common/icons.dart';
|
import 'package:aves/widgets/common/icons.dart';
|
||||||
import 'package:aves/widgets/filter_grids/chip_action_delegate.dart';
|
import 'package:aves/widgets/filter_grids/common/chip_action_delegate.dart';
|
||||||
import 'package:aves/widgets/filter_grids/filter_grid_page.dart';
|
import 'package:aves/widgets/filter_grids/common/chip_actions.dart';
|
||||||
|
import 'package:aves/widgets/filter_grids/common/chip_set_action_delegate.dart';
|
||||||
|
import 'package:aves/widgets/filter_grids/common/filter_grid_page.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
class CountryListPage extends StatelessWidget {
|
class CountryListPage extends StatelessWidget {
|
||||||
static const routeName = '/countries';
|
static const routeName = '/countries';
|
||||||
|
|
||||||
final CollectionSource source;
|
final CollectionSource source;
|
||||||
|
|
||||||
static final ChipActionDelegate actionDelegate = CountryChipActionDelegate();
|
static final ChipSetActionDelegate setActionDelegate = CountryChipSetActionDelegate();
|
||||||
|
static final ChipActionDelegate actionDelegate = ChipActionDelegate();
|
||||||
|
|
||||||
const CountryListPage({@required this.source});
|
const CountryListPage({@required this.source});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Selector<Settings, ChipSortFactor>(
|
return Selector<Settings, Tuple2<ChipSortFactor, Set<CollectionFilter>>>(
|
||||||
selector: (context, s) => s.countrySortFactor,
|
selector: (context, s) => Tuple2(s.countrySortFactor, s.pinnedFilters),
|
||||||
builder: (context, sortFactor, child) {
|
builder: (context, s, child) {
|
||||||
return StreamBuilder(
|
return StreamBuilder(
|
||||||
stream: source.eventBus.on<LocationsChangedEvent>(),
|
stream: source.eventBus.on<LocationsChangedEvent>(),
|
||||||
builder: (context, snapshot) => FilterNavigationPage(
|
builder: (context, snapshot) => FilterNavigationPage(
|
||||||
source: source,
|
source: source,
|
||||||
title: 'Countries',
|
title: 'Countries',
|
||||||
actionDelegate: actionDelegate,
|
chipSetActionDelegate: setActionDelegate,
|
||||||
|
chipActionDelegate: actionDelegate,
|
||||||
|
chipActionsBuilder: (filter) => [
|
||||||
|
settings.pinnedFilters.contains(filter) ? ChipAction.unpin : ChipAction.pin,
|
||||||
|
],
|
||||||
filterEntries: _getCountryEntries(),
|
filterEntries: _getCountryEntries(),
|
||||||
filterBuilder: (s) => LocationFilter(LocationLevel.country, s),
|
filterBuilder: (s) => LocationFilter(LocationLevel.country, s),
|
||||||
emptyBuilder: () => EmptyContent(
|
emptyBuilder: () => EmptyContent(
|
||||||
|
@ -44,9 +54,11 @@ class CountryListPage extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, ImageEntry> _getCountryEntries() {
|
Map<String, ImageEntry> _getCountryEntries() {
|
||||||
|
final pinned = settings.pinnedFilters.whereType<LocationFilter>().map((f) => f.countryNameAndCode);
|
||||||
|
|
||||||
final entriesByDate = source.sortedEntriesForFilterList;
|
final entriesByDate = source.sortedEntriesForFilterList;
|
||||||
final locatedEntries = entriesByDate.where((entry) => entry.isLocated);
|
final locatedEntries = entriesByDate.where((entry) => entry.isLocated);
|
||||||
final countries = source.sortedCountries.map((countryNameAndCode) {
|
final allMapEntries = source.sortedCountries.map((countryNameAndCode) {
|
||||||
final split = countryNameAndCode.split(LocationFilter.locationSeparator);
|
final split = countryNameAndCode.split(LocationFilter.locationSeparator);
|
||||||
ImageEntry entry;
|
ImageEntry entry;
|
||||||
if (split.length > 1) {
|
if (split.length > 1) {
|
||||||
|
@ -56,12 +68,19 @@ class CountryListPage extends StatelessWidget {
|
||||||
return MapEntry(countryNameAndCode, entry);
|
return MapEntry(countryNameAndCode, entry);
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
|
final byPin = groupBy<MapEntry<String, ImageEntry>, bool>(allMapEntries, (e) => pinned.contains(e.key));
|
||||||
|
final pinnedMapEntries = (byPin[true] ?? []);
|
||||||
|
final unpinnedMapEntries = (byPin[false] ?? []);
|
||||||
|
|
||||||
switch (settings.countrySortFactor) {
|
switch (settings.countrySortFactor) {
|
||||||
case ChipSortFactor.date:
|
case ChipSortFactor.date:
|
||||||
countries.sort(FilterNavigationPage.compareChipByDate);
|
pinnedMapEntries.sort(FilterNavigationPage.compareChipByDate);
|
||||||
|
unpinnedMapEntries.sort(FilterNavigationPage.compareChipByDate);
|
||||||
break;
|
break;
|
||||||
case ChipSortFactor.name:
|
case ChipSortFactor.name:
|
||||||
|
// already sorted by name at the source level
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return Map.fromEntries(countries);
|
return Map.fromEntries([...pinnedMapEntries, ...unpinnedMapEntries]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
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/image_entry.dart';
|
import 'package:aves/model/image_entry.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
|
@ -6,31 +7,40 @@ import 'package:aves/model/source/enums.dart';
|
||||||
import 'package:aves/model/source/tag.dart';
|
import 'package:aves/model/source/tag.dart';
|
||||||
import 'package:aves/widgets/collection/empty.dart';
|
import 'package:aves/widgets/collection/empty.dart';
|
||||||
import 'package:aves/widgets/common/icons.dart';
|
import 'package:aves/widgets/common/icons.dart';
|
||||||
import 'package:aves/widgets/filter_grids/chip_action_delegate.dart';
|
import 'package:aves/widgets/filter_grids/common/chip_action_delegate.dart';
|
||||||
import 'package:aves/widgets/filter_grids/filter_grid_page.dart';
|
import 'package:aves/widgets/filter_grids/common/chip_actions.dart';
|
||||||
|
import 'package:aves/widgets/filter_grids/common/chip_set_action_delegate.dart';
|
||||||
|
import 'package:aves/widgets/filter_grids/common/filter_grid_page.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
class TagListPage extends StatelessWidget {
|
class TagListPage extends StatelessWidget {
|
||||||
static const routeName = '/tags';
|
static const routeName = '/tags';
|
||||||
|
|
||||||
final CollectionSource source;
|
final CollectionSource source;
|
||||||
|
|
||||||
static final ChipActionDelegate actionDelegate = TagChipActionDelegate();
|
static final ChipSetActionDelegate setActionDelegate = TagChipSetActionDelegate();
|
||||||
|
static final ChipActionDelegate actionDelegate = ChipActionDelegate();
|
||||||
|
|
||||||
const TagListPage({@required this.source});
|
const TagListPage({@required this.source});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Selector<Settings, ChipSortFactor>(
|
return Selector<Settings, Tuple2<ChipSortFactor, Set<CollectionFilter>>>(
|
||||||
selector: (context, s) => s.tagSortFactor,
|
selector: (context, s) => Tuple2(s.tagSortFactor, s.pinnedFilters),
|
||||||
builder: (context, sortFactor, child) {
|
builder: (context, s, child) {
|
||||||
return StreamBuilder(
|
return StreamBuilder(
|
||||||
stream: source.eventBus.on<TagsChangedEvent>(),
|
stream: source.eventBus.on<TagsChangedEvent>(),
|
||||||
builder: (context, snapshot) => FilterNavigationPage(
|
builder: (context, snapshot) => FilterNavigationPage(
|
||||||
source: source,
|
source: source,
|
||||||
title: 'Tags',
|
title: 'Tags',
|
||||||
actionDelegate: actionDelegate,
|
chipSetActionDelegate: setActionDelegate,
|
||||||
|
chipActionDelegate: actionDelegate,
|
||||||
|
chipActionsBuilder: (filter) => [
|
||||||
|
settings.pinnedFilters.contains(filter) ? ChipAction.unpin : ChipAction.pin,
|
||||||
|
],
|
||||||
filterEntries: _getTagEntries(),
|
filterEntries: _getTagEntries(),
|
||||||
filterBuilder: (s) => TagFilter(s),
|
filterBuilder: (s) => TagFilter(s),
|
||||||
emptyBuilder: () => EmptyContent(
|
emptyBuilder: () => EmptyContent(
|
||||||
|
@ -44,20 +54,29 @@ class TagListPage extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, ImageEntry> _getTagEntries() {
|
Map<String, ImageEntry> _getTagEntries() {
|
||||||
|
final pinned = settings.pinnedFilters.whereType<TagFilter>().map((f) => f.tag);
|
||||||
|
|
||||||
final entriesByDate = source.sortedEntriesForFilterList;
|
final entriesByDate = source.sortedEntriesForFilterList;
|
||||||
final tags = source.sortedTags
|
final allMapEntries = source.sortedTags
|
||||||
.map((tag) => MapEntry(
|
.map((tag) => MapEntry(
|
||||||
tag,
|
tag,
|
||||||
entriesByDate.firstWhere((entry) => entry.xmpSubjects.contains(tag), orElse: () => null),
|
entriesByDate.firstWhere((entry) => entry.xmpSubjects.contains(tag), orElse: () => null),
|
||||||
))
|
))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
|
final byPin = groupBy<MapEntry<String, ImageEntry>, bool>(allMapEntries, (e) => pinned.contains(e.key));
|
||||||
|
final pinnedMapEntries = (byPin[true] ?? []);
|
||||||
|
final unpinnedMapEntries = (byPin[false] ?? []);
|
||||||
|
|
||||||
switch (settings.tagSortFactor) {
|
switch (settings.tagSortFactor) {
|
||||||
case ChipSortFactor.date:
|
case ChipSortFactor.date:
|
||||||
tags.sort(FilterNavigationPage.compareChipByDate);
|
pinnedMapEntries.sort(FilterNavigationPage.compareChipByDate);
|
||||||
|
unpinnedMapEntries.sort(FilterNavigationPage.compareChipByDate);
|
||||||
break;
|
break;
|
||||||
case ChipSortFactor.name:
|
case ChipSortFactor.name:
|
||||||
|
// already sorted by name at the source level
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return Map.fromEntries(tags);
|
return Map.fromEntries([...pinnedMapEntries, ...unpinnedMapEntries]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue