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: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 {
|
abstract class CollectionFilter {
|
||||||
const CollectionFilter();
|
const CollectionFilter();
|
||||||
|
|
||||||
bool filter(ImageEntry entry);
|
bool filter(ImageEntry entry);
|
||||||
|
|
||||||
|
String get label;
|
||||||
|
|
||||||
|
IconData get icon => null;
|
||||||
}
|
}
|
||||||
|
|
||||||
class AlbumFilter extends CollectionFilter {
|
class AlbumFilter extends CollectionFilter {
|
||||||
|
@ -13,6 +20,21 @@ class AlbumFilter extends CollectionFilter {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool filter(ImageEntry entry) => entry.directory == album;
|
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 {
|
class TagFilter extends CollectionFilter {
|
||||||
|
@ -22,6 +44,21 @@ class TagFilter extends CollectionFilter {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool filter(ImageEntry entry) => entry.xmpSubjects.contains(tag);
|
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 {
|
class CountryFilter extends CollectionFilter {
|
||||||
|
@ -31,23 +68,74 @@ class CountryFilter extends CollectionFilter {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool filter(ImageEntry entry) => entry.isLocated && entry.addressDetails.countryName == country;
|
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 {
|
class VideoFilter extends CollectionFilter {
|
||||||
@override
|
@override
|
||||||
bool filter(ImageEntry entry) => entry.isVideo;
|
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 {
|
class GifFilter extends CollectionFilter {
|
||||||
@override
|
@override
|
||||||
bool filter(ImageEntry entry) => entry.isGif;
|
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 {
|
class MetadataFilter extends CollectionFilter {
|
||||||
final String value;
|
final String query;
|
||||||
|
|
||||||
const MetadataFilter(this.value);
|
const MetadataFilter(this.query);
|
||||||
|
|
||||||
@override
|
@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 {
|
class CollectionLens with ChangeNotifier {
|
||||||
final CollectionSource source;
|
final CollectionSource source;
|
||||||
final List<CollectionFilter> filters;
|
final Set<CollectionFilter> filters;
|
||||||
GroupFactor groupFactor;
|
GroupFactor groupFactor;
|
||||||
SortFactor sortFactor;
|
SortFactor sortFactor;
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ class CollectionLens with ChangeNotifier {
|
||||||
List<CollectionFilter> filters,
|
List<CollectionFilter> filters,
|
||||||
GroupFactor groupFactor,
|
GroupFactor groupFactor,
|
||||||
SortFactor sortFactor,
|
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.groupFactor = groupFactor ?? GroupFactor.month,
|
||||||
this.sortFactor = sortFactor ?? SortFactor.date {
|
this.sortFactor = sortFactor ?? SortFactor.date {
|
||||||
_subscriptions.add(source.eventBus.on<EntryAddedEvent>().listen((e) => onEntryAdded()));
|
_subscriptions.add(source.eventBus.on<EntryAddedEvent>().listen((e) => onEntryAdded()));
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:aves/model/collection_lens.dart';
|
import 'package:aves/model/collection_lens.dart';
|
||||||
import 'package:aves/widgets/album/all_collection_app_bar.dart';
|
import 'package:aves/widgets/album/all_collection_app_bar.dart';
|
||||||
import 'package:aves/widgets/album/collection_drawer.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/album/thumbnail_collection.dart';
|
||||||
import 'package:aves/widgets/common/menu_row.dart';
|
import 'package:aves/widgets/common/menu_row.dart';
|
||||||
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||||
|
@ -31,6 +32,7 @@ class CollectionPage extends StatelessWidget {
|
||||||
: SliverAppBar(
|
: SliverAppBar(
|
||||||
title: Text(title),
|
title: Text(title),
|
||||||
actions: _buildActions(),
|
actions: _buildActions(),
|
||||||
|
bottom: FilterBar(collection.filters),
|
||||||
floating: true,
|
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 Widget appBar;
|
||||||
final WidgetBuilder emptyBuilder;
|
final WidgetBuilder emptyBuilder;
|
||||||
|
|
||||||
final ScrollController _scrollController = ScrollController();
|
|
||||||
final ValueNotifier<int> _columnCountNotifier = ValueNotifier(4);
|
final ValueNotifier<int> _columnCountNotifier = ValueNotifier(4);
|
||||||
final GlobalKey _scrollableKey = GlobalKey();
|
final GlobalKey _scrollableKey = GlobalKey();
|
||||||
|
|
||||||
|
@ -48,7 +47,7 @@ class ThumbnailCollection extends StatelessWidget {
|
||||||
builder: (context, columnCount, child) {
|
builder: (context, columnCount, child) {
|
||||||
final scrollView = CustomScrollView(
|
final scrollView = CustomScrollView(
|
||||||
key: _scrollableKey,
|
key: _scrollableKey,
|
||||||
controller: _scrollController,
|
primary: true,
|
||||||
slivers: [
|
slivers: [
|
||||||
if (appBar != null) appBar,
|
if (appBar != null) appBar,
|
||||||
if (collection.isEmpty && emptyBuilder != null)
|
if (collection.isEmpty && emptyBuilder != null)
|
||||||
|
@ -77,7 +76,7 @@ class ThumbnailCollection extends StatelessWidget {
|
||||||
heightScrollThumb: avesScrollThumbHeight,
|
heightScrollThumb: avesScrollThumbHeight,
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
scrollThumbBuilder: avesScrollThumbBuilder(),
|
scrollThumbBuilder: avesScrollThumbBuilder(),
|
||||||
controller: _scrollController,
|
controller: PrimaryScrollController.of(context),
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(
|
||||||
// padding to get scroll thumb below app bar, above nav bar
|
// padding to get scroll thumb below app bar, above nav bar
|
||||||
top: topPadding,
|
top: topPadding,
|
||||||
|
|
Loading…
Reference in a new issue