info: moved tag filters to basic section

This commit is contained in:
Thibault Deckers 2020-03-26 19:15:34 +09:00
parent 0199f9bd22
commit 4c23a0f5ad
8 changed files with 96 additions and 97 deletions

View file

@ -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<String> 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

View file

@ -16,7 +16,8 @@ class FilterBar extends StatelessWidget implements PreferredSizeWidget {
FilterBar(Set<CollectionFilter> 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) {},
);

View file

@ -59,7 +59,7 @@ class ImageSearchDelegate extends SearchDelegate<ImageEntry> {
child: ChangeNotifierProvider<CollectionLens>.value(
value: CollectionLens(
source: collection.source,
filters: [MetadataFilter(query.toLowerCase())],
filters: [QueryFilter(query.toLowerCase())],
groupFactor: collection.groupFactor,
sortFactor: collection.sortFactor,
),

View file

@ -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(

View file

@ -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(),
),
),
]
],
);
}

View file

@ -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<InfoPage> {
],
),
);
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<InfoPage> {
padding: horizontalPadding + const EdgeInsets.only(top: 8),
sliver: basicAndLocationSliver,
),
SliverPadding(
padding: horizontalPadding,
sliver: tagSliver,
),
SliverPadding(
padding: horizontalPadding + EdgeInsets.only(bottom: 8 + mqViewInsetsBottom),
sliver: metadataSliver,

View file

@ -112,7 +112,7 @@ class _LocationSectionState extends State<LocationSection> {
child: Wrap(
spacing: 8,
children: filters
.map((filter) => AvesFilterChip.fromFilter(
.map((filter) => AvesFilterChip(
filter,
onPressed: widget.onFilter,
))

View file

@ -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(),
),
),
],
),
);
}
}