invoice/lib/redux/document/document_actions.dart

578 lines
18 KiB
Dart

// Dart imports:
import 'dart:async';
import 'dart:io' as file;
import 'dart:io';
// Flutter imports:
import 'package:built_collection/built_collection.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
// Package imports:
import 'package:flutter_redux/flutter_redux.dart';
import 'package:flutter_styled_toast/flutter_styled_toast.dart';
// Project imports:
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/main_app.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/redux/auth/auth_actions.dart';
import 'package:invoiceninja_flutter/redux/client/client_actions.dart';
import 'package:invoiceninja_flutter/redux/credit/credit_actions.dart';
import 'package:invoiceninja_flutter/redux/expense/expense_actions.dart';
import 'package:invoiceninja_flutter/redux/group/group_actions.dart';
import 'package:invoiceninja_flutter/redux/invoice/invoice_actions.dart';
import 'package:invoiceninja_flutter/redux/product/product_actions.dart';
import 'package:invoiceninja_flutter/redux/project/project_actions.dart';
import 'package:invoiceninja_flutter/redux/purchase_order/purchase_order_actions.dart';
import 'package:invoiceninja_flutter/redux/quote/quote_actions.dart';
import 'package:invoiceninja_flutter/redux/recurring_expense/recurring_expense_actions.dart';
import 'package:invoiceninja_flutter/redux/recurring_invoice/recurring_invoice_actions.dart';
import 'package:invoiceninja_flutter/redux/task/task_actions.dart';
import 'package:invoiceninja_flutter/redux/vendor/vendor_actions.dart';
import 'package:invoiceninja_flutter/ui/app/entities/entity_actions_dialog.dart';
import 'package:invoiceninja_flutter/utils/completers.dart';
import 'package:invoiceninja_flutter/utils/dialogs.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:invoiceninja_flutter/utils/platforms.dart';
import 'package:path_provider/path_provider.dart';
import 'package:pinch_zoom/pinch_zoom.dart';
import 'package:printing/printing.dart';
import 'package:invoiceninja_flutter/utils/web_stub.dart'
if (dart.library.html) 'package:invoiceninja_flutter/utils/web.dart';
import 'package:share_plus/share_plus.dart';
class ViewDocumentList implements PersistUI {
ViewDocumentList({
this.force = false,
this.page = 0,
});
final bool force;
final int page;
}
class ViewDocument implements PersistUI {
ViewDocument({this.documentId, this.force});
final String? documentId;
final bool? force;
}
class EditDocument implements PersistUI {
EditDocument({
this.document,
this.completer,
});
final DocumentEntity? document;
final Completer? completer;
}
class UpdateDocument implements PersistUI {
UpdateDocument(this.document);
final DocumentEntity document;
}
class LoadDocument {
LoadDocument({this.completer, this.documentId});
final Completer? completer;
final String? documentId;
}
class LoadDocumentData {
LoadDocumentData({this.completer, this.documentId});
final Completer? completer;
final String? documentId;
}
class LoadDocumentActivity {
LoadDocumentActivity({this.completer, this.documentId});
final Completer? completer;
final String? documentId;
}
class LoadDocuments {
LoadDocuments({this.completer});
final Completer? completer;
}
class LoadDocumentRequest implements StartLoading {}
class LoadDocumentFailure implements StopLoading {
LoadDocumentFailure(this.error);
final dynamic error;
@override
String toString() {
return 'LoadDocumentFailure{error: $error}';
}
}
class LoadDocumentSuccess implements StopLoading, PersistData {
LoadDocumentSuccess(this.document);
final DocumentEntity document;
@override
String toString() {
return 'LoadDocumentSuccess{document: $document}';
}
}
class LoadDocumentDataRequest implements StartLoading {}
class LoadDocumentsRequest implements StartLoading {}
class LoadDocumentsFailure implements StopLoading {
LoadDocumentsFailure(this.error);
final dynamic error;
@override
String toString() {
return 'LoadDocumentsFailure{error: $error}';
}
}
class LoadDocumentsSuccess implements StopLoading {
LoadDocumentsSuccess(this.documents);
final List<DocumentEntity> documents;
@override
String toString() {
return 'LoadDocumentsSuccess{documents: $documents}';
}
}
class SaveDocumentRequest implements StartSaving {
SaveDocumentRequest({
required this.completer,
required this.document,
});
final Completer completer;
final DocumentEntity? document;
}
class SaveDocumentSuccess implements StopSaving, PersistData, PersistUI {
SaveDocumentSuccess(this.document);
final DocumentEntity document;
}
class AddDocumentSuccess implements StopSaving, PersistData, PersistUI {
AddDocumentSuccess(this.documents, this.parentType, this.parentId);
final BuiltList<DocumentEntity> documents;
final EntityType parentType;
final String parentId;
}
class SaveDocumentFailure implements StopSaving {
SaveDocumentFailure(this.error);
final Object error;
}
class DownloadDocumentsRequest implements StartSaving {
DownloadDocumentsRequest({this.completer, this.documentIds});
final Completer? completer;
final List<String>? documentIds;
}
class DownloadDocumentsSuccess implements StopSaving {}
class DownloadDocumentsFailure implements StopSaving {
DownloadDocumentsFailure(this.error);
final Object error;
}
class ArchiveDocumentRequest implements StartSaving {
ArchiveDocumentRequest(this.completer, this.documentIds);
final Completer completer;
final List<String> documentIds;
}
class ArchiveDocumentSuccess implements StopSaving, PersistData {
ArchiveDocumentSuccess(this.documents);
final List<DocumentEntity> documents;
}
class ArchiveDocumentFailure implements StopSaving {
ArchiveDocumentFailure(this.documents);
final List<DocumentEntity?> documents;
}
class DeleteDocumentRequest implements StartSaving {
DeleteDocumentRequest({
required this.completer,
required this.documentIds,
required this.password,
required this.idToken,
});
final Completer completer;
final List<String> documentIds;
final String? password;
final String? idToken;
}
class DeleteDocumentSuccess
implements StopSaving, PersistData, UserVerifiedPassword {
DeleteDocumentSuccess({this.documentId});
final String? documentId;
//DeleteDocumentSuccess(this.documents);
//final List<DocumentEntity> documents;
}
class DeleteDocumentFailure implements StopSaving {
//DeleteDocumentFailure(this.documents);
//final List<DocumentEntity> documents;
}
class RestoreDocumentRequest implements StartSaving {
RestoreDocumentRequest(this.completer, this.documentIds);
final Completer completer;
final List<String> documentIds;
}
class RestoreDocumentSuccess implements StopSaving, PersistData {
RestoreDocumentSuccess(this.documents);
final List<DocumentEntity> documents;
}
class RestoreDocumentFailure implements StopSaving {
RestoreDocumentFailure(this.documents);
final List<DocumentEntity?> documents;
}
class FilterDocuments implements PersistUI {
FilterDocuments(this.filter);
final String? filter;
}
class FilterDocumentsByStatus implements PersistUI {
FilterDocumentsByStatus(this.status);
final EntityStatus status;
}
class SortDocuments implements PersistUI, PersistPrefs {
SortDocuments(this.field);
final String field;
}
class FilterDocumentsByState implements PersistUI {
FilterDocumentsByState(this.state);
final EntityState state;
}
class FilterDocumentsByCustom1 implements PersistUI {
FilterDocumentsByCustom1(this.value);
final String value;
}
class FilterDocumentsByCustom2 implements PersistUI {
FilterDocumentsByCustom2(this.value);
final String value;
}
class FilterDocumentsByCustom3 implements PersistUI {
FilterDocumentsByCustom3(this.value);
final String value;
}
class FilterDocumentsByCustom4 implements PersistUI {
FilterDocumentsByCustom4(this.value);
final String value;
}
void handleDocumentAction(
BuildContext? context, List<BaseEntity> documents, EntityAction? action) {
if (documents.isEmpty) {
return;
}
final store = StoreProvider.of<AppState>(context!);
final localization = AppLocalization.of(context);
final documentIds = documents.map((document) => document.id).toList();
final document = store.state.documentState.map[documentIds.first]!;
switch (action) {
case EntityAction.edit:
editEntity(entity: document);
break;
case EntityAction.restore:
final message = documentIds.length > 1
? localization!.restoredDocuments
.replaceFirst(':value', ':count')
.replaceFirst(':count', documentIds.length.toString())
: localization!.restoredDocument;
store.dispatch(RestoreDocumentRequest(
snackBarCompleter<Null>(message), documentIds));
break;
case EntityAction.archive:
final message = documentIds.length > 1
? localization!.archivedDocuments
.replaceFirst(':value', ':count')
.replaceFirst(':count', documentIds.length.toString())
: localization!.archivedDocument;
store.dispatch(ArchiveDocumentRequest(
snackBarCompleter<Null>(message), documentIds));
break;
/*
case EntityAction.delete:
final message = documentIds.length > 1
? localization.deletedDocuments
.replaceFirst(':value', ':count').replaceFirst(':count', documentIds.length.toString())
: localization.deletedDocument;
store.dispatch(DeleteDocumentRequest(
completer: snackBarCompleter<Null>( message),
documentIds: documentIds,
));
break;
*/
case EntityAction.toggleMultiselect:
if (!store.state.documentListState.isInMultiselect()) {
store.dispatch(StartDocumentMultiselect());
}
if (documents.isEmpty) {
break;
}
for (final document in documents) {
if (!store.state.documentListState.isSelected(document.id)) {
store.dispatch(AddToDocumentMultiselect(entity: document));
} else {
store.dispatch(RemoveFromDocumentMultiselect(entity: document));
}
}
break;
case EntityAction.more:
showEntityActionsDialog(
entities: [document],
);
break;
case EntityAction.bulkDownload:
store.dispatch(
DownloadDocumentsRequest(
documentIds: documentIds,
completer: snackBarCompleter<Null>(
localization!.exportedData,
),
),
);
break;
case EntityAction.viewDocument:
void showDocument() {
showDialog<void>(
context: navigatorKey.currentContext!,
builder: (context) {
final DocumentEntity document =
store.state.documentState.map[documentIds.first]!;
return AlertDialog(
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(localization!.close.toUpperCase())),
],
content: document.isImage
? PinchZoom(
child: Image.memory(document.data!),
)
: SizedBox(
width: 600,
child: PdfPreview(
build: (format) => document.data!,
canChangeOrientation: false,
canChangePageFormat: false,
allowPrinting: false,
allowSharing: false,
canDebug: false,
),
),
);
});
}
if (document.data == null) {
store.dispatch(LoadDocumentData(
documentId: document.id,
completer: Completer<void>()
..future.then((value) => showDocument())));
} else {
showDocument();
}
break;
case EntityAction.download:
void downloadDocument() async {
final DocumentEntity? document =
store.state.documentState.map[documentIds.first];
if (kIsWeb) {
if (document?.data != null) {
WebUtils.downloadBinaryFile(document!.name, document.data!);
}
} else {
final directory = await (isDesktopOS()
? getDownloadsDirectory() as FutureOr<file.Directory>
: getApplicationDocumentsDirectory());
String filePath =
'${directory.path}${file.Platform.pathSeparator}${document!.name}';
if (file.File(filePath).existsSync()) {
final extension = document.name.split('.').last;
final timestamp = DateTime.now().millisecondsSinceEpoch;
filePath =
filePath.replaceFirst('.$extension', '_$timestamp.$extension');
}
await File(filePath).writeAsBytes(document.data!);
if (isDesktopOS()) {
showToast(localization!.fileSavedInPath
.replaceFirst(':path', directory.path));
} else {
await Share.shareXFiles([XFile(filePath)]);
}
}
}
if (document.data == null) {
store.dispatch(LoadDocumentData(
documentId: document.id,
completer: Completer<void>()
..future.then((value) => downloadDocument())));
} else {
downloadDocument();
}
break;
case EntityAction.delete:
confirmCallback(
context: context,
callback: (_) {
passwordCallback(
context: context,
callback: (password, idToken) {
final completer = snackBarCompleter<Null>(
AppLocalization.of(context)!.deletedDocument);
switch (document.parentType) {
case EntityType.client:
completer.future.then<Null>((_) => store
.dispatch(LoadClient(clientId: document.parentId)));
break;
case EntityType.credit:
completer.future.then<Null>((_) => store
.dispatch(LoadCredit(creditId: document.parentId)));
break;
case EntityType.expense:
completer.future.then<Null>((_) => store
.dispatch(LoadExpense(expenseId: document.parentId)));
break;
case EntityType.group:
completer.future.then<Null>((_) => store
.dispatch(LoadGroup(groupId: document.parentId)));
break;
case EntityType.invoice:
completer.future.then<Null>((_) => store
.dispatch(LoadInvoice(invoiceId: document.parentId)));
break;
case EntityType.product:
completer.future.then<Null>((_) => store
.dispatch(LoadProduct(productId: document.parentId)));
break;
case EntityType.project:
completer.future.then<Null>((_) => store
.dispatch(LoadProject(projectId: document.parentId)));
break;
case EntityType.purchaseOrder:
completer.future.then<Null>((_) => store.dispatch(
LoadPurchaseOrder(
purchaseOrderId: document.parentId)));
break;
case EntityType.quote:
completer.future.then<Null>((_) => store
.dispatch(LoadQuote(quoteId: document.parentId)));
break;
case EntityType.recurringExpense:
completer.future.then<Null>((_) => store.dispatch(
LoadRecurringExpense(
recurringExpenseId: document.parentId)));
break;
case EntityType.recurringInvoice:
completer.future.then<Null>((_) => store.dispatch(
LoadRecurringInvoice(
recurringInvoiceId: document.parentId)));
break;
case EntityType.task:
completer.future.then<Null>((_) =>
store.dispatch(LoadTask(taskId: document.parentId)));
break;
case EntityType.vendor:
completer.future.then<Null>((_) => store
.dispatch(LoadVendor(vendorId: document.parentId)));
break;
default:
completer.future
.then<Null>((_) => store.dispatch(RefreshData()));
}
completer.future
.then<Null>((_) => store.dispatch(RefreshData()));
store.dispatch(DeleteDocumentRequest(
completer: completer,
documentIds: [document.id],
password: password,
idToken: idToken,
));
});
});
break;
default:
print('## ERROR: unhandled action $action in document_actions');
break;
}
}
class StartDocumentMultiselect {}
class AddToDocumentMultiselect {
AddToDocumentMultiselect({required this.entity});
final BaseEntity? entity;
}
class RemoveFromDocumentMultiselect {
RemoveFromDocumentMultiselect({required this.entity});
final BaseEntity? entity;
}
class ClearDocumentMultiselect {}