From 4c23a0f5ad9359981d7544261c8709a562c99115 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Thu, 26 Mar 2020 19:15:34 +0900 Subject: [PATCH] info: moved tag filters to basic section --- lib/model/collection_filters.dart | 69 ++++++++++++++++--- lib/widgets/album/filter_bar.dart | 5 +- lib/widgets/album/search_delegate.dart | 2 +- lib/widgets/common/aves_filter_chip.dart | 31 +++------ .../fullscreen/info/basic_section.dart | 26 +++++-- lib/widgets/fullscreen/info/info_page.dart | 10 --- .../fullscreen/info/location_section.dart | 2 +- lib/widgets/fullscreen/info/xmp_section.dart | 48 ------------- 8 files changed, 96 insertions(+), 97 deletions(-) delete mode 100644 lib/widgets/fullscreen/info/xmp_section.dart diff --git a/lib/model/collection_filters.dart b/lib/model/collection_filters.dart index ab8c861cd..44d2d79b0 100644 --- a/lib/model/collection_filters.dart +++ b/lib/model/collection_filters.dart @@ -1,19 +1,35 @@ import 'package:aves/model/image_entry.dart'; +import 'package:aves/widgets/common/icons.dart'; import 'package:flutter/widgets.dart'; import 'package:outline_material_icons/outline_material_icons.dart'; import 'package:path/path.dart'; abstract class CollectionFilter { + static const List collectionFilterOrder = [ + VideoFilter.type, + GifFilter.type, + AlbumFilter.type, + CountryFilter.type, + TagFilter.type, + QueryFilter.type, + ]; + const CollectionFilter(); bool filter(ImageEntry entry); String get label; - IconData get icon => null; + Widget iconBuilder(BuildContext context); + + String get typeKey; + + int get displayPriority => collectionFilterOrder.indexOf(typeKey); } class AlbumFilter extends CollectionFilter { + static const type = 'album'; + final String album; const AlbumFilter(this.album); @@ -25,7 +41,10 @@ class AlbumFilter extends CollectionFilter { String get label => album.split(separator).last; @override - IconData get icon => OMIcons.photoAlbum; + Widget iconBuilder(context) => IconUtils.getAlbumIcon(context, album) ?? Icon(OMIcons.photoAlbum); + + @override + String get typeKey => type; @override bool operator ==(Object other) { @@ -38,6 +57,8 @@ class AlbumFilter extends CollectionFilter { } class TagFilter extends CollectionFilter { + static const type = 'tag'; + final String tag; const TagFilter(this.tag); @@ -49,7 +70,10 @@ class TagFilter extends CollectionFilter { String get label => tag; @override - IconData get icon => OMIcons.localOffer; + Widget iconBuilder(context) => Icon(OMIcons.localOffer); + + @override + String get typeKey => type; @override bool operator ==(Object other) { @@ -62,6 +86,8 @@ class TagFilter extends CollectionFilter { } class CountryFilter extends CollectionFilter { + static const type = 'country'; + final String country; const CountryFilter(this.country); @@ -73,7 +99,10 @@ class CountryFilter extends CollectionFilter { String get label => country; @override - IconData get icon => OMIcons.place; + Widget iconBuilder(context) => Icon(OMIcons.place); + + @override + String get typeKey => type; @override bool operator ==(Object other) { @@ -86,12 +115,20 @@ class CountryFilter extends CollectionFilter { } class VideoFilter extends CollectionFilter { + static const type = 'video'; + @override bool filter(ImageEntry entry) => entry.isVideo; @override String get label => 'Video'; + @override + Widget iconBuilder(context) => Icon(OMIcons.movie); + + @override + String get typeKey => type; + @override bool operator ==(Object other) { if (other.runtimeType != runtimeType) return false; @@ -103,12 +140,20 @@ class VideoFilter extends CollectionFilter { } class GifFilter extends CollectionFilter { + static const type = 'gif'; + @override bool filter(ImageEntry entry) => entry.isGif; @override String get label => 'GIF'; + @override + Widget iconBuilder(context) => Icon(OMIcons.gif); + + @override + String get typeKey => type; + @override bool operator ==(Object other) { if (other.runtimeType != runtimeType) return false; @@ -119,21 +164,29 @@ class GifFilter extends CollectionFilter { int get hashCode => 'GifFilter'.hashCode; } -class MetadataFilter extends CollectionFilter { +class QueryFilter extends CollectionFilter { + static const type = 'query'; + final String query; - const MetadataFilter(this.query); + const QueryFilter(this.query); @override bool filter(ImageEntry entry) => entry.search(query); @override - String get label => '"${query}"'; + String get label => '${query}'; + + @override + Widget iconBuilder(context) => Icon(OMIcons.formatQuote); + + @override + String get typeKey => type; @override bool operator ==(Object other) { if (other.runtimeType != runtimeType) return false; - return other is MetadataFilter && other.query == query; + return other is QueryFilter && other.query == query; } @override diff --git a/lib/widgets/album/filter_bar.dart b/lib/widgets/album/filter_bar.dart index a3b3497e6..11f776e4c 100644 --- a/lib/widgets/album/filter_bar.dart +++ b/lib/widgets/album/filter_bar.dart @@ -16,7 +16,8 @@ class FilterBar extends StatelessWidget implements PreferredSizeWidget { FilterBar(Set filters) : this.filters = filters.toList() ..sort((a, b) { - return compareAsciiUpperCase(a.label, b.label); + final c = a.displayPriority.compareTo(b.displayPriority); + return c != 0 ? c : compareAsciiUpperCase(a.label, b.label); }); @override @@ -40,7 +41,7 @@ class FilterBar extends StatelessWidget implements PreferredSizeWidget { itemBuilder: (context, index) { if (index >= filters.length) return null; final filter = filters[index]; - return AvesFilterChip.fromFilter( + return AvesFilterChip( filter, onPressed: (filter) {}, ); diff --git a/lib/widgets/album/search_delegate.dart b/lib/widgets/album/search_delegate.dart index 6990b3f04..d955ba36d 100644 --- a/lib/widgets/album/search_delegate.dart +++ b/lib/widgets/album/search_delegate.dart @@ -59,7 +59,7 @@ class ImageSearchDelegate extends SearchDelegate { child: ChangeNotifierProvider.value( value: CollectionLens( source: collection.source, - filters: [MetadataFilter(query.toLowerCase())], + filters: [QueryFilter(query.toLowerCase())], groupFactor: collection.groupFactor, sortFactor: collection.sortFactor, ), diff --git a/lib/widgets/common/aves_filter_chip.dart b/lib/widgets/common/aves_filter_chip.dart index 531b08148..f07f65a22 100644 --- a/lib/widgets/common/aves_filter_chip.dart +++ b/lib/widgets/common/aves_filter_chip.dart @@ -5,37 +5,28 @@ import 'package:flutter/material.dart'; typedef FilterCallback = void Function(CollectionFilter filter); class AvesFilterChip extends StatelessWidget { - final String label; - final IconData icon; - final VoidCallback onPressed; + final CollectionFilter filter; + final FilterCallback onPressed; - const AvesFilterChip({ - this.icon, - @required this.label, - @required this.onPressed, - }); - - factory AvesFilterChip.fromFilter( - CollectionFilter filter, { - @required FilterCallback onPressed, - }) => - AvesFilterChip( - icon: filter.icon, - label: filter.label, - onPressed: onPressed != null ? () => onPressed(filter) : null, - ); + String get label => filter.label; static const double buttonBorderWidth = 2; static const double maxChipWidth = 160; + const AvesFilterChip( + this.filter, { + @required this.onPressed, + }); + @override Widget build(BuildContext context) { + final icon = filter.iconBuilder(context); return ConstrainedBox( constraints: const BoxConstraints(maxWidth: maxChipWidth), child: Tooltip( message: label, child: OutlineButton( - onPressed: onPressed, + onPressed: onPressed != null ? () => onPressed(filter) : null, borderSide: BorderSide( color: stringToColor(label), width: buttonBorderWidth, @@ -47,7 +38,7 @@ class AvesFilterChip extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ if (icon != null) ...[ - Icon(icon), + icon, const SizedBox(width: 8), ], Flexible( diff --git a/lib/widgets/fullscreen/info/basic_section.dart b/lib/widgets/fullscreen/info/basic_section.dart index 79ce79449..1ff422f1e 100644 --- a/lib/widgets/fullscreen/info/basic_section.dart +++ b/lib/widgets/fullscreen/info/basic_section.dart @@ -3,6 +3,7 @@ import 'package:aves/model/image_entry.dart'; import 'package:aves/utils/file_utils.dart'; import 'package:aves/widgets/common/aves_filter_chip.dart'; import 'package:aves/widgets/fullscreen/info/info_page.dart'; +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; @@ -23,7 +24,12 @@ class BasicSection extends StatelessWidget { final showMegaPixels = !entry.isVideo && !entry.isGif && entry.megaPixels != null && entry.megaPixels > 0; final resolutionText = '${entry.width ?? '?'} × ${entry.height ?? '?'}${showMegaPixels ? ' (${entry.megaPixels} MP)' : ''}'; - final filter = entry.directory != null ? AlbumFilter(entry.directory) : null; + final tags = entry.xmpSubjects..sort(compareAsciiUpperCase); + final filters = [ + if (entry.directory != null) AlbumFilter(entry.directory), + ...tags.map((tag) => TagFilter(tag)), + ]; + return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -36,13 +42,19 @@ class BasicSection extends StatelessWidget { 'URI': entry.uri ?? '?', if (entry.path != null) 'Path': entry.path, }), - if (filter != null) ...[ - const SizedBox(height: 8), - AvesFilterChip.fromFilter( - filter, - onPressed: onFilter, + if (filters.isNotEmpty != null) + Padding( + padding: const EdgeInsets.symmetric(horizontal: AvesFilterChip.buttonBorderWidth / 2) + const EdgeInsets.only(top: 8), + child: Wrap( + spacing: 8, + children: filters + .map((filter) => AvesFilterChip( + filter, + onPressed: onFilter, + )) + .toList(), + ), ), - ] ], ); } diff --git a/lib/widgets/fullscreen/info/info_page.dart b/lib/widgets/fullscreen/info/info_page.dart index 11f8cf0e6..921507c26 100644 --- a/lib/widgets/fullscreen/info/info_page.dart +++ b/lib/widgets/fullscreen/info/info_page.dart @@ -7,7 +7,6 @@ import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; import 'package:aves/widgets/fullscreen/info/basic_section.dart'; import 'package:aves/widgets/fullscreen/info/location_section.dart'; import 'package:aves/widgets/fullscreen/info/metadata_section.dart'; -import 'package:aves/widgets/fullscreen/info/xmp_section.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:outline_material_icons/outline_material_icons.dart'; @@ -97,11 +96,6 @@ class InfoPageState extends State { ], ), ); - final tagSliver = XmpTagSectionSliver( - collection: collection, - entry: entry, - onFilter: _goToFilteredCollection, - ); final metadataSliver = MetadataSectionSliver( entry: entry, visibleNotifier: widget.visibleNotifier, @@ -115,10 +109,6 @@ class InfoPageState extends State { padding: horizontalPadding + const EdgeInsets.only(top: 8), sliver: basicAndLocationSliver, ), - SliverPadding( - padding: horizontalPadding, - sliver: tagSliver, - ), SliverPadding( padding: horizontalPadding + EdgeInsets.only(bottom: 8 + mqViewInsetsBottom), sliver: metadataSliver, diff --git a/lib/widgets/fullscreen/info/location_section.dart b/lib/widgets/fullscreen/info/location_section.dart index f9cae21b5..ff2f707a1 100644 --- a/lib/widgets/fullscreen/info/location_section.dart +++ b/lib/widgets/fullscreen/info/location_section.dart @@ -112,7 +112,7 @@ class _LocationSectionState extends State { child: Wrap( spacing: 8, children: filters - .map((filter) => AvesFilterChip.fromFilter( + .map((filter) => AvesFilterChip( filter, onPressed: widget.onFilter, )) diff --git a/lib/widgets/fullscreen/info/xmp_section.dart b/lib/widgets/fullscreen/info/xmp_section.dart deleted file mode 100644 index 4c6116093..000000000 --- a/lib/widgets/fullscreen/info/xmp_section.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'package:aves/model/collection_filters.dart'; -import 'package:aves/model/collection_lens.dart'; -import 'package:aves/model/image_entry.dart'; -import 'package:aves/widgets/common/aves_filter_chip.dart'; -import 'package:aves/widgets/fullscreen/info/info_page.dart'; -import 'package:collection/collection.dart'; -import 'package:flutter/material.dart'; -import 'package:outline_material_icons/outline_material_icons.dart'; - -class XmpTagSectionSliver extends AnimatedWidget { - final CollectionLens collection; - final ImageEntry entry; - final FilterCallback onFilter; - - XmpTagSectionSliver({ - Key key, - @required this.collection, - @required this.entry, - @required this.onFilter, - }) : super(key: key, listenable: entry.metadataChangeNotifier); - - @override - Widget build(BuildContext context) { - final tags = entry.xmpSubjects..sort(compareAsciiUpperCase); - return SliverList( - delegate: SliverChildListDelegate.fixed( - tags.isEmpty - ? [] - : [ - const SectionRow(OMIcons.localOffer), - Padding( - padding: const EdgeInsets.symmetric(horizontal: AvesFilterChip.buttonBorderWidth / 2), - child: Wrap( - spacing: 8, - children: tags - .map((tag) => TagFilter(tag)) - .map((filter) => AvesFilterChip.fromFilter( - filter, - onPressed: onFilter, - )) - .toList(), - ), - ), - ], - ), - ); - } -}