diff --git a/lib/model/source/media_store_source.dart b/lib/model/source/media_store_source.dart index 838ddd9e7..ea0cd40f2 100644 --- a/lib/model/source/media_store_source.dart +++ b/lib/model/source/media_store_source.dart @@ -111,7 +111,9 @@ class MediaStoreSource extends CollectionSource { final uriByContentId = Map.fromEntries(changedUris.map((uri) { if (uri == null) return null; final idString = Uri.parse(uri).pathSegments.last; - return MapEntry(int.tryParse(idString), uri); + final contentId = int.tryParse(idString); + if (contentId == null) return null; + return MapEntry(contentId, uri); }).where((kv) => kv != null)); // clean up obsolete entries diff --git a/lib/widgets/collection/entry_set_action_delegate.dart b/lib/widgets/collection/entry_set_action_delegate.dart index 36d96fcd2..6c3276361 100644 --- a/lib/widgets/collection/entry_set_action_delegate.dart +++ b/lib/widgets/collection/entry_set_action_delegate.dart @@ -79,14 +79,14 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware if (!await checkFreeSpaceForMove(context, selection, destinationAlbum, moveType)) return; final copy = moveType == MoveType.copy; + final selectionCount = selection.length; showOpReport( context: context, - selection: selection, opStream: ImageFileService.move(selection, copy: copy, destinationAlbum: destinationAlbum), + itemCount: selectionCount, onDone: (processed) async { final movedOps = processed.where((e) => e.success); final movedCount = movedOps.length; - final selectionCount = selection.length; if (movedCount < selectionCount) { final count = selectionCount - movedCount; showFeedback(context, 'Failed to ${copy ? 'copy' : 'move'} ${Intl.plural(count, one: '$count item', other: '$count items')}'); @@ -132,14 +132,14 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware if (!await checkStoragePermission(context, selection)) return; + final selectionCount = selection.length; showOpReport( context: context, - selection: selection, opStream: ImageFileService.delete(selection), + itemCount: selectionCount, onDone: (processed) { final deletedUris = processed.where((e) => e.success).map((e) => e.uri).toList(); final deletedCount = deletedUris.length; - final selectionCount = selection.length; if (deletedCount < selectionCount) { final count = selectionCount - deletedCount; showFeedback(context, 'Failed to delete ${Intl.plural(count, one: '$count item', other: '$count items')}'); diff --git a/lib/widgets/common/action_mixins/feedback.dart b/lib/widgets/common/action_mixins/feedback.dart index 3a0120985..4a1743e68 100644 --- a/lib/widgets/common/action_mixins/feedback.dart +++ b/lib/widgets/common/action_mixins/feedback.dart @@ -1,5 +1,3 @@ -import 'package:aves/model/entry.dart'; -import 'package:aves/services/image_op_events.dart'; import 'package:aves/theme/durations.dart'; import 'package:flushbar/flushbar.dart'; import 'package:flutter/material.dart'; @@ -27,22 +25,20 @@ mixin FeedbackMixin { // report overlay for multiple operations - OverlayEntry _opReportOverlayEntry; - - void showOpReport({ + void showOpReport({ @required BuildContext context, - @required Set selection, @required Stream opStream, - @required void Function(Set processed) onDone, + @required int itemCount, + void Function(Set processed) onDone, }) { + OverlayEntry _opReportOverlayEntry; _opReportOverlayEntry = OverlayEntry( builder: (context) => ReportOverlay( opStream: opStream, - itemCount: selection.length, + itemCount: itemCount, onDone: (processed) { - _opReportOverlayEntry?.remove(); - _opReportOverlayEntry = null; - onDone(processed); + _opReportOverlayEntry.remove(); + onDone?.call(processed); }, ), ); diff --git a/lib/widgets/filter_grids/common/chip_action_delegate.dart b/lib/widgets/filter_grids/common/chip_action_delegate.dart index 5dfeba406..3d220401b 100644 --- a/lib/widgets/filter_grids/common/chip_action_delegate.dart +++ b/lib/widgets/filter_grids/common/chip_action_delegate.dart @@ -79,14 +79,14 @@ class AlbumChipActionDelegate extends ChipActionDelegate with FeedbackMixin, Per if (!await checkStoragePermission(context, selection)) return; + final selectionCount = selection.length; showOpReport( context: context, - selection: selection, opStream: ImageFileService.delete(selection), + itemCount: selectionCount, onDone: (processed) { final deletedUris = processed.where((e) => e.success).map((e) => e.uri).toList(); final deletedCount = deletedUris.length; - final selectionCount = selection.length; if (deletedCount < selectionCount) { final count = selectionCount - deletedCount; showFeedback(context, 'Failed to delete ${Intl.plural(count, one: '$count item', other: '$count items')}'); @@ -113,14 +113,14 @@ class AlbumChipActionDelegate extends ChipActionDelegate with FeedbackMixin, Per if (!await checkFreeSpaceForMove(context, selection, destinationAlbum, MoveType.move)) return; + final selectionCount = selection.length; showOpReport( context: context, - selection: selection, opStream: ImageFileService.move(selection, copy: false, destinationAlbum: destinationAlbum), + itemCount: selectionCount, onDone: (processed) async { final movedOps = processed.where((e) => e.success); final movedCount = movedOps.length; - final selectionCount = selection.length; if (movedCount < selectionCount) { final count = selectionCount - movedCount; showFeedback(context, 'Failed to move ${Intl.plural(count, one: '$count item', other: '$count items')}'); diff --git a/lib/widgets/viewer/entry_action_delegate.dart b/lib/widgets/viewer/entry_action_delegate.dart index d201c698f..566405e0c 100644 --- a/lib/widgets/viewer/entry_action_delegate.dart +++ b/lib/widgets/viewer/entry_action_delegate.dart @@ -54,7 +54,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix _showRenameDialog(context, entry); break; case EntryAction.print: - EntryPrinter(entry).print(); + EntryPrinter(entry).print(context); break; case EntryAction.rotateCCW: _rotate(context, entry, clockwise: false); @@ -181,14 +181,14 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix selection.add(entry); } + final selectionCount = selection.length; showOpReport( context: context, - selection: selection, opStream: ImageFileService.export(selection, destinationAlbum: destinationAlbum), + itemCount: selectionCount, onDone: (processed) { final movedOps = processed.where((e) => e.success); final movedCount = movedOps.length; - final selectionCount = selection.length; if (movedCount < selectionCount) { final count = selectionCount - movedCount; showFeedback(context, 'Failed to export ${Intl.plural(count, one: '$count page', other: '$count pages')}'); diff --git a/lib/widgets/viewer/printer.dart b/lib/widgets/viewer/printer.dart index d774f5d38..dee300e6f 100644 --- a/lib/widgets/viewer/printer.dart +++ b/lib/widgets/viewer/printer.dart @@ -1,23 +1,26 @@ +import 'dart:async'; import 'dart:convert'; import 'package:aves/model/entry.dart'; import 'package:aves/model/entry_images.dart'; import 'package:aves/services/image_file_service.dart'; import 'package:aves/services/metadata_service.dart'; +import 'package:aves/widgets/common/action_mixins/feedback.dart'; +import 'package:flutter/widgets.dart'; import 'package:pdf/widgets.dart' as pdf; import 'package:pedantic/pedantic.dart'; import 'package:printing/printing.dart'; -class EntryPrinter { +class EntryPrinter with FeedbackMixin { final AvesEntry entry; - const EntryPrinter(this.entry); + EntryPrinter(this.entry); - Future print() async { + Future print(BuildContext context) async { final documentName = entry.bestTitle ?? 'Aves'; final doc = pdf.Document(title: documentName); - final pages = await _buildPages(); + final pages = await _buildPages(context); if (pages.isNotEmpty) { pages.forEach(doc.addPage); // Page unawaited(Printing.layoutPdf( @@ -27,7 +30,7 @@ class EntryPrinter { } } - Future> _buildPages() async { + Future> _buildPages(BuildContext context) async { final pages = []; void _addPdfPage(pdf.Widget pdfChild) { @@ -47,10 +50,18 @@ class EntryPrinter { if (entry.isMultipage) { final multiPageInfo = await MetadataService.getMultiPageInfo(entry); if (multiPageInfo.pageCount > 1) { + final streamController = StreamController.broadcast(); + showOpReport( + context: context, + opStream: streamController.stream, + itemCount: multiPageInfo.pageCount, + ); for (final page in multiPageInfo.pages) { final pageEntry = entry.getPageEntry(page); _addPdfPage(await _buildPageImage(pageEntry)); + streamController.sink.add(pageEntry); } + await streamController.close(); } } if (pages.isEmpty) {