diff --git a/lib/widgets/album/collection_app_bar.dart b/lib/widgets/album/collection_app_bar.dart index 61f8576c3..0fd8ada1a 100644 --- a/lib/widgets/album/collection_app_bar.dart +++ b/lib/widgets/album/collection_app_bar.dart @@ -3,7 +3,7 @@ import 'package:aves/model/filters/query.dart'; import 'package:aves/model/settings.dart'; import 'package:aves/widgets/album/collection_page.dart'; import 'package:aves/widgets/album/filter_bar.dart'; -import 'package:aves/widgets/album/search_delegate.dart'; +import 'package:aves/widgets/album/search/search_delegate.dart'; import 'package:aves/widgets/common/menu_row.dart'; import 'package:aves/widgets/stats.dart'; import 'package:flutter/foundation.dart'; diff --git a/lib/widgets/album/search/expandable_filter_row.dart b/lib/widgets/album/search/expandable_filter_row.dart new file mode 100644 index 000000000..fd95fae34 --- /dev/null +++ b/lib/widgets/album/search/expandable_filter_row.dart @@ -0,0 +1,93 @@ +import 'package:aves/model/filters/filters.dart'; +import 'package:aves/utils/constants.dart'; +import 'package:aves/widgets/album/search/search_delegate.dart'; +import 'package:aves/widgets/common/aves_filter_chip.dart'; +import 'package:flutter/material.dart'; +import 'package:outline_material_icons/outline_material_icons.dart'; + +class ExpandableFilterRow extends StatelessWidget { + final String title; + final Iterable filters; + final ValueNotifier expandedNotifier; + final FilterCallback onPressed; + + const ExpandableFilterRow({ + this.title, + @required this.filters, + this.expandedNotifier, + @required this.onPressed, + }); + + @override + Widget build(BuildContext context) { + if (filters.isEmpty) return const SizedBox.shrink(); + + final hasTitle = title != null && title.isNotEmpty; + + final isExpanded = hasTitle && expandedNotifier?.value == title; + + Widget titleRow; + if (hasTitle) { + titleRow = Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + Text( + title, + style: Constants.titleTextStyle, + ), + const Spacer(), + IconButton( + icon: Icon(isExpanded ? OMIcons.expandLess : OMIcons.expandMore), + onPressed: () => expandedNotifier.value = isExpanded ? null : title, + ), + ], + ), + ); + } + + final filtersList = filters.toList(); + final filterChips = isExpanded + ? Padding( + padding: const EdgeInsets.symmetric(horizontal: AvesFilterChip.buttonBorderWidth / 2 + 6), + child: Wrap( + spacing: 8, + children: filtersList + .map((filter) => AvesFilterChip( + filter: filter, + onPressed: onPressed, + )) + .toList(), + ), + ) + : Container( + height: kMinInteractiveDimension, + child: ListView.separated( + scrollDirection: Axis.horizontal, + physics: const BouncingScrollPhysics(), + padding: const EdgeInsets.all(AvesFilterChip.buttonBorderWidth / 2) + const EdgeInsets.symmetric(horizontal: 6), + itemBuilder: (context, index) { + if (index >= filtersList.length) return null; + final filter = filtersList[index]; + return Center( + child: AvesFilterChip( + filter: filter, + onPressed: onPressed, + ), + ); + }, + separatorBuilder: (context, index) => const SizedBox(width: 8), + itemCount: filtersList.length, + ), + ); + return titleRow != null + ? Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + titleRow, + filterChips, + ], + ) + : filterChips; + } +} diff --git a/lib/widgets/album/search/search_delegate.dart b/lib/widgets/album/search/search_delegate.dart new file mode 100644 index 000000000..1a32b678d --- /dev/null +++ b/lib/widgets/album/search/search_delegate.dart @@ -0,0 +1,104 @@ +import 'package:aves/model/collection_lens.dart'; +import 'package:aves/model/collection_source.dart'; +import 'package:aves/model/filters/album.dart'; +import 'package:aves/model/filters/country.dart'; +import 'package:aves/model/filters/favourite.dart'; +import 'package:aves/model/filters/filters.dart'; +import 'package:aves/model/filters/gif.dart'; +import 'package:aves/model/filters/query.dart'; +import 'package:aves/model/filters/tag.dart'; +import 'package:aves/model/filters/video.dart'; +import 'package:aves/widgets/album/search/expandable_filter_row.dart'; +import 'package:flutter/material.dart'; +import 'package:outline_material_icons/outline_material_icons.dart'; + +class ImageSearchDelegate extends SearchDelegate { + final CollectionLens collection; + final ValueNotifier expandedSectionNotifier = ValueNotifier(null); + + ImageSearchDelegate(this.collection); + + @override + ThemeData appBarTheme(BuildContext context) { + return Theme.of(context); + } + + @override + Widget buildLeading(BuildContext context) { + return IconButton( + tooltip: 'Back', + icon: AnimatedIcon( + icon: AnimatedIcons.menu_arrow, + progress: transitionAnimation, + ), + onPressed: () => close(context, null), + ); + } + + @override + List buildActions(BuildContext context) { + return [ + if (query.isNotEmpty) + IconButton( + tooltip: 'Clear', + icon: Icon(OMIcons.clear), + onPressed: () { + query = ''; + showSuggestions(context); + }, + ), + ]; + } + + @override + Widget buildSuggestions(BuildContext context) { + final source = collection.source; + final upQuery = query.toUpperCase(); + final containQuery = (String s) => s.toUpperCase().contains(upQuery); + return SafeArea( + child: ValueListenableBuilder( + valueListenable: expandedSectionNotifier, + builder: (context, expandedSection, child) { + debugPrint('builder expandedSection=$expandedSection'); + return ListView( + children: [ + _buildFilterRow( + context: context, + filters: [FavouriteFilter(), VideoFilter(), GifFilter()].where((f) => containQuery(f.label)), + ), + _buildFilterRow( + context: context, + title: 'Albums', + filters: source.sortedAlbums.where(containQuery).map((s) => AlbumFilter(s, CollectionSource.getUniqueAlbumName(s, source.sortedAlbums))).where((f) => containQuery(f.uniqueName)), + ), + _buildFilterRow( + context: context, + title: 'Countries', + filters: source.sortedCountries.where(containQuery).map((s) => CountryFilter(s)), + ), + _buildFilterRow( + context: context, + title: 'Tags', + filters: source.sortedTags.where(containQuery).map((s) => TagFilter(s)), + ), + ], + ); + }), + ); + } + + Widget _buildFilterRow({@required BuildContext context, String title, @required Iterable filters}) { + return ExpandableFilterRow( + title: title, + filters: filters, + expandedNotifier: expandedSectionNotifier, + onPressed: (filter) => close(context, filter), + ); + } + + @override + Widget buildResults(BuildContext context) { + close(context, QueryFilter(query)); + return const SizedBox.shrink(); + } +} diff --git a/lib/widgets/album/search_delegate.dart b/lib/widgets/album/search_delegate.dart deleted file mode 100644 index 15f181d85..000000000 --- a/lib/widgets/album/search_delegate.dart +++ /dev/null @@ -1,121 +0,0 @@ -import 'package:aves/model/collection_lens.dart'; -import 'package:aves/model/collection_source.dart'; -import 'package:aves/model/filters/album.dart'; -import 'package:aves/model/filters/country.dart'; -import 'package:aves/model/filters/favourite.dart'; -import 'package:aves/model/filters/filters.dart'; -import 'package:aves/model/filters/gif.dart'; -import 'package:aves/model/filters/query.dart'; -import 'package:aves/model/filters/tag.dart'; -import 'package:aves/model/filters/video.dart'; -import 'package:aves/utils/constants.dart'; -import 'package:aves/widgets/common/aves_filter_chip.dart'; -import 'package:flutter/material.dart'; -import 'package:outline_material_icons/outline_material_icons.dart'; - -class ImageSearchDelegate extends SearchDelegate { - final CollectionLens collection; - - ImageSearchDelegate(this.collection); - - @override - ThemeData appBarTheme(BuildContext context) { - return Theme.of(context); - } - - @override - Widget buildLeading(BuildContext context) { - return IconButton( - tooltip: 'Back', - icon: AnimatedIcon( - icon: AnimatedIcons.menu_arrow, - progress: transitionAnimation, - ), - onPressed: () => close(context, null), - ); - } - - @override - List buildActions(BuildContext context) { - return [ - if (query.isNotEmpty) - IconButton( - tooltip: 'Clear', - icon: Icon(OMIcons.clear), - onPressed: () { - query = ''; - showSuggestions(context); - }, - ), - ]; - } - - @override - Widget buildSuggestions(BuildContext context) { - final source = collection.source; - final upQuery = query.toUpperCase(); - final containQuery = (String s) => s.toUpperCase().contains(upQuery); - return SafeArea( - child: ListView( - children: [ - ..._buildFilterRow( - filters: [FavouriteFilter(), VideoFilter(), GifFilter()].where((f) => containQuery(f.label)), - ), - ..._buildFilterRow( - title: 'Albums', - filters: source.sortedAlbums.where(containQuery).map((s) => AlbumFilter(s, CollectionSource.getUniqueAlbumName(s, source.sortedAlbums))).where((f) => containQuery(f.uniqueName)), - ), - ..._buildFilterRow( - title: 'Countries', - filters: source.sortedCountries.where(containQuery).map((s) => CountryFilter(s)), - ), - ..._buildFilterRow( - title: 'Tags', - filters: source.sortedTags.where(containQuery).map((s) => TagFilter(s)), - ), - ], - ), - ); - } - - List _buildFilterRow({String title, @required Iterable filters}) { - if (filters.isEmpty) return []; - final filtersList = filters.toList(); - return [ - if (title != null && title.isNotEmpty) - Padding( - padding: const EdgeInsets.all(16), - child: Text( - title, - style: Constants.titleTextStyle, - ), - ), - Container( - height: kMinInteractiveDimension, - child: ListView.separated( - scrollDirection: Axis.horizontal, - physics: const BouncingScrollPhysics(), - padding: const EdgeInsets.all(AvesFilterChip.buttonBorderWidth / 2) + const EdgeInsets.symmetric(horizontal: 6), - itemBuilder: (context, index) { - if (index >= filtersList.length) return null; - final filter = filtersList[index]; - return Center( - child: AvesFilterChip( - filter: filter, - onPressed: (filter) => close(context, filter), - ), - ); - }, - separatorBuilder: (context, index) => const SizedBox(width: 8), - itemCount: filtersList.length, - ), - ), - ]; - } - - @override - Widget buildResults(BuildContext context) { - close(context, QueryFilter(query)); - return const SizedBox.shrink(); - } -}