print: show feedback for multipage entries

This commit is contained in:
Thibault Deckers 2021-01-27 21:16:43 +09:00
parent 10b4ce6898
commit c7b6e17a7f
6 changed files with 37 additions and 28 deletions

View file

@ -111,7 +111,9 @@ class MediaStoreSource extends CollectionSource {
final uriByContentId = Map.fromEntries(changedUris.map((uri) { final uriByContentId = Map.fromEntries(changedUris.map((uri) {
if (uri == null) return null; if (uri == null) return null;
final idString = Uri.parse(uri).pathSegments.last; 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)); }).where((kv) => kv != null));
// clean up obsolete entries // clean up obsolete entries

View file

@ -79,14 +79,14 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
if (!await checkFreeSpaceForMove(context, selection, destinationAlbum, moveType)) return; if (!await checkFreeSpaceForMove(context, selection, destinationAlbum, moveType)) return;
final copy = moveType == MoveType.copy; final copy = moveType == MoveType.copy;
final selectionCount = selection.length;
showOpReport<MoveOpEvent>( showOpReport<MoveOpEvent>(
context: context, context: context,
selection: selection,
opStream: ImageFileService.move(selection, copy: copy, destinationAlbum: destinationAlbum), opStream: ImageFileService.move(selection, copy: copy, destinationAlbum: destinationAlbum),
itemCount: selectionCount,
onDone: (processed) async { onDone: (processed) async {
final movedOps = processed.where((e) => e.success); final movedOps = processed.where((e) => e.success);
final movedCount = movedOps.length; final movedCount = movedOps.length;
final selectionCount = selection.length;
if (movedCount < selectionCount) { if (movedCount < selectionCount) {
final count = selectionCount - movedCount; final count = selectionCount - movedCount;
showFeedback(context, 'Failed to ${copy ? 'copy' : 'move'} ${Intl.plural(count, one: '$count item', other: '$count items')}'); 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; if (!await checkStoragePermission(context, selection)) return;
final selectionCount = selection.length;
showOpReport<ImageOpEvent>( showOpReport<ImageOpEvent>(
context: context, context: context,
selection: selection,
opStream: ImageFileService.delete(selection), opStream: ImageFileService.delete(selection),
itemCount: selectionCount,
onDone: (processed) { onDone: (processed) {
final deletedUris = processed.where((e) => e.success).map((e) => e.uri).toList(); final deletedUris = processed.where((e) => e.success).map((e) => e.uri).toList();
final deletedCount = deletedUris.length; final deletedCount = deletedUris.length;
final selectionCount = selection.length;
if (deletedCount < selectionCount) { if (deletedCount < selectionCount) {
final count = selectionCount - deletedCount; final count = selectionCount - deletedCount;
showFeedback(context, 'Failed to delete ${Intl.plural(count, one: '$count item', other: '$count items')}'); showFeedback(context, 'Failed to delete ${Intl.plural(count, one: '$count item', other: '$count items')}');

View file

@ -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:aves/theme/durations.dart';
import 'package:flushbar/flushbar.dart'; import 'package:flushbar/flushbar.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -27,22 +25,20 @@ mixin FeedbackMixin {
// report overlay for multiple operations // report overlay for multiple operations
OverlayEntry _opReportOverlayEntry; void showOpReport<T>({
void showOpReport<T extends ImageOpEvent>({
@required BuildContext context, @required BuildContext context,
@required Set<AvesEntry> selection,
@required Stream<T> opStream, @required Stream<T> opStream,
@required void Function(Set<T> processed) onDone, @required int itemCount,
void Function(Set<T> processed) onDone,
}) { }) {
OverlayEntry _opReportOverlayEntry;
_opReportOverlayEntry = OverlayEntry( _opReportOverlayEntry = OverlayEntry(
builder: (context) => ReportOverlay<T>( builder: (context) => ReportOverlay<T>(
opStream: opStream, opStream: opStream,
itemCount: selection.length, itemCount: itemCount,
onDone: (processed) { onDone: (processed) {
_opReportOverlayEntry?.remove(); _opReportOverlayEntry.remove();
_opReportOverlayEntry = null; onDone?.call(processed);
onDone(processed);
}, },
), ),
); );

View file

@ -79,14 +79,14 @@ class AlbumChipActionDelegate extends ChipActionDelegate with FeedbackMixin, Per
if (!await checkStoragePermission(context, selection)) return; if (!await checkStoragePermission(context, selection)) return;
final selectionCount = selection.length;
showOpReport<ImageOpEvent>( showOpReport<ImageOpEvent>(
context: context, context: context,
selection: selection,
opStream: ImageFileService.delete(selection), opStream: ImageFileService.delete(selection),
itemCount: selectionCount,
onDone: (processed) { onDone: (processed) {
final deletedUris = processed.where((e) => e.success).map((e) => e.uri).toList(); final deletedUris = processed.where((e) => e.success).map((e) => e.uri).toList();
final deletedCount = deletedUris.length; final deletedCount = deletedUris.length;
final selectionCount = selection.length;
if (deletedCount < selectionCount) { if (deletedCount < selectionCount) {
final count = selectionCount - deletedCount; final count = selectionCount - deletedCount;
showFeedback(context, 'Failed to delete ${Intl.plural(count, one: '$count item', other: '$count items')}'); 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; if (!await checkFreeSpaceForMove(context, selection, destinationAlbum, MoveType.move)) return;
final selectionCount = selection.length;
showOpReport<MoveOpEvent>( showOpReport<MoveOpEvent>(
context: context, context: context,
selection: selection,
opStream: ImageFileService.move(selection, copy: false, destinationAlbum: destinationAlbum), opStream: ImageFileService.move(selection, copy: false, destinationAlbum: destinationAlbum),
itemCount: selectionCount,
onDone: (processed) async { onDone: (processed) async {
final movedOps = processed.where((e) => e.success); final movedOps = processed.where((e) => e.success);
final movedCount = movedOps.length; final movedCount = movedOps.length;
final selectionCount = selection.length;
if (movedCount < selectionCount) { if (movedCount < selectionCount) {
final count = selectionCount - movedCount; final count = selectionCount - movedCount;
showFeedback(context, 'Failed to move ${Intl.plural(count, one: '$count item', other: '$count items')}'); showFeedback(context, 'Failed to move ${Intl.plural(count, one: '$count item', other: '$count items')}');

View file

@ -54,7 +54,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
_showRenameDialog(context, entry); _showRenameDialog(context, entry);
break; break;
case EntryAction.print: case EntryAction.print:
EntryPrinter(entry).print(); EntryPrinter(entry).print(context);
break; break;
case EntryAction.rotateCCW: case EntryAction.rotateCCW:
_rotate(context, entry, clockwise: false); _rotate(context, entry, clockwise: false);
@ -181,14 +181,14 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
selection.add(entry); selection.add(entry);
} }
final selectionCount = selection.length;
showOpReport<ExportOpEvent>( showOpReport<ExportOpEvent>(
context: context, context: context,
selection: selection,
opStream: ImageFileService.export(selection, destinationAlbum: destinationAlbum), opStream: ImageFileService.export(selection, destinationAlbum: destinationAlbum),
itemCount: selectionCount,
onDone: (processed) { onDone: (processed) {
final movedOps = processed.where((e) => e.success); final movedOps = processed.where((e) => e.success);
final movedCount = movedOps.length; final movedCount = movedOps.length;
final selectionCount = selection.length;
if (movedCount < selectionCount) { if (movedCount < selectionCount) {
final count = selectionCount - movedCount; final count = selectionCount - movedCount;
showFeedback(context, 'Failed to export ${Intl.plural(count, one: '$count page', other: '$count pages')}'); showFeedback(context, 'Failed to export ${Intl.plural(count, one: '$count page', other: '$count pages')}');

View file

@ -1,23 +1,26 @@
import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:aves/model/entry.dart'; import 'package:aves/model/entry.dart';
import 'package:aves/model/entry_images.dart'; import 'package:aves/model/entry_images.dart';
import 'package:aves/services/image_file_service.dart'; import 'package:aves/services/image_file_service.dart';
import 'package:aves/services/metadata_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:pdf/widgets.dart' as pdf;
import 'package:pedantic/pedantic.dart'; import 'package:pedantic/pedantic.dart';
import 'package:printing/printing.dart'; import 'package:printing/printing.dart';
class EntryPrinter { class EntryPrinter with FeedbackMixin {
final AvesEntry entry; final AvesEntry entry;
const EntryPrinter(this.entry); EntryPrinter(this.entry);
Future<void> print() async { Future<void> print(BuildContext context) async {
final documentName = entry.bestTitle ?? 'Aves'; final documentName = entry.bestTitle ?? 'Aves';
final doc = pdf.Document(title: documentName); final doc = pdf.Document(title: documentName);
final pages = await _buildPages(); final pages = await _buildPages(context);
if (pages.isNotEmpty) { if (pages.isNotEmpty) {
pages.forEach(doc.addPage); // Page pages.forEach(doc.addPage); // Page
unawaited(Printing.layoutPdf( unawaited(Printing.layoutPdf(
@ -27,7 +30,7 @@ class EntryPrinter {
} }
} }
Future<List<pdf.Page>> _buildPages() async { Future<List<pdf.Page>> _buildPages(BuildContext context) async {
final pages = <pdf.Page>[]; final pages = <pdf.Page>[];
void _addPdfPage(pdf.Widget pdfChild) { void _addPdfPage(pdf.Widget pdfChild) {
@ -47,10 +50,18 @@ class EntryPrinter {
if (entry.isMultipage) { if (entry.isMultipage) {
final multiPageInfo = await MetadataService.getMultiPageInfo(entry); final multiPageInfo = await MetadataService.getMultiPageInfo(entry);
if (multiPageInfo.pageCount > 1) { if (multiPageInfo.pageCount > 1) {
final streamController = StreamController<AvesEntry>.broadcast();
showOpReport<AvesEntry>(
context: context,
opStream: streamController.stream,
itemCount: multiPageInfo.pageCount,
);
for (final page in multiPageInfo.pages) { for (final page in multiPageInfo.pages) {
final pageEntry = entry.getPageEntry(page); final pageEntry = entry.getPageEntry(page);
_addPdfPage(await _buildPageImage(pageEntry)); _addPdfPage(await _buildPageImage(pageEntry));
streamController.sink.add(pageEntry);
} }
await streamController.close();
} }
} }
if (pages.isEmpty) { if (pages.isEmpty) {