info: moved tag filters to basic section
This commit is contained in:
parent
0199f9bd22
commit
4c23a0f5ad
8 changed files with 96 additions and 97 deletions
|
@ -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
|
||||
|
|
|
@ -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) {},
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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(),
|
||||
),
|
||||
),
|
||||
]
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
))
|
||||
|
|
|
@ -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(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue