filter bar: clear filter, app filter color

This commit is contained in:
Thibault Deckers 2020-03-27 13:05:54 +09:00
parent 4c23a0f5ad
commit cb553df009
27 changed files with 257 additions and 141 deletions

View file

@ -5,8 +5,8 @@ import 'package:aves/model/settings.dart';
import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/utils/android_file_utils.dart';
import 'package:aves/utils/viewer_service.dart'; import 'package:aves/utils/viewer_service.dart';
import 'package:aves/widgets/album/collection_page.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/data_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_store_collection_provider.dart';
import 'package:aves/widgets/fullscreen/fullscreen_page.dart'; import 'package:aves/widgets/fullscreen/fullscreen_page.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';

View file

@ -1,10 +1,16 @@
import 'package:aves/model/image_entry.dart'; 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/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:flutter/widgets.dart';
import 'package:outline_material_icons/outline_material_icons.dart'; import 'package:outline_material_icons/outline_material_icons.dart';
import 'package:palette_generator/palette_generator.dart';
import 'package:path/path.dart'; import 'package:path/path.dart';
abstract class CollectionFilter { abstract class CollectionFilter implements Comparable<CollectionFilter> {
static const List<String> collectionFilterOrder = [ static const List<String> collectionFilterOrder = [
VideoFilter.type, VideoFilter.type,
GifFilter.type, GifFilter.type,
@ -20,11 +26,19 @@ abstract class CollectionFilter {
String get label; String get label;
Widget iconBuilder(BuildContext context); Widget iconBuilder(BuildContext context, double size);
Future<Color> color(BuildContext context) => SynchronousFuture(stringToColor(label));
String get typeKey; String get typeKey;
int get displayPriority => collectionFilterOrder.indexOf(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 { class AlbumFilter extends CollectionFilter {
@ -41,7 +55,23 @@ class AlbumFilter extends CollectionFilter {
String get label => album.split(separator).last; String get label => album.split(separator).last;
@override @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> 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 @override
String get typeKey => type; String get typeKey => type;
@ -70,7 +100,7 @@ class TagFilter extends CollectionFilter {
String get label => tag; String get label => tag;
@override @override
Widget iconBuilder(context) => Icon(OMIcons.localOffer); Widget iconBuilder(context, size) => Icon(OMIcons.localOffer, size: size);
@override @override
String get typeKey => type; String get typeKey => type;
@ -99,7 +129,7 @@ class CountryFilter extends CollectionFilter {
String get label => country; String get label => country;
@override @override
Widget iconBuilder(context) => Icon(OMIcons.place); Widget iconBuilder(context, size) => Icon(OMIcons.place, size: size);
@override @override
String get typeKey => type; String get typeKey => type;
@ -124,7 +154,7 @@ class VideoFilter extends CollectionFilter {
String get label => 'Video'; String get label => 'Video';
@override @override
Widget iconBuilder(context) => Icon(OMIcons.movie); Widget iconBuilder(context, size) => Icon(OMIcons.movie, size: size);
@override @override
String get typeKey => type; String get typeKey => type;
@ -149,7 +179,7 @@ class GifFilter extends CollectionFilter {
String get label => 'GIF'; String get label => 'GIF';
@override @override
Widget iconBuilder(context) => Icon(OMIcons.gif); Widget iconBuilder(context, size) => Icon(OMIcons.gif, size: size);
@override @override
String get typeKey => type; String get typeKey => type;
@ -178,7 +208,7 @@ class QueryFilter extends CollectionFilter {
String get label => '${query}'; String get label => '${query}';
@override @override
Widget iconBuilder(context) => Icon(OMIcons.formatQuote); Widget iconBuilder(context, size) => Icon(OMIcons.formatQuote, size: size);
@override @override
String get typeKey => type; String get typeKey => type;

View file

@ -52,7 +52,7 @@ class CollectionLens with ChangeNotifier {
source: source, source: source,
filters: [ filters: [
...filters, ...filters,
if (filter != null) filter, filter,
], ],
groupFactor: groupFactor, groupFactor: groupFactor,
sortFactor: sortFactor, sortFactor: sortFactor,
@ -71,6 +71,14 @@ class CollectionLens with ChangeNotifier {
Object heroTag(ImageEntry entry) => '$hashCode${entry.uri}'; 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) { void sort(SortFactor sortFactor) {
this.sortFactor = sortFactor; this.sortFactor = sortFactor;
_applySort(); _applySort();

View file

@ -82,7 +82,7 @@ class _CollectionDrawerState extends State<CollectionDrawer> {
); );
final buildAlbumEntry = (album) => _FilteredCollectionNavTile( final buildAlbumEntry = (album) => _FilteredCollectionNavTile(
source: source, source: source,
leading: IconUtils.getAlbumIcon(context, album), leading: IconUtils.getAlbumIcon(context: context, album: album),
title: CollectionSource.getUniqueAlbumName(album, source.sortedAlbums), title: CollectionSource.getUniqueAlbumName(album, source.sortedAlbums),
dense: true, dense: true,
filter: AlbumFilter(album), filter: AlbumFilter(album),

View file

@ -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/collection_drawer.dart';
import 'package:aves/widgets/album/filter_bar.dart'; import 'package:aves/widgets/album/filter_bar.dart';
import 'package:aves/widgets/album/thumbnail_collection.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/menu_row.dart';
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
import 'package:aves/widgets/stats.dart'; import 'package:aves/widgets/stats.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:outline_material_icons/outline_material_icons.dart'; import 'package:outline_material_icons/outline_material_icons.dart';
@ -27,7 +27,7 @@ class CollectionPage extends StatelessWidget {
: SliverAppBar( : SliverAppBar(
title: const Text('Aves'), title: const Text('Aves'),
actions: _buildActions(), actions: _buildActions(),
bottom: FilterBar(collection.filters), bottom: FilterBar(),
floating: true, floating: true,
), ),
), ),

View file

@ -4,7 +4,7 @@ import 'dart:ui' as ui;
import 'package:aves/model/image_entry.dart'; import 'package:aves/model/image_entry.dart';
import 'package:aves/widgets/album/collection_section.dart'; import 'package:aves/widgets/album/collection_section.dart';
import 'package:aves/widgets/album/thumbnail.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/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter_sticky_header/flutter_sticky_header.dart'; import 'package:flutter_sticky_header/flutter_sticky_header.dart';

View file

@ -162,7 +162,7 @@ class SectionHeader extends StatelessWidget {
} }
Widget _buildAlbumSectionHeader(BuildContext context) { 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) { if (albumIcon != null) {
albumIcon = Material( albumIcon = Material(
type: MaterialType.circle, type: MaterialType.circle,

View file

@ -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:aves/widgets/common/aves_filter_chip.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class FilterBar extends StatelessWidget implements PreferredSizeWidget { class FilterBar extends StatelessWidget implements PreferredSizeWidget {
final List<CollectionFilter> filters; static const EdgeInsets padding = EdgeInsets.symmetric(horizontal: 8);
final ScrollController _scrollController = ScrollController();
static const EdgeInsets padding = EdgeInsets.only(left: 8, right: 8, bottom: 8);
@override @override
final Size preferredSize = Size.fromHeight(kMinInteractiveDimension + padding.vertical); final Size preferredSize = Size.fromHeight(kMinInteractiveDimension + padding.vertical);
FilterBar(Set<CollectionFilter> 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
debugPrint('$runtimeType build');
final collection = Provider.of<CollectionLens>(context);
final filters = collection.filters.toList()..sort();
return Container( return Container(
// specify transparent as a workaround to prevent // specify transparent as a workaround to prevent
// chip border clipping when the floating app bar is fading // chip border clipping when the floating app bar is fading
@ -34,16 +27,18 @@ class FilterBar extends StatelessWidget implements PreferredSizeWidget {
onNotification: (notification) => true, onNotification: (notification) => true,
child: ListView.separated( child: ListView.separated(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
controller: _scrollController,
primary: false, primary: false,
physics: const BouncingScrollPhysics(), physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.all(AvesFilterChip.buttonBorderWidth / 2), padding: const EdgeInsets.all(AvesFilterChip.buttonBorderWidth / 2),
itemBuilder: (context, index) { itemBuilder: (context, index) {
if (index >= filters.length) return null; if (index >= filters.length) return null;
final filter = filters[index]; final filter = filters[index];
return AvesFilterChip( return Center(
child: AvesFilterChip(
filter, filter,
onPressed: (filter) {}, clearable: true,
onPressed: collection.removeFilter,
),
); );
}, },
separatorBuilder: (context, index) => const SizedBox(width: 8), separatorBuilder: (context, index) => const SizedBox(width: 8),

View file

@ -2,7 +2,7 @@ import 'package:aves/model/collection_filters.dart';
import 'package:aves/model/collection_lens.dart'; import 'package:aves/model/collection_lens.dart';
import 'package:aves/model/image_entry.dart'; import 'package:aves/model/image_entry.dart';
import 'package:aves/widgets/album/thumbnail_collection.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:flutter/material.dart';
import 'package:outline_material_icons/outline_material_icons.dart'; import 'package:outline_material_icons/outline_material_icons.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';

View file

@ -4,7 +4,7 @@ import 'package:aves/model/image_entry.dart';
import 'package:aves/utils/constants.dart'; import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/common/icons.dart'; import 'package:aves/widgets/common/icons.dart';
import 'package:aves/widgets/common/image_preview.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/material.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';

View file

@ -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<StatefulWidget> createState() => AppIconState();
}
class AppIconState extends State<AppIcon> {
Future<Uint8List> _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<Uint8List> 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();
},
);
}
}

View file

@ -1,45 +1,63 @@
import 'package:aves/model/collection_filters.dart'; import 'package:aves/model/collection_filters.dart';
import 'package:aves/utils/color_utils.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:outline_material_icons/outline_material_icons.dart';
typedef FilterCallback = void Function(CollectionFilter filter); typedef FilterCallback = void Function(CollectionFilter filter);
class AvesFilterChip extends StatelessWidget { class AvesFilterChip extends StatefulWidget {
final CollectionFilter filter; final CollectionFilter filter;
final bool clearable;
final FilterCallback onPressed; final FilterCallback onPressed;
String get label => filter.label;
static const double buttonBorderWidth = 2; static const double buttonBorderWidth = 2;
static const double maxChipWidth = 160; static const double maxChipWidth = 160;
static const double iconSize = 20;
static const double padding = 6;
const AvesFilterChip( const AvesFilterChip(
this.filter, { this.filter, {
this.clearable = false,
@required this.onPressed, @required this.onPressed,
}); });
@override
_AvesFilterChipState createState() => _AvesFilterChipState();
}
class _AvesFilterChipState extends State<AvesFilterChip> {
Future<Color> _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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final icon = filter.iconBuilder(context); final leading = filter.iconBuilder(context, AvesFilterChip.iconSize);
return ConstrainedBox( final trailing = widget.clearable ? Icon(OMIcons.clear, size: AvesFilterChip.iconSize) : null;
constraints: const BoxConstraints(maxWidth: maxChipWidth),
child: Tooltip( final child = Row(
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, mainAxisSize: MainAxisSize.min,
children: [ children: [
if (icon != null) ...[ if (leading != null) ...[
icon, leading,
const SizedBox(width: 8), const SizedBox(width: AvesFilterChip.padding * 1.6),
], ],
Flexible( Flexible(
child: Text( child: Text(
@ -49,8 +67,35 @@ class AvesFilterChip extends StatelessWidget {
maxLines: 1, maxLines: 1,
), ),
), ),
if (trailing != null) ...[
const SizedBox(width: AvesFilterChip.padding),
trailing,
], ],
],
);
final shape = RoundedRectangleBorder(
borderRadius: BorderRadius.circular(42),
);
return ConstrainedBox(
constraints: const BoxConstraints(maxWidth: AvesFilterChip.maxChipWidth),
child: Tooltip(
message: label,
child: FutureBuilder(
future: _colorFuture,
builder: (context, AsyncSnapshot<Color> 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,
);
},
), ),
), ),
); );

View file

@ -2,10 +2,9 @@ import 'dart:ui';
import 'package:aves/model/image_entry.dart'; import 'package:aves/model/image_entry.dart';
import 'package:aves/utils/android_file_utils.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:flutter/material.dart';
import 'package:outline_material_icons/outline_material_icons.dart'; import 'package:outline_material_icons/outline_material_icons.dart';
import 'package:provider/provider.dart';
class VideoIcon extends StatelessWidget { class VideoIcon extends StatelessWidget {
final ImageEntry entry; final ImageEntry entry;
@ -88,23 +87,27 @@ class OverlayIcon extends StatelessWidget {
} }
class IconUtils { class IconUtils {
static Widget getAlbumIcon(BuildContext context, String albumDirectory) { static Widget getAlbumIcon({
switch (androidFileUtils.getAlbumType(albumDirectory)) { @required BuildContext context,
@required String album,
double size = 24,
}) {
switch (androidFileUtils.getAlbumType(album)) {
case AlbumType.Camera: case AlbumType.Camera:
return Icon(OMIcons.photoCamera); return Icon(OMIcons.photoCamera, size: size);
case AlbumType.Screenshots: case AlbumType.Screenshots:
case AlbumType.ScreenRecordings: case AlbumType.ScreenRecordings:
return Icon(OMIcons.smartphone); return Icon(OMIcons.smartphone, size: size);
case AlbumType.Download: case AlbumType.Download:
return Icon(Icons.file_download); return Icon(Icons.file_download, size: size);
case AlbumType.App: case AlbumType.App:
return Selector<MediaQueryData, double>( return Image(
selector: (c, mq) => mq.devicePixelRatio, image: AppIconImage(
builder: (c, devicePixelRatio, child) => AppIcon( packageName: androidFileUtils.getAlbumAppPackageName(album),
packageName: androidFileUtils.getAlbumAppPackageName(albumDirectory), size: size,
size: IconTheme.of(context).size,
devicePixelRatio: devicePixelRatio,
), ),
width: size,
height: size,
); );
case AlbumType.Default: case AlbumType.Default:
default: default:

View file

@ -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<AppIconImageKey> {
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<AppIconImageKey> obtainKey(ImageConfiguration configuration) {
return SynchronousFuture<AppIconImageKey>(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<ui.Codec> _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);
}

View file

@ -47,7 +47,7 @@ class UriImage extends ImageProvider<UriImage> {
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
if (other.runtimeType != runtimeType) return false; 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 @override

View file

@ -4,9 +4,10 @@ import 'package:aves/model/image_metadata.dart';
import 'package:aves/model/metadata_db.dart'; import 'package:aves/model/metadata_db.dart';
import 'package:aves/model/settings.dart'; import 'package:aves/model/settings.dart';
import 'package:aves/utils/file_utils.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/material.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
import 'package:flutter_svg/flutter_svg.dart';
class DebugPage extends StatefulWidget { class DebugPage extends StatefulWidget {
final CollectionSource source; final CollectionSource source;
@ -86,6 +87,9 @@ class DebugPageState extends State<DebugPage> {
}, },
), ),
const Divider(), const Divider(),
Text('Image cache: ${imageCache.currentSize} items, ${formatFilesize(imageCache.currentSizeBytes)}'),
Text('SVG cache: ${PictureProvider.cacheCount} items'),
const Divider(),
const Text('Time dilation'), const Text('Time dilation'),
Slider( Slider(
value: timeDilation, value: timeDilation,

View file

@ -3,6 +3,7 @@ import 'dart:io';
import 'package:aves/model/collection_lens.dart'; import 'package:aves/model/collection_lens.dart';
import 'package:aves/model/image_entry.dart'; import 'package:aves/model/image_entry.dart';
import 'package:aves/utils/android_app_service.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:flushbar/flushbar.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pdf/widgets.dart' as pdf; import 'package:pdf/widgets.dart' as pdf;
@ -77,7 +78,7 @@ class FullscreenActionDelegate {
final doc = pdf.Document(title: entry.title); final doc = pdf.Document(title: entry.title);
final image = await pdfImageFromImageProvider( final image = await pdfImageFromImageProvider(
pdf: doc.document, 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 doc.addPage(pdf.Page(build: (context) => pdf.Center(child: pdf.Image(image)))); // Page
unawaited(Printing.layoutPdf( unawaited(Printing.layoutPdf(

View file

@ -3,13 +3,14 @@ import 'dart:math';
import 'package:aves/model/collection_lens.dart'; import 'package:aves/model/collection_lens.dart';
import 'package:aves/model/image_entry.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/fullscreen_action_delegate.dart';
import 'package:aves/widgets/fullscreen/image_page.dart'; import 'package:aves/widgets/fullscreen/image_page.dart';
import 'package:aves/widgets/fullscreen/info/info_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/bottom.dart';
import 'package:aves/widgets/fullscreen/overlay/top.dart'; import 'package:aves/widgets/fullscreen/overlay/top.dart';
import 'package:aves/widgets/fullscreen/overlay/video.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/material.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@ -126,14 +127,14 @@ class FullscreenBodyState extends State<FullscreenBody> with SingleTickerProvide
if (_currentVerticalPage.value == infoPage) { if (_currentVerticalPage.value == infoPage) {
// back from info to image // back from info to image
_goToVerticalPage(imagePage); _goToVerticalPage(imagePage);
return Future.value(false); return SynchronousFuture(false);
} }
if (!ModalRoute.of(context).canPop) { if (!ModalRoute.of(context).canPop) {
// exit app when trying to pop a fullscreen page that is a viewer for a single entry // exit app when trying to pop a fullscreen page that is a viewer for a single entry
exit(0); exit(0);
} }
_onLeave(); _onLeave();
return Future.value(true); return SynchronousFuture(true);
}, },
child: Stack( child: Stack(
children: [ children: [

View file

@ -1,6 +1,6 @@
import 'package:aves/model/collection_lens.dart'; import 'package:aves/model/collection_lens.dart';
import 'package:aves/model/image_entry.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:aves/widgets/fullscreen/fullscreen_body.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';

View file

@ -1,7 +1,7 @@
import 'package:aves/model/image_entry.dart'; import 'package:aves/model/image_entry.dart';
import 'package:aves/utils/constants.dart'; import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/fullscreen/uri_image_provider.dart'; import 'package:aves/widgets/common/image_providers/uri_image_provider.dart';
import 'package:aves/widgets/fullscreen/uri_picture_provider.dart'; import 'package:aves/widgets/common/image_providers/uri_picture_provider.dart';
import 'package:aves/widgets/fullscreen/video.dart'; import 'package:aves/widgets/fullscreen/video.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';

View file

@ -3,7 +3,7 @@ import 'package:aves/model/collection_lens.dart';
import 'package:aves/model/image_entry.dart'; import 'package:aves/model/image_entry.dart';
import 'package:aves/widgets/album/collection_page.dart'; import 'package:aves/widgets/album/collection_page.dart';
import 'package:aves/widgets/common/aves_filter_chip.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/basic_section.dart';
import 'package:aves/widgets/fullscreen/info/location_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/metadata_section.dart';
@ -155,11 +155,12 @@ class InfoPageState extends State<InfoPage> {
void _goToFilteredCollection(CollectionFilter filter) { void _goToFilteredCollection(CollectionFilter filter) {
if (collection == null) return; if (collection == null) return;
Navigator.push( Navigator.pushAndRemoveUntil(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => CollectionPage(collection.derive(filter)), builder: (context) => CollectionPage(collection.derive(filter)),
), ),
(route) => false,
); );
} }
} }

View file

@ -1,7 +1,7 @@
import 'package:aves/model/collection_lens.dart'; import 'package:aves/model/collection_lens.dart';
import 'package:aves/model/image_entry.dart'; import 'package:aves/model/image_entry.dart';
import 'package:aves/utils/color_utils.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:charts_flutter/flutter.dart' as charts;
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';

View file

@ -232,6 +232,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.1.1" 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: path:
dependency: "direct main" dependency: "direct main"
description: description:

View file

@ -40,6 +40,7 @@ dependencies:
outline_material_icons: outline_material_icons:
path: path:
pdf: pdf:
palette_generator:
pedantic: pedantic:
percent_indicator: percent_indicator:
permission_handler: permission_handler: