From cb553df009d1c2aef6487f0b6f8b57da22dab861 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Fri, 27 Mar 2020 13:05:54 +0900 Subject: [PATCH] filter bar: clear filter, app filter color --- lib/main.dart | 4 +- lib/model/collection_filters.dart | 46 ++++++-- lib/model/collection_lens.dart | 10 +- lib/widgets/album/collection_drawer.dart | 2 +- lib/widgets/album/collection_page.dart | 4 +- lib/widgets/album/collection_scaling.dart | 2 +- lib/widgets/album/collection_section.dart | 2 +- lib/widgets/album/filter_bar.dart | 31 +++-- lib/widgets/album/search_delegate.dart | 2 +- lib/widgets/album/thumbnail.dart | 2 +- lib/widgets/common/app_icon.dart | 49 -------- lib/widgets/common/aves_filter_chip.dart | 107 +++++++++++++----- .../media_query_data_provider.dart | 0 .../media_store_collection_provider.dart | 0 lib/widgets/common/icons.dart | 29 ++--- .../app_icon_image_provider.dart | 69 +++++++++++ .../image_providers}/uri_image_provider.dart | 2 +- .../uri_picture_provider.dart | 0 lib/widgets/debug_page.dart | 6 +- .../fullscreen_action_delegate.dart | 3 +- lib/widgets/fullscreen/fullscreen_body.dart | 7 +- lib/widgets/fullscreen/fullscreen_page.dart | 2 +- lib/widgets/fullscreen/image_view.dart | 4 +- lib/widgets/fullscreen/info/info_page.dart | 5 +- lib/widgets/stats.dart | 2 +- pubspec.lock | 7 ++ pubspec.yaml | 1 + 27 files changed, 257 insertions(+), 141 deletions(-) delete mode 100644 lib/widgets/common/app_icon.dart rename lib/widgets/common/{providers => data_providers}/media_query_data_provider.dart (100%) rename lib/widgets/common/{providers => data_providers}/media_store_collection_provider.dart (100%) create mode 100644 lib/widgets/common/image_providers/app_icon_image_provider.dart rename lib/widgets/{fullscreen => common/image_providers}/uri_image_provider.dart (93%) rename lib/widgets/{fullscreen => common/image_providers}/uri_picture_provider.dart (100%) diff --git a/lib/main.dart b/lib/main.dart index fcb02af39..a6d0eab44 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,8 +5,8 @@ import 'package:aves/model/settings.dart'; import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/utils/viewer_service.dart'; import 'package:aves/widgets/album/collection_page.dart'; -import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; -import 'package:aves/widgets/common/providers/media_store_collection_provider.dart'; +import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart'; +import 'package:aves/widgets/common/data_providers/media_store_collection_provider.dart'; import 'package:aves/widgets/fullscreen/fullscreen_page.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; diff --git a/lib/model/collection_filters.dart b/lib/model/collection_filters.dart index 44d2d79b0..5978ad91b 100644 --- a/lib/model/collection_filters.dart +++ b/lib/model/collection_filters.dart @@ -1,10 +1,16 @@ import 'package:aves/model/image_entry.dart'; +import 'package:aves/utils/android_file_utils.dart'; +import 'package:aves/utils/color_utils.dart'; import 'package:aves/widgets/common/icons.dart'; +import 'package:aves/widgets/common/image_providers/app_icon_image_provider.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:outline_material_icons/outline_material_icons.dart'; +import 'package:palette_generator/palette_generator.dart'; import 'package:path/path.dart'; -abstract class CollectionFilter { +abstract class CollectionFilter implements Comparable { static const List collectionFilterOrder = [ VideoFilter.type, GifFilter.type, @@ -20,11 +26,19 @@ abstract class CollectionFilter { String get label; - Widget iconBuilder(BuildContext context); + Widget iconBuilder(BuildContext context, double size); + + Future color(BuildContext context) => SynchronousFuture(stringToColor(label)); String get typeKey; int get displayPriority => collectionFilterOrder.indexOf(typeKey); + + @override + int compareTo(CollectionFilter other) { + final c = displayPriority.compareTo(other.displayPriority); + return c != 0 ? c : compareAsciiUpperCase(label, other.label); + } } class AlbumFilter extends CollectionFilter { @@ -41,7 +55,23 @@ class AlbumFilter extends CollectionFilter { String get label => album.split(separator).last; @override - Widget iconBuilder(context) => IconUtils.getAlbumIcon(context, album) ?? Icon(OMIcons.photoAlbum); + Widget iconBuilder(context, size) { + return IconUtils.getAlbumIcon(context: context, album: album, size: size) ?? Icon(OMIcons.photoAlbum, size: size); + } + + Future color(BuildContext context) async { + Color color; + if (androidFileUtils.getAlbumType(album) == AlbumType.App) { + final palette = await PaletteGenerator.fromImageProvider( + AppIconImage( + packageName: androidFileUtils.getAlbumAppPackageName(album), + size: 24, + ), + ); + color = palette.dominantColor?.color; + } + return color ?? super.color(context); + } @override String get typeKey => type; @@ -70,7 +100,7 @@ class TagFilter extends CollectionFilter { String get label => tag; @override - Widget iconBuilder(context) => Icon(OMIcons.localOffer); + Widget iconBuilder(context, size) => Icon(OMIcons.localOffer, size: size); @override String get typeKey => type; @@ -99,7 +129,7 @@ class CountryFilter extends CollectionFilter { String get label => country; @override - Widget iconBuilder(context) => Icon(OMIcons.place); + Widget iconBuilder(context, size) => Icon(OMIcons.place, size: size); @override String get typeKey => type; @@ -124,7 +154,7 @@ class VideoFilter extends CollectionFilter { String get label => 'Video'; @override - Widget iconBuilder(context) => Icon(OMIcons.movie); + Widget iconBuilder(context, size) => Icon(OMIcons.movie, size: size); @override String get typeKey => type; @@ -149,7 +179,7 @@ class GifFilter extends CollectionFilter { String get label => 'GIF'; @override - Widget iconBuilder(context) => Icon(OMIcons.gif); + Widget iconBuilder(context, size) => Icon(OMIcons.gif, size: size); @override String get typeKey => type; @@ -178,7 +208,7 @@ class QueryFilter extends CollectionFilter { String get label => '${query}'; @override - Widget iconBuilder(context) => Icon(OMIcons.formatQuote); + Widget iconBuilder(context, size) => Icon(OMIcons.formatQuote, size: size); @override String get typeKey => type; diff --git a/lib/model/collection_lens.dart b/lib/model/collection_lens.dart index b10437e91..e52b78800 100644 --- a/lib/model/collection_lens.dart +++ b/lib/model/collection_lens.dart @@ -52,7 +52,7 @@ class CollectionLens with ChangeNotifier { source: source, filters: [ ...filters, - if (filter != null) filter, + filter, ], groupFactor: groupFactor, sortFactor: sortFactor, @@ -71,6 +71,14 @@ class CollectionLens with ChangeNotifier { Object heroTag(ImageEntry entry) => '$hashCode${entry.uri}'; + void removeFilter(CollectionFilter filter) { + if (!filters.contains(filter)) return; + filters.remove(filter); + _applyFilters(); + _applySort(); + _applyGroup(); + } + void sort(SortFactor sortFactor) { this.sortFactor = sortFactor; _applySort(); diff --git a/lib/widgets/album/collection_drawer.dart b/lib/widgets/album/collection_drawer.dart index 63911bbc7..2d8528ed5 100644 --- a/lib/widgets/album/collection_drawer.dart +++ b/lib/widgets/album/collection_drawer.dart @@ -82,7 +82,7 @@ class _CollectionDrawerState extends State { ); final buildAlbumEntry = (album) => _FilteredCollectionNavTile( source: source, - leading: IconUtils.getAlbumIcon(context, album), + leading: IconUtils.getAlbumIcon(context: context, album: album), title: CollectionSource.getUniqueAlbumName(album, source.sortedAlbums), dense: true, filter: AlbumFilter(album), diff --git a/lib/widgets/album/collection_page.dart b/lib/widgets/album/collection_page.dart index c654b0e3c..919a30564 100644 --- a/lib/widgets/album/collection_page.dart +++ b/lib/widgets/album/collection_page.dart @@ -3,8 +3,8 @@ import 'package:aves/widgets/album/all_collection_app_bar.dart'; import 'package:aves/widgets/album/collection_drawer.dart'; import 'package:aves/widgets/album/filter_bar.dart'; import 'package:aves/widgets/album/thumbnail_collection.dart'; +import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart'; import 'package:aves/widgets/common/menu_row.dart'; -import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; import 'package:aves/widgets/stats.dart'; import 'package:flutter/material.dart'; import 'package:outline_material_icons/outline_material_icons.dart'; @@ -27,7 +27,7 @@ class CollectionPage extends StatelessWidget { : SliverAppBar( title: const Text('Aves'), actions: _buildActions(), - bottom: FilterBar(collection.filters), + bottom: FilterBar(), floating: true, ), ), diff --git a/lib/widgets/album/collection_scaling.dart b/lib/widgets/album/collection_scaling.dart index 6e1cf2056..4becb6a1f 100644 --- a/lib/widgets/album/collection_scaling.dart +++ b/lib/widgets/album/collection_scaling.dart @@ -4,7 +4,7 @@ import 'dart:ui' as ui; import 'package:aves/model/image_entry.dart'; import 'package:aves/widgets/album/collection_section.dart'; import 'package:aves/widgets/album/thumbnail.dart'; -import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; +import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_sticky_header/flutter_sticky_header.dart'; diff --git a/lib/widgets/album/collection_section.dart b/lib/widgets/album/collection_section.dart index ac7969dbc..7a8b3f2dd 100644 --- a/lib/widgets/album/collection_section.dart +++ b/lib/widgets/album/collection_section.dart @@ -162,7 +162,7 @@ class SectionHeader extends StatelessWidget { } Widget _buildAlbumSectionHeader(BuildContext context) { - Widget albumIcon = IconUtils.getAlbumIcon(context, sectionKey as String); + Widget albumIcon = IconUtils.getAlbumIcon(context: context, album: sectionKey as String); if (albumIcon != null) { albumIcon = Material( type: MaterialType.circle, diff --git a/lib/widgets/album/filter_bar.dart b/lib/widgets/album/filter_bar.dart index 11f776e4c..16c01decf 100644 --- a/lib/widgets/album/filter_bar.dart +++ b/lib/widgets/album/filter_bar.dart @@ -1,27 +1,20 @@ -import 'package:aves/model/collection_filters.dart'; +import 'package:aves/model/collection_lens.dart'; import 'package:aves/widgets/common/aves_filter_chip.dart'; -import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; class FilterBar extends StatelessWidget implements PreferredSizeWidget { - final List filters; - - final ScrollController _scrollController = ScrollController(); - - static const EdgeInsets padding = EdgeInsets.only(left: 8, right: 8, bottom: 8); + static const EdgeInsets padding = EdgeInsets.symmetric(horizontal: 8); @override final Size preferredSize = Size.fromHeight(kMinInteractiveDimension + padding.vertical); - FilterBar(Set filters) - : this.filters = filters.toList() - ..sort((a, b) { - final c = a.displayPriority.compareTo(b.displayPriority); - return c != 0 ? c : compareAsciiUpperCase(a.label, b.label); - }); - @override Widget build(BuildContext context) { + debugPrint('$runtimeType build'); + final collection = Provider.of(context); + final filters = collection.filters.toList()..sort(); + return Container( // specify transparent as a workaround to prevent // chip border clipping when the floating app bar is fading @@ -34,16 +27,18 @@ class FilterBar extends StatelessWidget implements PreferredSizeWidget { onNotification: (notification) => true, child: ListView.separated( scrollDirection: Axis.horizontal, - controller: _scrollController, primary: false, physics: const BouncingScrollPhysics(), padding: const EdgeInsets.all(AvesFilterChip.buttonBorderWidth / 2), itemBuilder: (context, index) { if (index >= filters.length) return null; final filter = filters[index]; - return AvesFilterChip( - filter, - onPressed: (filter) {}, + return Center( + child: AvesFilterChip( + filter, + clearable: true, + onPressed: collection.removeFilter, + ), ); }, separatorBuilder: (context, index) => const SizedBox(width: 8), diff --git a/lib/widgets/album/search_delegate.dart b/lib/widgets/album/search_delegate.dart index d955ba36d..a37ebe750 100644 --- a/lib/widgets/album/search_delegate.dart +++ b/lib/widgets/album/search_delegate.dart @@ -2,7 +2,7 @@ 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/thumbnail_collection.dart'; -import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; +import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart'; import 'package:flutter/material.dart'; import 'package:outline_material_icons/outline_material_icons.dart'; import 'package:provider/provider.dart'; diff --git a/lib/widgets/album/thumbnail.dart b/lib/widgets/album/thumbnail.dart index c47bfa6a2..6a0df071e 100644 --- a/lib/widgets/album/thumbnail.dart +++ b/lib/widgets/album/thumbnail.dart @@ -4,7 +4,7 @@ import 'package:aves/model/image_entry.dart'; import 'package:aves/utils/constants.dart'; import 'package:aves/widgets/common/icons.dart'; import 'package:aves/widgets/common/image_preview.dart'; -import 'package:aves/widgets/fullscreen/uri_picture_provider.dart'; +import 'package:aves/widgets/common/image_providers/uri_picture_provider.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; diff --git a/lib/widgets/common/app_icon.dart b/lib/widgets/common/app_icon.dart deleted file mode 100644 index bfaf53e30..000000000 --- a/lib/widgets/common/app_icon.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'dart:typed_data'; - -import 'package:aves/utils/android_app_service.dart'; -import 'package:flutter/material.dart'; -import 'package:transparent_image/transparent_image.dart'; - -class AppIcon extends StatefulWidget { - final String packageName; - final double size; - final double devicePixelRatio; - - const AppIcon({ - Key key, - @required this.packageName, - @required this.size, - @required this.devicePixelRatio, - }) : super(key: key); - - @override - State createState() => AppIconState(); -} - -class AppIconState extends State { - Future _byteLoader; - - @override - void initState() { - super.initState(); - final dim = (widget.size * widget.devicePixelRatio).round(); - _byteLoader = AndroidAppService.getAppIcon(widget.packageName, dim); - } - - @override - Widget build(BuildContext context) { - return FutureBuilder( - future: _byteLoader, - builder: (futureContext, AsyncSnapshot snapshot) { - final bytes = (snapshot.connectionState == ConnectionState.done && !snapshot.hasError) ? snapshot.data : kTransparentImage; - return bytes.isNotEmpty - ? Image.memory( - bytes, - width: widget.size, - height: widget.size, - ) - : const SizedBox.shrink(); - }, - ); - } -} diff --git a/lib/widgets/common/aves_filter_chip.dart b/lib/widgets/common/aves_filter_chip.dart index f07f65a22..2869944cf 100644 --- a/lib/widgets/common/aves_filter_chip.dart +++ b/lib/widgets/common/aves_filter_chip.dart @@ -1,56 +1,101 @@ import 'package:aves/model/collection_filters.dart'; -import 'package:aves/utils/color_utils.dart'; import 'package:flutter/material.dart'; +import 'package:outline_material_icons/outline_material_icons.dart'; typedef FilterCallback = void Function(CollectionFilter filter); -class AvesFilterChip extends StatelessWidget { +class AvesFilterChip extends StatefulWidget { final CollectionFilter filter; + final bool clearable; final FilterCallback onPressed; - String get label => filter.label; - static const double buttonBorderWidth = 2; static const double maxChipWidth = 160; + static const double iconSize = 20; + static const double padding = 6; const AvesFilterChip( this.filter, { + this.clearable = false, @required this.onPressed, }); + @override + _AvesFilterChipState createState() => _AvesFilterChipState(); +} + +class _AvesFilterChipState extends State { + Future _colorFuture; + + CollectionFilter get filter => widget.filter; + + String get label => filter.label; + + @override + void initState() { + super.initState(); + _initColorLoader(); + } + + @override + void didUpdateWidget(AvesFilterChip oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.filter != filter) { + _initColorLoader(); + } + } + + void _initColorLoader() => _colorFuture = filter.color(context); + @override Widget build(BuildContext context) { - final icon = filter.iconBuilder(context); + final leading = filter.iconBuilder(context, AvesFilterChip.iconSize); + final trailing = widget.clearable ? Icon(OMIcons.clear, size: AvesFilterChip.iconSize) : null; + + final child = Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (leading != null) ...[ + leading, + const SizedBox(width: AvesFilterChip.padding * 1.6), + ], + Flexible( + child: Text( + label, + softWrap: false, + overflow: TextOverflow.fade, + maxLines: 1, + ), + ), + if (trailing != null) ...[ + const SizedBox(width: AvesFilterChip.padding), + trailing, + ], + ], + ); + + final shape = RoundedRectangleBorder( + borderRadius: BorderRadius.circular(42), + ); + return ConstrainedBox( - constraints: const BoxConstraints(maxWidth: maxChipWidth), + constraints: const BoxConstraints(maxWidth: AvesFilterChip.maxChipWidth), child: Tooltip( message: label, - child: OutlineButton( - onPressed: onPressed != null ? () => onPressed(filter) : null, - borderSide: BorderSide( - color: stringToColor(label), - width: buttonBorderWidth, - ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(42), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (icon != null) ...[ - icon, - const SizedBox(width: 8), - ], - Flexible( - child: Text( - label, - softWrap: false, - overflow: TextOverflow.fade, - maxLines: 1, - ), + child: FutureBuilder( + future: _colorFuture, + builder: (context, AsyncSnapshot snapshot) { + return OutlineButton( + onPressed: widget.onPressed != null ? () => widget.onPressed(filter) : null, + borderSide: BorderSide( + color: snapshot.hasData ? snapshot.data : Colors.transparent, + width: AvesFilterChip.buttonBorderWidth, ), - ], - ), + padding: const EdgeInsets.symmetric(horizontal: AvesFilterChip.padding * 2), + shape: shape, + child: child, + ); + }, ), ), ); diff --git a/lib/widgets/common/providers/media_query_data_provider.dart b/lib/widgets/common/data_providers/media_query_data_provider.dart similarity index 100% rename from lib/widgets/common/providers/media_query_data_provider.dart rename to lib/widgets/common/data_providers/media_query_data_provider.dart diff --git a/lib/widgets/common/providers/media_store_collection_provider.dart b/lib/widgets/common/data_providers/media_store_collection_provider.dart similarity index 100% rename from lib/widgets/common/providers/media_store_collection_provider.dart rename to lib/widgets/common/data_providers/media_store_collection_provider.dart diff --git a/lib/widgets/common/icons.dart b/lib/widgets/common/icons.dart index 015342b16..9ed6faf0c 100644 --- a/lib/widgets/common/icons.dart +++ b/lib/widgets/common/icons.dart @@ -2,10 +2,9 @@ import 'dart:ui'; import 'package:aves/model/image_entry.dart'; import 'package:aves/utils/android_file_utils.dart'; -import 'package:aves/widgets/common/app_icon.dart'; +import 'package:aves/widgets/common/image_providers/app_icon_image_provider.dart'; import 'package:flutter/material.dart'; import 'package:outline_material_icons/outline_material_icons.dart'; -import 'package:provider/provider.dart'; class VideoIcon extends StatelessWidget { final ImageEntry entry; @@ -88,23 +87,27 @@ class OverlayIcon extends StatelessWidget { } class IconUtils { - static Widget getAlbumIcon(BuildContext context, String albumDirectory) { - switch (androidFileUtils.getAlbumType(albumDirectory)) { + static Widget getAlbumIcon({ + @required BuildContext context, + @required String album, + double size = 24, + }) { + switch (androidFileUtils.getAlbumType(album)) { case AlbumType.Camera: - return Icon(OMIcons.photoCamera); + return Icon(OMIcons.photoCamera, size: size); case AlbumType.Screenshots: case AlbumType.ScreenRecordings: - return Icon(OMIcons.smartphone); + return Icon(OMIcons.smartphone, size: size); case AlbumType.Download: - return Icon(Icons.file_download); + return Icon(Icons.file_download, size: size); case AlbumType.App: - return Selector( - selector: (c, mq) => mq.devicePixelRatio, - builder: (c, devicePixelRatio, child) => AppIcon( - packageName: androidFileUtils.getAlbumAppPackageName(albumDirectory), - size: IconTheme.of(context).size, - devicePixelRatio: devicePixelRatio, + return Image( + image: AppIconImage( + packageName: androidFileUtils.getAlbumAppPackageName(album), + size: size, ), + width: size, + height: size, ); case AlbumType.Default: default: diff --git a/lib/widgets/common/image_providers/app_icon_image_provider.dart b/lib/widgets/common/image_providers/app_icon_image_provider.dart new file mode 100644 index 000000000..3b738ec94 --- /dev/null +++ b/lib/widgets/common/image_providers/app_icon_image_provider.dart @@ -0,0 +1,69 @@ +import 'dart:typed_data'; +import 'dart:ui' as ui show Codec; + +import 'package:aves/utils/android_app_service.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +class AppIconImage extends ImageProvider { + const AppIconImage({ + @required this.packageName, + @required this.size, + this.scale = 1.0, + }) : assert(packageName != null), + assert(scale != null); + + final String packageName; + final double size; + final double scale; + + @override + Future obtainKey(ImageConfiguration configuration) { + return SynchronousFuture(AppIconImageKey( + packageName: packageName, + sizePixels: (size * configuration.devicePixelRatio).round(), + scale: scale, + )); + } + + @override + ImageStreamCompleter load(AppIconImageKey key, DecoderCallback decode) { + return MultiFrameImageStreamCompleter( + codec: _loadAsync(key, decode), + scale: key.scale, + informationCollector: () sync* { + yield ErrorDescription('uri=$packageName, size=$size'); + }, + ); + } + + Future _loadAsync(AppIconImageKey key, DecoderCallback decode) async { + final Uint8List bytes = await AndroidAppService.getAppIcon(key.packageName, key.sizePixels); + if (bytes.lengthInBytes == 0) { + return null; + } + + return await decode(bytes); + } +} + +class AppIconImageKey { + final String packageName; + final int sizePixels; + final double scale; + + const AppIconImageKey({ + @required this.packageName, + @required this.sizePixels, + this.scale, + }); + + @override + bool operator ==(Object other) { + if (other.runtimeType != runtimeType) return false; + return other is AppIconImageKey && other.packageName == packageName && other.sizePixels == sizePixels && other.scale == scale; + } + + @override + int get hashCode => hashValues(packageName, sizePixels, scale); +} diff --git a/lib/widgets/fullscreen/uri_image_provider.dart b/lib/widgets/common/image_providers/uri_image_provider.dart similarity index 93% rename from lib/widgets/fullscreen/uri_image_provider.dart rename to lib/widgets/common/image_providers/uri_image_provider.dart index 7ef3257dd..ce873626a 100644 --- a/lib/widgets/fullscreen/uri_image_provider.dart +++ b/lib/widgets/common/image_providers/uri_image_provider.dart @@ -47,7 +47,7 @@ class UriImage extends ImageProvider { @override bool operator ==(Object other) { if (other.runtimeType != runtimeType) return false; - return other is UriImage && other.uri == uri && other.scale == scale; + return other is UriImage && other.uri == uri && other.mimeType == mimeType && other.scale == scale; } @override diff --git a/lib/widgets/fullscreen/uri_picture_provider.dart b/lib/widgets/common/image_providers/uri_picture_provider.dart similarity index 100% rename from lib/widgets/fullscreen/uri_picture_provider.dart rename to lib/widgets/common/image_providers/uri_picture_provider.dart diff --git a/lib/widgets/debug_page.dart b/lib/widgets/debug_page.dart index c284a1973..9e4504bab 100644 --- a/lib/widgets/debug_page.dart +++ b/lib/widgets/debug_page.dart @@ -4,9 +4,10 @@ import 'package:aves/model/image_metadata.dart'; import 'package:aves/model/metadata_db.dart'; import 'package:aves/model/settings.dart'; import 'package:aves/utils/file_utils.dart'; -import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; +import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; +import 'package:flutter_svg/flutter_svg.dart'; class DebugPage extends StatefulWidget { final CollectionSource source; @@ -86,6 +87,9 @@ class DebugPageState extends State { }, ), const Divider(), + Text('Image cache: ${imageCache.currentSize} items, ${formatFilesize(imageCache.currentSizeBytes)}'), + Text('SVG cache: ${PictureProvider.cacheCount} items'), + const Divider(), const Text('Time dilation'), Slider( value: timeDilation, diff --git a/lib/widgets/fullscreen/fullscreen_action_delegate.dart b/lib/widgets/fullscreen/fullscreen_action_delegate.dart index 0e3f642ee..4a0f42710 100644 --- a/lib/widgets/fullscreen/fullscreen_action_delegate.dart +++ b/lib/widgets/fullscreen/fullscreen_action_delegate.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:aves/model/collection_lens.dart'; import 'package:aves/model/image_entry.dart'; import 'package:aves/utils/android_app_service.dart'; +import 'package:aves/widgets/common/image_providers/uri_image_provider.dart'; import 'package:flushbar/flushbar.dart'; import 'package:flutter/material.dart'; import 'package:pdf/widgets.dart' as pdf; @@ -77,7 +78,7 @@ class FullscreenActionDelegate { final doc = pdf.Document(title: entry.title); final image = await pdfImageFromImageProvider( pdf: doc.document, - image: FileImage(File(entry.path)), + image: UriImage(uri: entry.uri, mimeType: entry.mimeType), ); doc.addPage(pdf.Page(build: (context) => pdf.Center(child: pdf.Image(image)))); // Page unawaited(Printing.layoutPdf( diff --git a/lib/widgets/fullscreen/fullscreen_body.dart b/lib/widgets/fullscreen/fullscreen_body.dart index a1f42af5a..ded410e6a 100644 --- a/lib/widgets/fullscreen/fullscreen_body.dart +++ b/lib/widgets/fullscreen/fullscreen_body.dart @@ -3,13 +3,14 @@ import 'dart:math'; import 'package:aves/model/collection_lens.dart'; import 'package:aves/model/image_entry.dart'; +import 'package:aves/widgets/common/image_providers/uri_image_provider.dart'; import 'package:aves/widgets/fullscreen/fullscreen_action_delegate.dart'; import 'package:aves/widgets/fullscreen/image_page.dart'; import 'package:aves/widgets/fullscreen/info/info_page.dart'; import 'package:aves/widgets/fullscreen/overlay/bottom.dart'; import 'package:aves/widgets/fullscreen/overlay/top.dart'; import 'package:aves/widgets/fullscreen/overlay/video.dart'; -import 'package:aves/widgets/fullscreen/uri_image_provider.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; @@ -126,14 +127,14 @@ class FullscreenBodyState extends State with SingleTickerProvide if (_currentVerticalPage.value == infoPage) { // back from info to image _goToVerticalPage(imagePage); - return Future.value(false); + return SynchronousFuture(false); } if (!ModalRoute.of(context).canPop) { // exit app when trying to pop a fullscreen page that is a viewer for a single entry exit(0); } _onLeave(); - return Future.value(true); + return SynchronousFuture(true); }, child: Stack( children: [ diff --git a/lib/widgets/fullscreen/fullscreen_page.dart b/lib/widgets/fullscreen/fullscreen_page.dart index df76c3627..cf7bd78fa 100644 --- a/lib/widgets/fullscreen/fullscreen_page.dart +++ b/lib/widgets/fullscreen/fullscreen_page.dart @@ -1,6 +1,6 @@ import 'package:aves/model/collection_lens.dart'; import 'package:aves/model/image_entry.dart'; -import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; +import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart'; import 'package:aves/widgets/fullscreen/fullscreen_body.dart'; import 'package:flutter/material.dart'; diff --git a/lib/widgets/fullscreen/image_view.dart b/lib/widgets/fullscreen/image_view.dart index 4265a139b..5108ee412 100644 --- a/lib/widgets/fullscreen/image_view.dart +++ b/lib/widgets/fullscreen/image_view.dart @@ -1,7 +1,7 @@ import 'package:aves/model/image_entry.dart'; import 'package:aves/utils/constants.dart'; -import 'package:aves/widgets/fullscreen/uri_image_provider.dart'; -import 'package:aves/widgets/fullscreen/uri_picture_provider.dart'; +import 'package:aves/widgets/common/image_providers/uri_image_provider.dart'; +import 'package:aves/widgets/common/image_providers/uri_picture_provider.dart'; import 'package:aves/widgets/fullscreen/video.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; diff --git a/lib/widgets/fullscreen/info/info_page.dart b/lib/widgets/fullscreen/info/info_page.dart index 921507c26..34ab2f606 100644 --- a/lib/widgets/fullscreen/info/info_page.dart +++ b/lib/widgets/fullscreen/info/info_page.dart @@ -3,7 +3,7 @@ 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/common/data_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'; @@ -155,11 +155,12 @@ class InfoPageState extends State { void _goToFilteredCollection(CollectionFilter filter) { if (collection == null) return; - Navigator.push( + Navigator.pushAndRemoveUntil( context, MaterialPageRoute( builder: (context) => CollectionPage(collection.derive(filter)), ), + (route) => false, ); } } diff --git a/lib/widgets/stats.dart b/lib/widgets/stats.dart index 2617e015e..f533926ea 100644 --- a/lib/widgets/stats.dart +++ b/lib/widgets/stats.dart @@ -1,7 +1,7 @@ import 'package:aves/model/collection_lens.dart'; import 'package:aves/model/image_entry.dart'; import 'package:aves/utils/color_utils.dart'; -import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; +import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart'; import 'package:charts_flutter/flutter.dart' as charts; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; diff --git a/pubspec.lock b/pubspec.lock index 1a80db4dc..933710301 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -232,6 +232,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.1.1" + palette_generator: + dependency: "direct main" + description: + name: palette_generator + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.2" path: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index d4eaca27f..02d3518ef 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -40,6 +40,7 @@ dependencies: outline_material_icons: path: pdf: + palette_generator: pedantic: percent_indicator: permission_handler: