info: added album filter chip
This commit is contained in:
parent
246e697d9d
commit
0199f9bd22
11 changed files with 166 additions and 159 deletions
|
@ -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),
|
||||
),
|
||||
);
|
||||
}),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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),
|
||||
|
|
67
lib/widgets/common/aves_filter_chip.dart
Normal file
67
lib/widgets/common/aves_filter_chip.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue