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/model/image_entry.dart';
|
||||||
|
import 'package:aves/widgets/common/icons.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:outline_material_icons/outline_material_icons.dart';
|
import 'package:outline_material_icons/outline_material_icons.dart';
|
||||||
import 'package:path/path.dart';
|
import 'package:path/path.dart';
|
||||||
|
|
||||||
abstract class CollectionFilter {
|
abstract class CollectionFilter {
|
||||||
|
static const List<String> collectionFilterOrder = [
|
||||||
|
VideoFilter.type,
|
||||||
|
GifFilter.type,
|
||||||
|
AlbumFilter.type,
|
||||||
|
CountryFilter.type,
|
||||||
|
TagFilter.type,
|
||||||
|
QueryFilter.type,
|
||||||
|
];
|
||||||
|
|
||||||
const CollectionFilter();
|
const CollectionFilter();
|
||||||
|
|
||||||
bool filter(ImageEntry entry);
|
bool filter(ImageEntry entry);
|
||||||
|
|
||||||
String get label;
|
String get label;
|
||||||
|
|
||||||
IconData get icon => null;
|
Widget iconBuilder(BuildContext context);
|
||||||
|
|
||||||
|
String get typeKey;
|
||||||
|
|
||||||
|
int get displayPriority => collectionFilterOrder.indexOf(typeKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
class AlbumFilter extends CollectionFilter {
|
class AlbumFilter extends CollectionFilter {
|
||||||
|
static const type = 'album';
|
||||||
|
|
||||||
final String album;
|
final String album;
|
||||||
|
|
||||||
const AlbumFilter(this.album);
|
const AlbumFilter(this.album);
|
||||||
|
@ -25,7 +41,10 @@ class AlbumFilter extends CollectionFilter {
|
||||||
String get label => album.split(separator).last;
|
String get label => album.split(separator).last;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
IconData get icon => OMIcons.photoAlbum;
|
Widget iconBuilder(context) => IconUtils.getAlbumIcon(context, album) ?? Icon(OMIcons.photoAlbum);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get typeKey => type;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
|
@ -38,6 +57,8 @@ class AlbumFilter extends CollectionFilter {
|
||||||
}
|
}
|
||||||
|
|
||||||
class TagFilter extends CollectionFilter {
|
class TagFilter extends CollectionFilter {
|
||||||
|
static const type = 'tag';
|
||||||
|
|
||||||
final String tag;
|
final String tag;
|
||||||
|
|
||||||
const TagFilter(this.tag);
|
const TagFilter(this.tag);
|
||||||
|
@ -49,7 +70,10 @@ class TagFilter extends CollectionFilter {
|
||||||
String get label => tag;
|
String get label => tag;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
IconData get icon => OMIcons.localOffer;
|
Widget iconBuilder(context) => Icon(OMIcons.localOffer);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get typeKey => type;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
|
@ -62,6 +86,8 @@ class TagFilter extends CollectionFilter {
|
||||||
}
|
}
|
||||||
|
|
||||||
class CountryFilter extends CollectionFilter {
|
class CountryFilter extends CollectionFilter {
|
||||||
|
static const type = 'country';
|
||||||
|
|
||||||
final String country;
|
final String country;
|
||||||
|
|
||||||
const CountryFilter(this.country);
|
const CountryFilter(this.country);
|
||||||
|
@ -73,7 +99,10 @@ class CountryFilter extends CollectionFilter {
|
||||||
String get label => country;
|
String get label => country;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
IconData get icon => OMIcons.place;
|
Widget iconBuilder(context) => Icon(OMIcons.place);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get typeKey => type;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
|
@ -86,12 +115,20 @@ class CountryFilter extends CollectionFilter {
|
||||||
}
|
}
|
||||||
|
|
||||||
class VideoFilter extends CollectionFilter {
|
class VideoFilter extends CollectionFilter {
|
||||||
|
static const type = 'video';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool filter(ImageEntry entry) => entry.isVideo;
|
bool filter(ImageEntry entry) => entry.isVideo;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get label => 'Video';
|
String get label => 'Video';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget iconBuilder(context) => Icon(OMIcons.movie);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get typeKey => type;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
if (other.runtimeType != runtimeType) return false;
|
if (other.runtimeType != runtimeType) return false;
|
||||||
|
@ -103,12 +140,20 @@ class VideoFilter extends CollectionFilter {
|
||||||
}
|
}
|
||||||
|
|
||||||
class GifFilter extends CollectionFilter {
|
class GifFilter extends CollectionFilter {
|
||||||
|
static const type = 'gif';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool filter(ImageEntry entry) => entry.isGif;
|
bool filter(ImageEntry entry) => entry.isGif;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get label => 'GIF';
|
String get label => 'GIF';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget iconBuilder(context) => Icon(OMIcons.gif);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get typeKey => type;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
if (other.runtimeType != runtimeType) return false;
|
if (other.runtimeType != runtimeType) return false;
|
||||||
|
@ -119,21 +164,29 @@ class GifFilter extends CollectionFilter {
|
||||||
int get hashCode => 'GifFilter'.hashCode;
|
int get hashCode => 'GifFilter'.hashCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
class MetadataFilter extends CollectionFilter {
|
class QueryFilter extends CollectionFilter {
|
||||||
|
static const type = 'query';
|
||||||
|
|
||||||
final String query;
|
final String query;
|
||||||
|
|
||||||
const MetadataFilter(this.query);
|
const QueryFilter(this.query);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool filter(ImageEntry entry) => entry.search(query);
|
bool filter(ImageEntry entry) => entry.search(query);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get label => '"${query}"';
|
String get label => '${query}';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget iconBuilder(context) => Icon(OMIcons.formatQuote);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get typeKey => type;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
if (other.runtimeType != runtimeType) return false;
|
if (other.runtimeType != runtimeType) return false;
|
||||||
return other is MetadataFilter && other.query == query;
|
return other is QueryFilter && other.query == query;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -16,7 +16,8 @@ class FilterBar extends StatelessWidget implements PreferredSizeWidget {
|
||||||
FilterBar(Set<CollectionFilter> filters)
|
FilterBar(Set<CollectionFilter> filters)
|
||||||
: this.filters = filters.toList()
|
: this.filters = filters.toList()
|
||||||
..sort((a, b) {
|
..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
|
@override
|
||||||
|
@ -40,7 +41,7 @@ class FilterBar extends StatelessWidget implements PreferredSizeWidget {
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
if (index >= filters.length) return null;
|
if (index >= filters.length) return null;
|
||||||
final filter = filters[index];
|
final filter = filters[index];
|
||||||
return AvesFilterChip.fromFilter(
|
return AvesFilterChip(
|
||||||
filter,
|
filter,
|
||||||
onPressed: (filter) {},
|
onPressed: (filter) {},
|
||||||
);
|
);
|
||||||
|
|
|
@ -59,7 +59,7 @@ class ImageSearchDelegate extends SearchDelegate<ImageEntry> {
|
||||||
child: ChangeNotifierProvider<CollectionLens>.value(
|
child: ChangeNotifierProvider<CollectionLens>.value(
|
||||||
value: CollectionLens(
|
value: CollectionLens(
|
||||||
source: collection.source,
|
source: collection.source,
|
||||||
filters: [MetadataFilter(query.toLowerCase())],
|
filters: [QueryFilter(query.toLowerCase())],
|
||||||
groupFactor: collection.groupFactor,
|
groupFactor: collection.groupFactor,
|
||||||
sortFactor: collection.sortFactor,
|
sortFactor: collection.sortFactor,
|
||||||
),
|
),
|
||||||
|
|
|
@ -5,37 +5,28 @@ import 'package:flutter/material.dart';
|
||||||
typedef FilterCallback = void Function(CollectionFilter filter);
|
typedef FilterCallback = void Function(CollectionFilter filter);
|
||||||
|
|
||||||
class AvesFilterChip extends StatelessWidget {
|
class AvesFilterChip extends StatelessWidget {
|
||||||
final String label;
|
final CollectionFilter filter;
|
||||||
final IconData icon;
|
final FilterCallback onPressed;
|
||||||
final VoidCallback onPressed;
|
|
||||||
|
|
||||||
const AvesFilterChip({
|
String get label => filter.label;
|
||||||
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,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const double buttonBorderWidth = 2;
|
static const double buttonBorderWidth = 2;
|
||||||
static const double maxChipWidth = 160;
|
static const double maxChipWidth = 160;
|
||||||
|
|
||||||
|
const AvesFilterChip(
|
||||||
|
this.filter, {
|
||||||
|
@required this.onPressed,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final icon = filter.iconBuilder(context);
|
||||||
return ConstrainedBox(
|
return ConstrainedBox(
|
||||||
constraints: const BoxConstraints(maxWidth: maxChipWidth),
|
constraints: const BoxConstraints(maxWidth: maxChipWidth),
|
||||||
child: Tooltip(
|
child: Tooltip(
|
||||||
message: label,
|
message: label,
|
||||||
child: OutlineButton(
|
child: OutlineButton(
|
||||||
onPressed: onPressed,
|
onPressed: onPressed != null ? () => onPressed(filter) : null,
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(
|
||||||
color: stringToColor(label),
|
color: stringToColor(label),
|
||||||
width: buttonBorderWidth,
|
width: buttonBorderWidth,
|
||||||
|
@ -47,7 +38,7 @@ class AvesFilterChip extends StatelessWidget {
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
if (icon != null) ...[
|
if (icon != null) ...[
|
||||||
Icon(icon),
|
icon,
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
],
|
],
|
||||||
Flexible(
|
Flexible(
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'package:aves/model/image_entry.dart';
|
||||||
import 'package:aves/utils/file_utils.dart';
|
import 'package:aves/utils/file_utils.dart';
|
||||||
import 'package:aves/widgets/common/aves_filter_chip.dart';
|
import 'package:aves/widgets/common/aves_filter_chip.dart';
|
||||||
import 'package:aves/widgets/fullscreen/info/info_page.dart';
|
import 'package:aves/widgets/fullscreen/info/info_page.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.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 showMegaPixels = !entry.isVideo && !entry.isGif && entry.megaPixels != null && entry.megaPixels > 0;
|
||||||
final resolutionText = '${entry.width ?? '?'} × ${entry.height ?? '?'}${showMegaPixels ? ' (${entry.megaPixels} MP)' : ''}';
|
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(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
@ -36,13 +42,19 @@ class BasicSection extends StatelessWidget {
|
||||||
'URI': entry.uri ?? '?',
|
'URI': entry.uri ?? '?',
|
||||||
if (entry.path != null) 'Path': entry.path,
|
if (entry.path != null) 'Path': entry.path,
|
||||||
}),
|
}),
|
||||||
if (filter != null) ...[
|
if (filters.isNotEmpty != null)
|
||||||
const SizedBox(height: 8),
|
Padding(
|
||||||
AvesFilterChip.fromFilter(
|
padding: const EdgeInsets.symmetric(horizontal: AvesFilterChip.buttonBorderWidth / 2) + const EdgeInsets.only(top: 8),
|
||||||
|
child: Wrap(
|
||||||
|
spacing: 8,
|
||||||
|
children: filters
|
||||||
|
.map((filter) => AvesFilterChip(
|
||||||
filter,
|
filter,
|
||||||
onPressed: onFilter,
|
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/basic_section.dart';
|
||||||
import 'package:aves/widgets/fullscreen/info/location_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/metadata_section.dart';
|
||||||
import 'package:aves/widgets/fullscreen/info/xmp_section.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
import 'package:outline_material_icons/outline_material_icons.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(
|
final metadataSliver = MetadataSectionSliver(
|
||||||
entry: entry,
|
entry: entry,
|
||||||
visibleNotifier: widget.visibleNotifier,
|
visibleNotifier: widget.visibleNotifier,
|
||||||
|
@ -115,10 +109,6 @@ class InfoPageState extends State<InfoPage> {
|
||||||
padding: horizontalPadding + const EdgeInsets.only(top: 8),
|
padding: horizontalPadding + const EdgeInsets.only(top: 8),
|
||||||
sliver: basicAndLocationSliver,
|
sliver: basicAndLocationSliver,
|
||||||
),
|
),
|
||||||
SliverPadding(
|
|
||||||
padding: horizontalPadding,
|
|
||||||
sliver: tagSliver,
|
|
||||||
),
|
|
||||||
SliverPadding(
|
SliverPadding(
|
||||||
padding: horizontalPadding + EdgeInsets.only(bottom: 8 + mqViewInsetsBottom),
|
padding: horizontalPadding + EdgeInsets.only(bottom: 8 + mqViewInsetsBottom),
|
||||||
sliver: metadataSliver,
|
sliver: metadataSliver,
|
||||||
|
|
|
@ -112,7 +112,7 @@ class _LocationSectionState extends State<LocationSection> {
|
||||||
child: Wrap(
|
child: Wrap(
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
children: filters
|
children: filters
|
||||||
.map((filter) => AvesFilterChip.fromFilter(
|
.map((filter) => AvesFilterChip(
|
||||||
filter,
|
filter,
|
||||||
onPressed: widget.onFilter,
|
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