From 0199f9bd22a60abc8ab5d0bf6f4a5ec330297720 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Thu, 26 Mar 2020 18:16:21 +0900 Subject: [PATCH] info: added album filter chip --- lib/main.dart | 4 +- lib/model/collection_lens.dart | 39 +++++------ lib/widgets/album/collection_drawer.dart | 11 ++- lib/widgets/album/collection_page.dart | 9 +-- lib/widgets/album/filter_bar.dart | 43 ++---------- lib/widgets/common/aves_filter_chip.dart | 67 +++++++++++++++++++ .../fullscreen/info/basic_section.dart | 35 +++++++--- lib/widgets/fullscreen/info/info_page.dart | 22 ++++-- .../fullscreen/info/location_section.dart | 39 +++++------ .../fullscreen/info/navigation_button.dart | 29 -------- lib/widgets/fullscreen/info/xmp_section.dart | 27 +++----- 11 files changed, 166 insertions(+), 159 deletions(-) create mode 100644 lib/widgets/common/aves_filter_chip.dart delete mode 100644 lib/widgets/fullscreen/info/navigation_button.dart diff --git a/lib/main.dart b/lib/main.dart index 197be41ea..fcb02af39 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -107,9 +107,7 @@ class _HomePageState extends State { ) : MediaStoreCollectionProvider( child: Consumer( - builder: (context, collection, child) => CollectionPage( - collection: collection, - ), + builder: (context, collection, child) => CollectionPage(collection), ), ); }), diff --git a/lib/model/collection_lens.dart b/lib/model/collection_lens.dart index ec366563e..b10437e91 100644 --- a/lib/model/collection_lens.dart +++ b/lib/model/collection_lens.dart @@ -32,25 +32,6 @@ class CollectionLens with ChangeNotifier { onEntryAdded(); } - factory CollectionLens.empty() { - return CollectionLens( - source: CollectionSource(), - ); - } - - factory CollectionLens.from(CollectionLens lens, CollectionFilter filter) { - if (lens == null) return null; - return CollectionLens( - source: lens.source, - filters: [ - ...lens.filters, - if (filter != null) filter, - ], - groupFactor: lens.groupFactor, - sortFactor: lens.sortFactor, - ); - } - @override void dispose() { _subscriptions @@ -60,8 +41,28 @@ class CollectionLens with ChangeNotifier { super.dispose(); } + factory CollectionLens.empty() { + return CollectionLens( + source: CollectionSource(), + ); + } + + CollectionLens derive(CollectionFilter filter) { + return CollectionLens( + source: source, + filters: [ + ...filters, + if (filter != null) filter, + ], + groupFactor: groupFactor, + sortFactor: sortFactor, + ); + } + bool get isEmpty => _filteredEntries.isEmpty; + int get entryCount => _filteredEntries.length; + int get imageCount => _filteredEntries.where((entry) => !entry.isVideo).length; int get videoCount => _filteredEntries.where((entry) => entry.isVideo).length; diff --git a/lib/widgets/album/collection_drawer.dart b/lib/widgets/album/collection_drawer.dart index 02ab1d338..63911bbc7 100644 --- a/lib/widgets/album/collection_drawer.dart +++ b/lib/widgets/album/collection_drawer.dart @@ -278,13 +278,10 @@ class _FilteredCollectionNavTile extends StatelessWidget { Navigator.pushAndRemoveUntil( context, MaterialPageRoute( - builder: (context) => CollectionPage( - collection: CollectionLens( - source: source, - filters: [filter], - ), - title: title, - ), + builder: (context) => CollectionPage(CollectionLens( + source: source, + filters: [filter], + )), ), (route) => false, ); diff --git a/lib/widgets/album/collection_page.dart b/lib/widgets/album/collection_page.dart index 31bd678a0..c654b0e3c 100644 --- a/lib/widgets/album/collection_page.dart +++ b/lib/widgets/album/collection_page.dart @@ -12,13 +12,8 @@ import 'package:provider/provider.dart'; class CollectionPage extends StatelessWidget { final CollectionLens collection; - final String title; - const CollectionPage({ - Key key, - @required this.collection, - this.title, - }) : super(key: key); + const CollectionPage(this.collection); @override Widget build(BuildContext context) { @@ -30,7 +25,7 @@ class CollectionPage extends StatelessWidget { appBar: collection.filters.isEmpty ? AllCollectionAppBar() : SliverAppBar( - title: Text(title), + title: const Text('Aves'), actions: _buildActions(), bottom: FilterBar(collection.filters), floating: true, diff --git a/lib/widgets/album/filter_bar.dart b/lib/widgets/album/filter_bar.dart index 271f1689c..a3b3497e6 100644 --- a/lib/widgets/album/filter_bar.dart +++ b/lib/widgets/album/filter_bar.dart @@ -1,6 +1,5 @@ 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:aves/widgets/common/aves_filter_chip.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; @@ -9,7 +8,6 @@ class FilterBar extends StatelessWidget implements PreferredSizeWidget { final ScrollController _scrollController = ScrollController(); - static const double maxChipWidth = 160; static const EdgeInsets padding = EdgeInsets.only(left: 8, right: 8, bottom: 8); @override @@ -38,44 +36,13 @@ class FilterBar extends StatelessWidget implements PreferredSizeWidget { controller: _scrollController, primary: false, physics: const BouncingScrollPhysics(), - padding: EdgeInsets.all(NavigationButton.buttonBorderWidth / 2), + padding: const EdgeInsets.all(AvesFilterChip.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: Tooltip( - message: label, - 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, - ), - ), - ], - ), - ), - ), + return AvesFilterChip.fromFilter( + filter, + onPressed: (filter) {}, ); }, separatorBuilder: (context, index) => const SizedBox(width: 8), diff --git a/lib/widgets/common/aves_filter_chip.dart b/lib/widgets/common/aves_filter_chip.dart new file mode 100644 index 000000000..531b08148 --- /dev/null +++ b/lib/widgets/common/aves_filter_chip.dart @@ -0,0 +1,67 @@ +import 'package:aves/model/collection_filters.dart'; +import 'package:aves/utils/color_utils.dart'; +import 'package:flutter/material.dart'; + +typedef FilterCallback = void Function(CollectionFilter filter); + +class AvesFilterChip extends StatelessWidget { + final String label; + final IconData icon; + final VoidCallback 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, + ); + + static const double buttonBorderWidth = 2; + static const double maxChipWidth = 160; + + @override + Widget build(BuildContext context) { + return ConstrainedBox( + constraints: const BoxConstraints(maxWidth: maxChipWidth), + child: Tooltip( + message: label, + child: OutlineButton( + onPressed: onPressed, + borderSide: BorderSide( + color: stringToColor(label), + width: 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, + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/widgets/fullscreen/info/basic_section.dart b/lib/widgets/fullscreen/info/basic_section.dart index aaca455f7..79ce79449 100644 --- a/lib/widgets/fullscreen/info/basic_section.dart +++ b/lib/widgets/fullscreen/info/basic_section.dart @@ -1,15 +1,19 @@ +import 'package:aves/model/collection_filters.dart'; 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:flutter/material.dart'; import 'package:intl/intl.dart'; class BasicSection extends StatelessWidget { final ImageEntry entry; + final FilterCallback onFilter; const BasicSection({ Key key, @required this.entry, + @required this.onFilter, }) : super(key: key); @override @@ -19,15 +23,28 @@ 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)' : ''}'; - return InfoRowGroup({ - 'Title': entry.title ?? '?', - 'Date': dateText, - if (entry.isVideo) ..._buildVideoRows(), - if (!entry.isSvg) 'Resolution': resolutionText, - 'Size': entry.sizeBytes != null ? formatFilesize(entry.sizeBytes) : '?', - 'URI': entry.uri ?? '?', - if (entry.path != null) 'Path': entry.path, - }); + final filter = entry.directory != null ? AlbumFilter(entry.directory) : null; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + InfoRowGroup({ + 'Title': entry.title ?? '?', + 'Date': dateText, + if (entry.isVideo) ..._buildVideoRows(), + if (!entry.isSvg) 'Resolution': resolutionText, + 'Size': entry.sizeBytes != null ? formatFilesize(entry.sizeBytes) : '?', + 'URI': entry.uri ?? '?', + if (entry.path != null) 'Path': entry.path, + }), + if (filter != null) ...[ + const SizedBox(height: 8), + AvesFilterChip.fromFilter( + filter, + onPressed: onFilter, + ), + ] + ], + ); } Map _buildVideoRows() { diff --git a/lib/widgets/fullscreen/info/info_page.dart b/lib/widgets/fullscreen/info/info_page.dart index 28e31dd53..11f8cf0e6 100644 --- a/lib/widgets/fullscreen/info/info_page.dart +++ b/lib/widgets/fullscreen/info/info_page.dart @@ -1,10 +1,12 @@ +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/album/collection_page.dart'; +import 'package:aves/widgets/common/aves_filter_chip.dart'; 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/navigation_button.dart'; import 'package:aves/widgets/fullscreen/info/xmp_section.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; @@ -74,13 +76,14 @@ class InfoPageState extends State { entry: entry, showTitle: !locationAtTop, visibleNotifier: widget.visibleNotifier, + onFilter: _goToFilteredCollection, ); final basicAndLocationSliver = locationAtTop ? SliverToBoxAdapter( child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Expanded(child: BasicSection(entry: entry)), + Expanded(child: BasicSection(entry: entry, onFilter: _goToFilteredCollection)), const SizedBox(width: 8), Expanded(child: locationSection), ], @@ -89,7 +92,7 @@ class InfoPageState extends State { : SliverList( delegate: SliverChildListDelegate.fixed( [ - BasicSection(entry: entry), + BasicSection(entry: entry, onFilter: _goToFilteredCollection), locationSection, ], ), @@ -97,6 +100,7 @@ class InfoPageState extends State { final tagSliver = XmpTagSectionSliver( collection: collection, entry: entry, + onFilter: _goToFilteredCollection, ); final metadataSliver = MetadataSectionSliver( entry: entry, @@ -158,6 +162,16 @@ class InfoPageState extends State { curve: Curves.easeInOut, ); } + + void _goToFilteredCollection(CollectionFilter filter) { + if (collection == null) return; + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => CollectionPage(collection.derive(filter)), + ), + ); + } } class SectionRow extends StatelessWidget { @@ -171,7 +185,7 @@ class SectionRow extends StatelessWidget { final buildDivider = () => const SizedBox( width: dim, child: Divider( - thickness: NavigationButton.buttonBorderWidth, + thickness: AvesFilterChip.buttonBorderWidth, color: Colors.white70, ), ); diff --git a/lib/widgets/fullscreen/info/location_section.dart b/lib/widgets/fullscreen/info/location_section.dart index 3d649e956..f9cae21b5 100644 --- a/lib/widgets/fullscreen/info/location_section.dart +++ b/lib/widgets/fullscreen/info/location_section.dart @@ -4,9 +4,8 @@ import 'package:aves/model/image_entry.dart'; import 'package:aves/model/settings.dart'; import 'package:aves/utils/android_app_service.dart'; import 'package:aves/utils/geo_utils.dart'; -import 'package:aves/widgets/album/collection_page.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/navigation_button.dart'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:outline_material_icons/outline_material_icons.dart'; @@ -16,6 +15,7 @@ class LocationSection extends StatefulWidget { final ImageEntry entry; final bool showTitle; final ValueNotifier visibleNotifier; + final FilterCallback onFilter; const LocationSection({ Key key, @@ -23,6 +23,7 @@ class LocationSection extends StatefulWidget { @required this.entry, @required this.showTitle, @required this.visibleNotifier, + @required this.onFilter, }) : super(key: key); @override @@ -78,7 +79,10 @@ class _LocationSectionState extends State { } else if (entry.hasGps) { location = toDMS(entry.latLng).join(', '); } - final country = entry.addressDetails?.countryName ?? ''; + final country = entry.addressDetails?.countryName; + final filters = [ + if (country != null) CountryFilter(country), + ]; return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -102,17 +106,17 @@ class _LocationSectionState extends State { padding: const EdgeInsets.only(top: 8), child: InfoRowGroup({'Address': location}), ), - if (country.isNotEmpty) + if (filters.isNotEmpty) Padding( - padding: const EdgeInsets.symmetric(horizontal: NavigationButton.buttonBorderWidth / 2) + const EdgeInsets.only(top: 8), + padding: const EdgeInsets.symmetric(horizontal: AvesFilterChip.buttonBorderWidth / 2) + const EdgeInsets.only(top: 8), child: Wrap( spacing: 8, - children: [ - NavigationButton( - label: country, - onPressed: () => _goToCountry(context, country), - ), - ], + children: filters + .map((filter) => AvesFilterChip.fromFilter( + filter, + onPressed: widget.onFilter, + )) + .toList(), ), ), ], @@ -124,19 +128,6 @@ class _LocationSectionState extends State { } void _handleChange() => setState(() {}); - - void _goToCountry(BuildContext context, String country) { - if (collection == null) return; - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => CollectionPage( - collection: CollectionLens.from(collection, CountryFilter(country)), - title: country, - ), - ), - ); - } } class ImageMap extends StatefulWidget { diff --git a/lib/widgets/fullscreen/info/navigation_button.dart b/lib/widgets/fullscreen/info/navigation_button.dart deleted file mode 100644 index 958839ff0..000000000 --- a/lib/widgets/fullscreen/info/navigation_button.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:aves/utils/color_utils.dart'; -import 'package:flutter/material.dart'; - -class NavigationButton extends StatelessWidget { - final String label; - final VoidCallback onPressed; - - const NavigationButton({ - @required this.label, - @required this.onPressed, - }); - - static const double buttonBorderWidth = 2; - - @override - Widget build(BuildContext context) { - return OutlineButton( - onPressed: onPressed, - borderSide: BorderSide( - color: stringToColor(label), - width: buttonBorderWidth, - ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(42), - ), - child: Text(label), - ); - } -} diff --git a/lib/widgets/fullscreen/info/xmp_section.dart b/lib/widgets/fullscreen/info/xmp_section.dart index 911fd907f..4c6116093 100644 --- a/lib/widgets/fullscreen/info/xmp_section.dart +++ b/lib/widgets/fullscreen/info/xmp_section.dart @@ -1,9 +1,8 @@ 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/album/collection_page.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/navigation_button.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:outline_material_icons/outline_material_icons.dart'; @@ -11,11 +10,13 @@ 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 @@ -28,13 +29,14 @@ class XmpTagSectionSliver extends AnimatedWidget { : [ const SectionRow(OMIcons.localOffer), Padding( - padding: const EdgeInsets.symmetric(horizontal: NavigationButton.buttonBorderWidth / 2), + padding: const EdgeInsets.symmetric(horizontal: AvesFilterChip.buttonBorderWidth / 2), child: Wrap( spacing: 8, children: tags - .map((tag) => NavigationButton( - label: tag, - onPressed: () => _goToTag(context, tag), + .map((tag) => TagFilter(tag)) + .map((filter) => AvesFilterChip.fromFilter( + filter, + onPressed: onFilter, )) .toList(), ), @@ -43,17 +45,4 @@ class XmpTagSectionSliver extends AnimatedWidget { ), ); } - - void _goToTag(BuildContext context, String tag) { - if (collection == null) return; - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => CollectionPage( - collection: CollectionLens.from(collection, TagFilter(tag)), - title: tag, - ), - ), - ); - } }