search: expandable filter row
This commit is contained in:
parent
1c18cc320e
commit
54ba3c977f
4 changed files with 198 additions and 122 deletions
|
@ -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';
|
||||
|
|
93
lib/widgets/album/search/expandable_filter_row.dart
Normal file
93
lib/widgets/album/search/expandable_filter_row.dart
Normal 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;
|
||||
}
|
||||
}
|
104
lib/widgets/album/search/search_delegate.dart
Normal file
104
lib/widgets/album/search/search_delegate.dart
Normal 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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue