print: show feedback for multipage entries
This commit is contained in:
parent
10b4ce6898
commit
c7b6e17a7f
6 changed files with 37 additions and 28 deletions
|
@ -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
|
||||||
|
|
|
@ -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')}');
|
||||||
|
|
|
@ -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);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -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')}');
|
||||||
|
|
|
@ -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')}');
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue