added filters to app bar

This commit is contained in:
Thibault Deckers 2020-03-26 12:36:02 +09:00
parent 4ea985b8f8
commit 8a014888a6
5 changed files with 181 additions and 8 deletions

View file

@ -1,9 +1,16 @@
import 'package:aves/model/image_entry.dart';
import 'package:flutter/widgets.dart';
import 'package:outline_material_icons/outline_material_icons.dart';
import 'package:path/path.dart';
abstract class CollectionFilter {
const CollectionFilter();
bool filter(ImageEntry entry);
String get label;
IconData get icon => null;
}
class AlbumFilter extends CollectionFilter {
@ -13,6 +20,21 @@ class AlbumFilter extends CollectionFilter {
@override
bool filter(ImageEntry entry) => entry.directory == album;
@override
String get label => album.split(separator).last;
@override
IconData get icon => OMIcons.photoAlbum;
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) return false;
return other is AlbumFilter && other.album == album;
}
@override
int get hashCode => hashValues('AlbumFilter', album);
}
class TagFilter extends CollectionFilter {
@ -22,6 +44,21 @@ class TagFilter extends CollectionFilter {
@override
bool filter(ImageEntry entry) => entry.xmpSubjects.contains(tag);
@override
String get label => tag;
@override
IconData get icon => OMIcons.localOffer;
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) return false;
return other is TagFilter && other.tag == tag;
}
@override
int get hashCode => hashValues('TagFilter', tag);
}
class CountryFilter extends CollectionFilter {
@ -31,23 +68,74 @@ class CountryFilter extends CollectionFilter {
@override
bool filter(ImageEntry entry) => entry.isLocated && entry.addressDetails.countryName == country;
@override
String get label => country;
@override
IconData get icon => OMIcons.place;
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) return false;
return other is CountryFilter && other.country == country;
}
@override
int get hashCode => hashValues('CountryFilter', country);
}
class VideoFilter extends CollectionFilter {
@override
bool filter(ImageEntry entry) => entry.isVideo;
@override
String get label => 'Video';
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) return false;
return other is VideoFilter;
}
@override
int get hashCode => 'VideoFilter'.hashCode;
}
class GifFilter extends CollectionFilter {
@override
bool filter(ImageEntry entry) => entry.isGif;
@override
String get label => 'GIF';
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) return false;
return other is GifFilter;
}
@override
int get hashCode => 'GifFilter'.hashCode;
}
class MetadataFilter extends CollectionFilter {
final String value;
final String query;
const MetadataFilter(this.value);
const MetadataFilter(this.query);
@override
bool filter(ImageEntry entry) => entry.search(value);
bool filter(ImageEntry entry) => entry.search(query);
@override
String get label => '"${query}"';
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) return false;
return other is MetadataFilter && other.query == query;
}
@override
int get hashCode => hashValues('MetadataFilter', query);
}

View file

@ -9,7 +9,7 @@ import 'package:flutter/foundation.dart';
class CollectionLens with ChangeNotifier {
final CollectionSource source;
final List<CollectionFilter> filters;
final Set<CollectionFilter> filters;
GroupFactor groupFactor;
SortFactor sortFactor;
@ -23,7 +23,7 @@ class CollectionLens with ChangeNotifier {
List<CollectionFilter> filters,
GroupFactor groupFactor,
SortFactor sortFactor,
}) : this.filters = [if (filters != null) ...filters.where((f) => f != null)],
}) : this.filters = [if (filters != null) ...filters.where((f) => f != null)].toSet(),
this.groupFactor = groupFactor ?? GroupFactor.month,
this.sortFactor = sortFactor ?? SortFactor.date {
_subscriptions.add(source.eventBus.on<EntryAddedEvent>().listen((e) => onEntryAdded()));

View file

@ -1,6 +1,7 @@
import 'package:aves/model/collection_lens.dart';
import 'package:aves/widgets/album/all_collection_app_bar.dart';
import 'package:aves/widgets/album/collection_drawer.dart';
import 'package:aves/widgets/album/filter_bar.dart';
import 'package:aves/widgets/album/thumbnail_collection.dart';
import 'package:aves/widgets/common/menu_row.dart';
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
@ -31,6 +32,7 @@ class CollectionPage extends StatelessWidget {
: SliverAppBar(
title: Text(title),
actions: _buildActions(),
bottom: FilterBar(collection.filters),
floating: true,
),
),

View file

@ -0,0 +1,84 @@
import 'package:aves/model/collection_filters.dart';
import 'package:aves/utils/color_utils.dart';
import 'package:aves/widgets/fullscreen/info/navigation_button.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
class FilterBar extends StatelessWidget implements PreferredSizeWidget {
final List<CollectionFilter> filters;
final ScrollController _scrollController = ScrollController();
static const double maxChipWidth = 160;
static const EdgeInsets padding = EdgeInsets.only(left: 8, right: 8, bottom: 8);
@override
final Size preferredSize = Size.fromHeight(kMinInteractiveDimension + padding.vertical);
FilterBar(Set<CollectionFilter> filters)
: this.filters = filters.toList()
..sort((a, b) {
return compareAsciiUpperCase(a.label, b.label);
});
@override
Widget build(BuildContext context) {
return Container(
// specify transparent as a workaround to prevent
// chip border clipping when the floating app bar is fading
color: Colors.transparent,
padding: padding,
height: preferredSize.height,
child: NotificationListener<ScrollNotification>(
// cancel notification bubbling so that the draggable scrollbar
// does not misinterpret filter bar scrolling for collection scrolling
onNotification: (notification) => true,
child: ListView.separated(
scrollDirection: Axis.horizontal,
controller: _scrollController,
primary: false,
physics: const BouncingScrollPhysics(),
padding: EdgeInsets.all(NavigationButton.buttonBorderWidth / 2),
itemBuilder: (context, index) {
if (index >= filters.length) return null;
final filter = filters[index];
final icon = filter.icon;
final label = filter.label;
return ConstrainedBox(
constraints: const BoxConstraints(maxWidth: maxChipWidth),
child: OutlineButton(
onPressed: () {},
borderSide: BorderSide(
color: stringToColor(label),
width: NavigationButton.buttonBorderWidth,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(42),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (icon != null) ...[
Icon(icon),
const SizedBox(width: 8),
],
Flexible(
child: Text(
label,
softWrap: false,
overflow: TextOverflow.fade,
maxLines: 1,
),
),
],
),
),
);
},
separatorBuilder: (context, index) => const SizedBox(width: 8),
itemCount: filters.length,
),
),
);
}
}

View file

@ -10,7 +10,6 @@ class ThumbnailCollection extends StatelessWidget {
final Widget appBar;
final WidgetBuilder emptyBuilder;
final ScrollController _scrollController = ScrollController();
final ValueNotifier<int> _columnCountNotifier = ValueNotifier(4);
final GlobalKey _scrollableKey = GlobalKey();
@ -48,7 +47,7 @@ class ThumbnailCollection extends StatelessWidget {
builder: (context, columnCount, child) {
final scrollView = CustomScrollView(
key: _scrollableKey,
controller: _scrollController,
primary: true,
slivers: [
if (appBar != null) appBar,
if (collection.isEmpty && emptyBuilder != null)
@ -77,7 +76,7 @@ class ThumbnailCollection extends StatelessWidget {
heightScrollThumb: avesScrollThumbHeight,
backgroundColor: Colors.white,
scrollThumbBuilder: avesScrollThumbBuilder(),
controller: _scrollController,
controller: PrimaryScrollController.of(context),
padding: EdgeInsets.only(
// padding to get scroll thumb below app bar, above nav bar
top: topPadding,