search: expandable filter row

This commit is contained in:
Thibault Deckers 2020-03-30 11:11:07 +09:00
parent 1c18cc320e
commit 54ba3c977f
4 changed files with 198 additions and 122 deletions

View file

@ -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';

View file

@ -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<CollectionFilter> filters;
final ValueNotifier<String> 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;
}
}

View file

@ -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<CollectionFilter> {
final CollectionLens collection;
final ValueNotifier<String> 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<Widget> 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<String>(
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<CollectionFilter> 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();
}
}

View file

@ -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<CollectionFilter> {
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<Widget> 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<Widget> _buildFilterRow({String title, @required Iterable<CollectionFilter> 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();
}
}