added filters to app bar
This commit is contained in:
parent
4ea985b8f8
commit
8a014888a6
5 changed files with 181 additions and 8 deletions
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
|
|
84
lib/widgets/album/filter_bar.dart
Normal file
84
lib/widgets/album/filter_bar.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue