// Dart imports: import 'dart:async'; import 'dart:convert'; // Flutter imports: import 'package:flutter/material.dart'; // Package imports: import 'package:built_collection/built_collection.dart'; import 'package:flutter_redux/flutter_redux.dart'; import 'package:http/http.dart'; import 'package:invoiceninja_flutter/redux/document/document_actions.dart'; import 'package:url_launcher/url_launcher.dart'; // Project imports: import 'package:invoiceninja_flutter/constants.dart'; import 'package:invoiceninja_flutter/data/models/models.dart'; import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/design/design_selectors.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:http/http.dart' as http; import 'package:invoiceninja_flutter/data/web_client.dart'; import 'package:printing/printing.dart'; class ViewCreditList implements PersistUI { ViewCreditList({this.force = false}); final bool force; } class ViewCredit implements PersistUI, PersistPrefs { ViewCredit({ this.creditId, this.force = false, }); final String creditId; final bool force; } class EditCredit implements PersistUI, PersistPrefs { EditCredit( {this.credit, this.creditItemIndex, this.completer, this.force = false}); final InvoiceEntity credit; final int creditItemIndex; final Completer completer; final bool force; } class ShowEmailCredit { ShowEmailCredit({this.credit, this.context, this.completer}); final InvoiceEntity credit; final BuildContext context; final Completer completer; } class ShowPdfCredit { ShowPdfCredit({this.credit, this.context, this.activityId}); final InvoiceEntity credit; final BuildContext context; final String activityId; } class EditCreditItem implements PersistUI { EditCreditItem([this.creditItemIndex]); final int creditItemIndex; } class UpdateCredit implements PersistUI { UpdateCredit(this.credit); final InvoiceEntity credit; } class UpdateCreditClient implements PersistUI { UpdateCreditClient({this.client}); final ClientEntity client; } class LoadCredit { LoadCredit({this.completer, this.creditId}); final Completer completer; final String creditId; } class LoadCredits { LoadCredits({this.completer}); final Completer completer; } class LoadCreditRequest implements StartLoading {} class LoadCreditFailure implements StopLoading { LoadCreditFailure(this.error); final dynamic error; @override String toString() { return 'LoadCreditFailure{error: $error}'; } } class LoadCreditSuccess implements StopLoading, PersistData { LoadCreditSuccess(this.credit); final InvoiceEntity credit; @override String toString() { return 'LoadCreditSuccess{credit: $credit}'; } } class LoadCreditsRequest implements StartLoading {} class LoadCreditsFailure implements StopLoading { LoadCreditsFailure(this.error); final dynamic error; @override String toString() { return 'LoadCreditsFailure{error: $error}'; } } class LoadCreditsSuccess implements StopLoading { LoadCreditsSuccess(this.credits); final BuiltList credits; @override String toString() { return 'LoadCreditsSuccess{credits: $credits}'; } } class AddCreditContact implements PersistUI { AddCreditContact({this.contact, this.invitation}); final ClientContactEntity contact; final InvitationEntity invitation; } class RemoveCreditContact implements PersistUI { RemoveCreditContact({this.invitation}); final InvitationEntity invitation; } class AddCreditItem implements PersistUI { AddCreditItem({this.creditItem}); final InvoiceItemEntity creditItem; } class MoveCreditItem implements PersistUI { MoveCreditItem({ this.oldIndex, this.newIndex, }); final int oldIndex; final int newIndex; } class AddCreditItems implements PersistUI { AddCreditItems(this.creditItems); final List creditItems; } class UpdateCreditItem implements PersistUI { UpdateCreditItem({this.index, this.creditItem}); final int index; final InvoiceItemEntity creditItem; } class DeleteCreditItem implements PersistUI { DeleteCreditItem(this.index); final int index; } class SaveCreditRequest implements StartSaving { SaveCreditRequest({ @required this.completer, @required this.credit, @required this.action, }); final Completer completer; final InvoiceEntity credit; final EntityAction action; } class SaveCreditSuccess implements StopSaving, PersistData, PersistUI { SaveCreditSuccess(this.credit); final InvoiceEntity credit; } class AddCreditSuccess implements StopSaving, PersistData, PersistUI { AddCreditSuccess(this.credit); final InvoiceEntity credit; } class SaveCreditFailure implements StopSaving { SaveCreditFailure(this.error); final Object error; } class EmailCreditRequest implements StartSaving { EmailCreditRequest( {this.completer, this.creditId, this.template, this.subject, this.body}); final Completer completer; final String creditId; final EmailTemplate template; final String subject; final String body; } class EmailCreditSuccess implements StopSaving, PersistData {} class EmailCreditFailure implements StopSaving { EmailCreditFailure(this.error); final dynamic error; } class MarkSentCreditRequest implements StartSaving { MarkSentCreditRequest(this.completer, this.creditIds); final Completer completer; final List creditIds; } class MarkSentCreditSuccess implements StopSaving, PersistData { MarkSentCreditSuccess(this.credits); final List credits; } class MarkSentCreditFailure implements StopSaving { MarkSentCreditFailure(this.error); final Object error; } class BulkEmailCreditsRequest implements StartSaving { BulkEmailCreditsRequest(this.completer, this.creditIds); final Completer completer; final List creditIds; } class BulkEmailCreditsSuccess implements StopSaving, PersistData { BulkEmailCreditsSuccess(this.credits); final List credits; } class BulkEmailCreditsFailure implements StopSaving { BulkEmailCreditsFailure(this.error); final dynamic error; } class MarkCreditsPaidRequest implements StartSaving { MarkCreditsPaidRequest(this.completer, this.invoiceIds); final Completer completer; final List invoiceIds; } class MarkCreditsPaidSuccess implements StopSaving { MarkCreditsPaidSuccess(this.invoices); final List invoices; } class MarkCreditsPaidFailure implements StopSaving { MarkCreditsPaidFailure(this.error); final dynamic error; } class ArchiveCreditsRequest implements StartSaving { ArchiveCreditsRequest(this.completer, this.creditIds); final Completer completer; final List creditIds; } class ArchiveCreditsSuccess implements StopSaving, PersistData { ArchiveCreditsSuccess(this.credits); final List credits; } class ArchiveCreditsFailure implements StopSaving { ArchiveCreditsFailure(this.credits); final List credits; } class DeleteCreditsRequest implements StartSaving { DeleteCreditsRequest(this.completer, this.creditIds); final Completer completer; final List creditIds; } class DeleteCreditsSuccess implements StopSaving, PersistData { DeleteCreditsSuccess(this.credits); final List credits; } class DeleteCreditsFailure implements StopSaving { DeleteCreditsFailure(this.credits); final List credits; } class DownloadCreditsRequest implements StartSaving { DownloadCreditsRequest(this.completer, this.creditIds); final Completer completer; final List creditIds; } class DownloadCreditsSuccess implements StopSaving {} class DownloadCreditsFailure implements StopSaving { DownloadCreditsFailure(this.error); final Object error; } class RestoreCreditsRequest implements StartSaving { RestoreCreditsRequest(this.completer, this.creditIds); final Completer completer; final List creditIds; } class RestoreCreditsSuccess implements StopSaving, PersistData { RestoreCreditsSuccess(this.credits); final List credits; } class RestoreCreditsFailure implements StopSaving { RestoreCreditsFailure(this.credits); final List credits; } class FilterCredits implements PersistUI { FilterCredits(this.filter); final String filter; } class SortCredits implements PersistUI, PersistPrefs { SortCredits(this.field); final String field; } class FilterCreditsByState implements PersistUI { FilterCreditsByState(this.state); final EntityState state; } class FilterCreditsByStatus implements PersistUI { FilterCreditsByStatus(this.status); final EntityStatus status; } class FilterCreditDropdown { FilterCreditDropdown(this.filter); final String filter; } class FilterCreditsByCustom1 implements PersistUI { FilterCreditsByCustom1(this.value); final String value; } class FilterCreditsByCustom2 implements PersistUI { FilterCreditsByCustom2(this.value); final String value; } class FilterCreditsByCustom3 implements PersistUI { FilterCreditsByCustom3(this.value); final String value; } class FilterCreditsByCustom4 implements PersistUI { FilterCreditsByCustom4(this.value); final String value; } class SaveCreditDocumentRequest implements StartSaving { SaveCreditDocumentRequest({ @required this.completer, @required this.multipartFile, @required this.credit, }); final Completer completer; final MultipartFile multipartFile; final InvoiceEntity credit; } class SaveCreditDocumentSuccess implements StopSaving, PersistData, PersistUI { SaveCreditDocumentSuccess(this.document); final DocumentEntity document; } class SaveCreditDocumentFailure implements StopSaving { SaveCreditDocumentFailure(this.error); final Object error; } Future handleCreditAction( BuildContext context, List credits, EntityAction action) async { final store = StoreProvider.of(context); final state = store.state; final localization = AppLocalization.of(context); final credit = credits.first as InvoiceEntity; final creditIds = credits.map((credit) => credit.id).toList(); switch (action) { case EntityAction.edit: editEntity(entity: credit); break; case EntityAction.viewPdf: store.dispatch(ShowPdfCredit(credit: credit, context: context)); break; case EntityAction.clientPortal: launchUrl(Uri.parse(credit.invitationSilentLink)); break; case EntityAction.markSent: store.dispatch(MarkSentCreditRequest( snackBarCompleter(context, localization.markedCreditAsSent), creditIds)); break; case EntityAction.sendEmail: case EntityAction.bulkSendEmail: bool emailValid = true; credits.forEach((credit) { final client = state.clientState.get( (credit as InvoiceEntity).clientId, ); if (!client.hasEmailAddress) { emailValid = false; } }); if (!emailValid) { showMessageDialog( context: context, message: localization.clientEmailNotSet, secondaryActions: [ TextButton( onPressed: () { Navigator.of(context).pop(); editEntity(entity: state.clientState.get(credit.clientId)); }, child: Text(localization.editClient.toUpperCase())) ]); return; } if (action == EntityAction.sendEmail) { store.dispatch(ShowEmailCredit( completer: snackBarCompleter(context, localization.emailedCredit), credit: credit, context: context)); } else { confirmCallback( context: context, message: localization.bulkEmailCredits, callback: (_) { store.dispatch(BulkEmailCreditsRequest( snackBarCompleter( context, creditIds.length == 1 ? localization.emailedCredit : localization.emailedCredits), creditIds)); }); } break; case EntityAction.cloneToPurchaseOrder: final designId = getDesignIdForVendorByEntity( state: state, vendorId: credit.vendorId, entityType: EntityType.purchaseOrder); createEntity( context: context, entity: credit.clone.rebuild((b) => b ..entityType = EntityType.purchaseOrder ..designId = designId)); break; case EntityAction.cloneToOther: cloneToDialog(context: context, invoice: credit); break; case EntityAction.cloneToInvoice: final designId = getDesignIdForClientByEntity( state: state, clientId: credit.clientId, entityType: EntityType.invoice); createEntity( context: context, entity: credit.clone.rebuild((b) => b ..entityType = EntityType.invoice ..designId = designId)); break; case EntityAction.cloneToQuote: final designId = getDesignIdForClientByEntity( state: state, clientId: credit.clientId, entityType: EntityType.quote); createEntity( context: context, entity: credit.clone.rebuild((b) => b ..entityType = EntityType.quote ..designId = designId)); break; case EntityAction.clone: case EntityAction.cloneToCredit: createEntity(context: context, entity: credit.clone); break; case EntityAction.cloneToRecurring: final designId = getDesignIdForClientByEntity( state: state, clientId: credit.clientId, entityType: EntityType.invoice); createEntity( context: context, entity: credit.clone.rebuild((b) => b ..entityType = EntityType.recurringInvoice ..designId = designId)); break; case EntityAction.markPaid: store.dispatch(MarkCreditsPaidRequest( snackBarCompleter( context, credits.length == 1 ? localization.markedCreditAsPaid : localization.markedCreditsAsPaid), creditIds)); break; case EntityAction.applyCredit: createEntity( context: context, entity: PaymentEntity( state: state, client: state.clientState.get(credit.clientId)) .rebuild((b) => b ..typeId = kPaymentTypeCredit ..credits.addAll(credits .map((credit) => PaymentableEntity.fromCredit(credit)) .toList())), ); break; case EntityAction.download: launchUrl(Uri.parse(credit.invitationDownloadLink)); break; case EntityAction.bulkDownload: store.dispatch(DownloadCreditsRequest( snackBarCompleter(context, localization.exportedData), creditIds)); break; case EntityAction.restore: final message = creditIds.length > 1 ? localization.restoredCredits .replaceFirst(':value', creditIds.length.toString()) : localization.restoredCredit; store.dispatch(RestoreCreditsRequest( snackBarCompleter(context, message), creditIds)); break; case EntityAction.archive: final message = creditIds.length > 1 ? localization.archivedCredits .replaceFirst(':value', creditIds.length.toString()) : localization.archivedCredit; store.dispatch(ArchiveCreditsRequest( snackBarCompleter(context, message), creditIds)); break; case EntityAction.delete: final message = creditIds.length > 1 ? localization.deletedCredits .replaceFirst(':value', creditIds.length.toString()) : localization.deletedCredit; store.dispatch(DeleteCreditsRequest( snackBarCompleter(context, message), creditIds)); break; case EntityAction.toggleMultiselect: if (!store.state.creditListState.isInMultiselect()) { store.dispatch(StartCreditMultiselect()); } for (final credit in credits) { if (!store.state.creditListState.isSelected(credit.id)) { store.dispatch(AddToCreditMultiselect(entity: credit)); } else { store.dispatch(RemoveFromCreditMultiselect(entity: credit)); } } break; case EntityAction.printPdf: final invitation = credit.invitations.first; final url = invitation.downloadLink; store.dispatch(StartSaving()); final http.Response response = await WebClient().get(url, '', rawResponse: true); store.dispatch(StopSaving()); await Printing.layoutPdf(onLayout: (_) => response.bodyBytes); break; case EntityAction.bulkPrint: store.dispatch(StartSaving()); final url = state.credentials.url + '/credits/bulk'; final data = json.encode( {'ids': creditIds, 'action': EntityAction.bulkPrint.toApiParam()}); final http.Response response = await WebClient() .post(url, state.credentials.token, data: data, rawResponse: true); store.dispatch(StopSaving()); await Printing.layoutPdf(onLayout: (_) => response.bodyBytes); break; case EntityAction.more: showEntityActionsDialog( entities: [credit], ); break; case EntityAction.documents: final documentIds = []; for (var credit in credits) { for (var document in (credit as InvoiceEntity).documents) { documentIds.add(document.id); } } store.dispatch( DownloadDocumentsRequest( documentIds: documentIds, completer: snackBarCompleter( context, localization.exportedData, ), ), ); break; default: print('## ERROR: unhandled action $action in credit_actions'); break; } } class StartCreditMultiselect {} class AddToCreditMultiselect { AddToCreditMultiselect({@required this.entity}); final BaseEntity entity; } class RemoveFromCreditMultiselect { RemoveFromCreditMultiselect({@required this.entity}); final BaseEntity entity; } class ClearCreditMultiselect {} class UpdateCreditTab implements PersistUI { UpdateCreditTab({this.tabIndex}); final int tabIndex; }