info: added album filter chip

This commit is contained in:
Thibault Deckers 2020-03-26 18:16:21 +09:00
parent 246e697d9d
commit 0199f9bd22
11 changed files with 166 additions and 159 deletions

View file

@ -107,9 +107,7 @@ class _HomePageState extends State<HomePage> {
)
: MediaStoreCollectionProvider(
child: Consumer<CollectionLens>(
builder: (context, collection, child) => CollectionPage(
collection: collection,
),
builder: (context, collection, child) => CollectionPage(collection),
),
);
}),

View file

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

View file

@ -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,
);

View file

@ -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,

View file

@ -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),

View file

@ -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,
),
),
],
),
),
),
);
}
}

View file

@ -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<String, String> _buildVideoRows() {

View file

@ -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<InfoPage> {
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<InfoPage> {
: SliverList(
delegate: SliverChildListDelegate.fixed(
[
BasicSection(entry: entry),
BasicSection(entry: entry, onFilter: _goToFilteredCollection),
locationSection,
],
),
@ -97,6 +100,7 @@ class InfoPageState extends State<InfoPage> {
final tagSliver = XmpTagSectionSliver(
collection: collection,
entry: entry,
onFilter: _goToFilteredCollection,
);
final metadataSliver = MetadataSectionSliver(
entry: entry,
@ -158,6 +162,16 @@ class InfoPageState extends State<InfoPage> {
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,
),
);

View file

@ -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<bool> 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<LocationSection> {
} 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<LocationSection> {
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<LocationSection> {
}
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 {

View file

@ -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),
);
}
}

View file

@ -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,
),
),
);
}
}