// Dart imports: import 'dart:async'; // 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/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 ViewQuoteList implements PersistUI { ViewQuoteList({this.force = false}); final bool force; } class ViewQuote implements PersistUI, PersistPrefs { ViewQuote({ this.quoteId, this.force = false, }); final String quoteId; final bool force; } class EditQuote implements PersistUI, PersistPrefs { EditQuote( {this.quote, this.quoteItemIndex, this.completer, this.force = false}); final InvoiceEntity quote; final int quoteItemIndex; final Completer completer; final bool force; } class ShowEmailQuote { ShowEmailQuote({this.quote, this.context, this.completer}); final InvoiceEntity quote; final BuildContext context; final Completer completer; } class ShowPdfQuote { ShowPdfQuote({this.quote, this.context, this.activityId}); final InvoiceEntity quote; final BuildContext context; final String activityId; } class EditQuoteItem implements PersistUI { EditQuoteItem([this.quoteItemIndex]); final int quoteItemIndex; } class UpdateQuote implements PersistUI { UpdateQuote(this.quote); final InvoiceEntity quote; } class UpdateQuoteClient implements PersistUI { UpdateQuoteClient({this.client}); final ClientEntity client; } class LoadQuote { LoadQuote({this.completer, this.quoteId}); final Completer completer; final String quoteId; } class LoadQuotes { LoadQuotes({this.completer, this.page = 1}); final Completer completer; final int page; } class LoadQuoteRequest implements StartLoading {} class LoadQuoteFailure implements StopLoading { LoadQuoteFailure(this.error); final dynamic error; @override String toString() { return 'LoadQuoteFailure{error: $error}'; } } class LoadQuoteSuccess implements StopLoading, PersistData { LoadQuoteSuccess(this.quote); final InvoiceEntity quote; @override String toString() { return 'LoadQuoteSuccess{quote: $quote}'; } } class LoadQuotesRequest implements StartLoading {} class LoadQuotesFailure implements StopLoading { LoadQuotesFailure(this.error); final dynamic error; @override String toString() { return 'LoadQuotesFailure{error: $error}'; } } class LoadQuotesSuccess implements StopLoading { LoadQuotesSuccess(this.quotes); final BuiltList quotes; @override String toString() { return 'LoadQuotesSuccess{quotes: $quotes}'; } } class AddQuoteContact implements PersistUI { AddQuoteContact({this.contact, this.invitation}); final ClientContactEntity contact; final InvitationEntity invitation; } class RemoveQuoteContact implements PersistUI { RemoveQuoteContact({this.invitation}); final InvitationEntity invitation; } class AddQuoteItem implements PersistUI { AddQuoteItem({this.quoteItem}); final InvoiceItemEntity quoteItem; } class MoveQuoteItem implements PersistUI { MoveQuoteItem({ this.oldIndex, this.newIndex, }); final int oldIndex; final int newIndex; } class AddQuoteItems implements PersistUI { AddQuoteItems(this.quoteItems); final List quoteItems; } class UpdateQuoteItem implements PersistUI { UpdateQuoteItem({this.index, this.quoteItem}); final int index; final InvoiceItemEntity quoteItem; } class DeleteQuoteItem implements PersistUI { DeleteQuoteItem(this.index); final int index; } class SaveQuoteRequest implements StartSaving { SaveQuoteRequest({ @required this.completer, @required this.quote, @required this.action, }); final Completer completer; final InvoiceEntity quote; final EntityAction action; } class SaveQuoteSuccess implements StopSaving, PersistData, PersistUI { SaveQuoteSuccess(this.quote); final InvoiceEntity quote; } class AddQuoteSuccess implements StopSaving, PersistData, PersistUI { AddQuoteSuccess(this.quote); final InvoiceEntity quote; } class SaveQuoteFailure implements StopSaving { SaveQuoteFailure(this.error); final Object error; } class EmailQuoteRequest implements StartSaving { EmailQuoteRequest( {this.completer, this.quoteId, this.template, this.subject, this.body}); final Completer completer; final String quoteId; final EmailTemplate template; final String subject; final String body; } class EmailQuoteSuccess implements StopSaving, PersistData { EmailQuoteSuccess(this.quote); final InvoiceEntity quote; } class EmailQuoteFailure implements StopSaving { EmailQuoteFailure(this.error); final dynamic error; } class MarkSentQuotesRequest implements StartSaving { MarkSentQuotesRequest(this.completer, this.quoteIds); final Completer completer; final List quoteIds; } class MarkSentQuoteSuccess implements StopSaving, PersistData { MarkSentQuoteSuccess(this.quotes); final List quotes; } class MarkSentQuoteFailure implements StopSaving { MarkSentQuoteFailure(this.error); final Object error; } class BulkEmailQuotesRequest implements StartSaving { BulkEmailQuotesRequest(this.completer, this.quoteIds); final Completer completer; final List quoteIds; } class BulkEmailQuotesSuccess implements StopSaving, PersistData { BulkEmailQuotesSuccess(this.quotes); final List quotes; } class BulkEmailQuotesFailure implements StopSaving { BulkEmailQuotesFailure(this.error); final dynamic error; } class ArchiveQuotesRequest implements StartSaving { ArchiveQuotesRequest(this.completer, this.quoteIds); final Completer completer; final List quoteIds; } class ArchiveQuotesSuccess implements StopSaving, PersistData { ArchiveQuotesSuccess(this.quotes); final List quotes; } class ArchiveQuotesFailure implements StopSaving { ArchiveQuotesFailure(this.quotes); final List quotes; } class DeleteQuotesRequest implements StartSaving { DeleteQuotesRequest(this.completer, this.quoteIds); final Completer completer; final List quoteIds; } class DeleteQuotesSuccess implements StopSaving, PersistData { DeleteQuotesSuccess(this.quotes); final List quotes; } class DeleteQuotesFailure implements StopSaving { DeleteQuotesFailure(this.quotes); final List quotes; } class DownloadQuotesRequest implements StartSaving { DownloadQuotesRequest(this.completer, this.invoiceIds); final Completer completer; final List invoiceIds; } class DownloadQuotesSuccess implements StopSaving {} class DownloadQuotesFailure implements StopSaving { DownloadQuotesFailure(this.error); final Object error; } class RestoreQuotesRequest implements StartSaving { RestoreQuotesRequest(this.completer, this.quoteIds); final Completer completer; final List quoteIds; } class RestoreQuotesSuccess implements StopSaving, PersistData { RestoreQuotesSuccess(this.quotes); final List quotes; } class RestoreQuotesFailure implements StopSaving { RestoreQuotesFailure(this.quotes); final List quotes; } class FilterQuotes implements PersistUI { FilterQuotes(this.filter); final String filter; } class SortQuotes implements PersistUI, PersistPrefs { SortQuotes(this.field); final String field; } class FilterQuotesByState implements PersistUI { FilterQuotesByState(this.state); final EntityState state; } class FilterQuotesByStatus implements PersistUI { FilterQuotesByStatus(this.status); final EntityStatus status; } class FilterQuoteDropdown { FilterQuoteDropdown(this.filter); final String filter; } class FilterQuotesByCustom1 implements PersistUI { FilterQuotesByCustom1(this.value); final String value; } class FilterQuotesByCustom2 implements PersistUI { FilterQuotesByCustom2(this.value); final String value; } class FilterQuotesByCustom3 implements PersistUI { FilterQuotesByCustom3(this.value); final String value; } class FilterQuotesByCustom4 implements PersistUI { FilterQuotesByCustom4(this.value); final String value; } class ConvertQuotes implements StartSaving { ConvertQuotes(this.completer, this.quoteIds); final List quoteIds; final Completer completer; } class ConvertQuoteSuccess implements StopSaving { ConvertQuoteSuccess({this.quotes}); final List quotes; } class ConvertQuoteFailure implements StopSaving { ConvertQuoteFailure(this.error); final dynamic error; } class ApproveQuotes implements StartSaving { ApproveQuotes(this.completer, this.quoteIds); final List quoteIds; final Completer completer; } class ApproveQuoteSuccess implements StopSaving { ApproveQuoteSuccess({this.quotes}); final List quotes; } class ApproveQuoteFailure implements StopSaving { ApproveQuoteFailure(this.error); final dynamic error; } class SaveQuoteDocumentRequest implements StartSaving { SaveQuoteDocumentRequest({ @required this.completer, @required this.multipartFile, @required this.quote, }); final Completer completer; final MultipartFile multipartFile; final InvoiceEntity quote; } class SaveQuoteDocumentSuccess implements StopSaving, PersistData, PersistUI { SaveQuoteDocumentSuccess(this.document); final DocumentEntity document; } class SaveQuoteDocumentFailure implements StopSaving { SaveQuoteDocumentFailure(this.error); final Object error; } Future handleQuoteAction( BuildContext context, List quotes, EntityAction action) async { final store = StoreProvider.of(context); final state = store.state; final localization = AppLocalization.of(context); final quote = quotes.first as InvoiceEntity; final quoteIds = quotes.map((quote) => quote.id).toList(); switch (action) { case EntityAction.edit: editEntity(entity: quote); break; case EntityAction.viewPdf: store.dispatch(ShowPdfQuote(quote: quote, context: context)); break; case EntityAction.clientPortal: if (await canLaunch(quote.invitationSilentLink)) { await launch(quote.invitationSilentLink, forceSafariVC: false, forceWebView: false); } break; case EntityAction.convertToInvoice: store.dispatch(ConvertQuotes( snackBarCompleter(context, localization.convertedQuote), quoteIds)); break; case EntityAction.approve: final message = quoteIds.length > 1 ? localization.approvedQuotes .replaceFirst(':value', quoteIds.length.toString()) : localization.approveQuote; store.dispatch( ApproveQuotes(snackBarCompleter(context, message), quoteIds)); break; case EntityAction.viewInvoice: viewEntityById(entityId: quote.invoiceId, entityType: EntityType.invoice); break; case EntityAction.markSent: store.dispatch(MarkSentQuotesRequest( snackBarCompleter(context, localization.markedQuoteAsSent), quoteIds)); break; case EntityAction.sendEmail: case EntityAction.bulkSendEmail: bool emailValid = true; quotes.forEach((quote) { final client = state.clientState.get( (quote 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(quote.clientId)); }, child: Text(localization.editClient.toUpperCase())) ]); return; } if (action == EntityAction.sendEmail) { store.dispatch(ShowEmailQuote( completer: snackBarCompleter(context, localization.emailedQuote), quote: quote, context: context)); } else { confirmCallback( context: context, message: localization.bulkEmailQuotes, callback: (_) { store.dispatch(BulkEmailQuotesRequest( snackBarCompleter( context, quoteIds.length == 1 ? localization.emailedQuote : localization.emailedQuotes), quoteIds)); }); } break; case EntityAction.cloneToPurchaseOrder: final designId = getDesignIdForVendorByEntity( state: state, vendorId: quote.vendorId, entityType: EntityType.purchaseOrder); createEntity( context: context, entity: quote.clone.rebuild((b) => b ..entityType = EntityType.purchaseOrder ..designId = designId)); break; case EntityAction.cloneToOther: cloneToDialog(context: context, invoice: quote); break; case EntityAction.cloneToInvoice: final designId = getDesignIdForClientByEntity( state: state, clientId: quote.clientId, entityType: EntityType.invoice); createEntity( context: context, entity: quote.clone.rebuild((b) => b ..entityType = EntityType.invoice ..designId = designId)); break; case EntityAction.clone: case EntityAction.cloneToQuote: createEntity(context: context, entity: quote.clone); break; case EntityAction.cloneToCredit: final designId = getDesignIdForClientByEntity( state: state, clientId: quote.clientId, entityType: EntityType.credit); createEntity( context: context, entity: quote.clone.rebuild((b) => b ..entityType = EntityType.credit ..designId = designId)); break; case EntityAction.cloneToRecurring: final designId = getDesignIdForClientByEntity( state: state, clientId: quote.clientId, entityType: EntityType.invoice); createEntity( context: context, entity: quote.clone.rebuild((b) => b ..entityType = EntityType.recurringInvoice ..designId = designId)); break; case EntityAction.download: launch(quote.invitationDownloadLink); break; case EntityAction.bulkDownload: store.dispatch(DownloadQuotesRequest( snackBarCompleter(context, localization.exportedData), quoteIds)); break; case EntityAction.restore: final message = quoteIds.length > 1 ? localization.restoredQuotes .replaceFirst(':value', quoteIds.length.toString()) : localization.restoredQuote; store.dispatch(RestoreQuotesRequest( snackBarCompleter(context, message), quoteIds)); break; case EntityAction.archive: final message = quoteIds.length > 1 ? localization.archivedQuotes .replaceFirst(':value', quoteIds.length.toString()) : localization.archivedQuote; store.dispatch(ArchiveQuotesRequest( snackBarCompleter(context, message), quoteIds)); break; case EntityAction.delete: final message = quoteIds.length > 1 ? localization.deletedQuotes .replaceFirst(':value', quoteIds.length.toString()) : localization.deletedQuote; store.dispatch(DeleteQuotesRequest( snackBarCompleter(context, message), quoteIds)); break; case EntityAction.toggleMultiselect: if (!store.state.quoteListState.isInMultiselect()) { store.dispatch(StartQuoteMultiselect()); } for (final quote in quotes) { if (!store.state.quoteListState.isSelected(quote.id)) { store.dispatch(AddToQuoteMultiselect(entity: quote)); } else { store.dispatch(RemoveFromQuoteMultiselect(entity: quote)); } } break; case EntityAction.printPdf: final invitation = quote.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.more: showEntityActionsDialog( entities: [quote], ); break; case EntityAction.documents: final documentIds = []; for (var quote in quotes) { for (var document in (quote 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 quote_actions'); break; } } class StartQuoteMultiselect {} class AddToQuoteMultiselect { AddToQuoteMultiselect({@required this.entity}); final BaseEntity entity; } class RemoveFromQuoteMultiselect { RemoveFromQuoteMultiselect({@required this.entity}); final BaseEntity entity; } class ClearQuoteMultiselect {} class UpdateQuoteTab implements PersistUI { UpdateQuoteTab({this.tabIndex}); final int tabIndex; }