diff --git a/lib/data/repositories/settings_repository.dart b/lib/data/repositories/settings_repository.dart index b4a8706a01d..2a30cc86618 100644 --- a/lib/data/repositories/settings_repository.dart +++ b/lib/data/repositories/settings_repository.dart @@ -38,7 +38,7 @@ class SettingsRepository { } Future saveEInvoiceCertificate(Credentials credentials, - CompanyEntity company, MultipartFile? eInvoiceCertificate) async { + CompanyEntity company, MultipartFile eInvoiceCertificate) async { dynamic response; final url = credentials.url! + '/companies/${company.id}'; @@ -205,7 +205,7 @@ class SettingsRepository { } Future uploadLogo(Credentials credentials, String entityId, - MultipartFile? multipartFile, EntityType? type) async { + MultipartFile multipartFile, EntityType? type) async { final route = type == EntityType.company ? 'companies' : type == EntityType.group diff --git a/lib/data/web_client.dart b/lib/data/web_client.dart index d7393685d02..96a18fa78fe 100644 --- a/lib/data/web_client.dart +++ b/lib/data/web_client.dart @@ -68,7 +68,7 @@ class WebClient { String url, String? token, { dynamic data, - List? multipartFiles, + List? multipartFiles, String? secret, String? password, String? idToken, @@ -309,12 +309,12 @@ String _parseError(int code, String response) { } Future _uploadFiles( - String url, String? token, List multipartFiles, + String url, String? token, List multipartFiles, {String method = 'POST', dynamic data}) async { final request = http.MultipartRequest(method, Uri.parse(url)) ..fields.addAll(data ?? {}) ..headers.addAll(_getHeaders(url, token)) - ..files.addAll(multipartFiles as Iterable); + ..files.addAll(multipartFiles); return await http.Response.fromStream(await request.send()) .timeout(const Duration(minutes: 10)); diff --git a/lib/redux/app/app_actions.dart b/lib/redux/app/app_actions.dart index 028eb2229a1..b0ba340abb1 100644 --- a/lib/redux/app/app_actions.dart +++ b/lib/redux/app/app_actions.dart @@ -188,6 +188,7 @@ class UpdateUserPreferences implements PersistPrefs { this.enableTooltips, this.flexibleSearch, this.enableNativeBrowser, + this.downloadsFolder, this.statementIncludes, }); @@ -219,6 +220,7 @@ class UpdateUserPreferences implements PersistPrefs { final bool? enableTooltips; final bool? flexibleSearch; final bool? enableNativeBrowser; + final String? downloadsFolder; final BuiltList? statementIncludes; } diff --git a/lib/redux/company/company_actions.dart b/lib/redux/company/company_actions.dart index 6aca277306d..4e5ed878786 100644 --- a/lib/redux/company/company_actions.dart +++ b/lib/redux/company/company_actions.dart @@ -58,14 +58,14 @@ class SaveCompanyFailure implements StopSaving { class SaveEInvoiceCertificateRequest implements StartSaving { SaveEInvoiceCertificateRequest({ - this.completer, - this.company, - this.eInvoiceCertificate, + required this.completer, + required this.company, + required this.eInvoiceCertificate, }); - final Completer? completer; - final CompanyEntity? company; - final MultipartFile? eInvoiceCertificate; + final Completer completer; + final CompanyEntity company; + final MultipartFile eInvoiceCertificate; } class SaveEInvoiceCertificateSuccess diff --git a/lib/redux/settings/settings_actions.dart b/lib/redux/settings/settings_actions.dart index e7c532773c0..f158bc7623c 100644 --- a/lib/redux/settings/settings_actions.dart +++ b/lib/redux/settings/settings_actions.dart @@ -71,10 +71,10 @@ class UpdateUserSettings implements PersistUI { } class UploadLogoRequest implements StartSaving { - UploadLogoRequest({this.completer, this.multipartFile, this.type}); + UploadLogoRequest({this.completer, required this.multipartFile, this.type}); final Completer? completer; - final MultipartFile? multipartFile; + final MultipartFile multipartFile; final EntityType? type; } diff --git a/lib/redux/settings/settings_middleware.dart b/lib/redux/settings/settings_middleware.dart index d34a46e7c3a..1766a753551 100644 --- a/lib/redux/settings/settings_middleware.dart +++ b/lib/redux/settings/settings_middleware.dart @@ -126,16 +126,16 @@ Middleware _saveEInvoiceCertificate( settingsRepository .saveEInvoiceCertificate( store.state.credentials, - action.company!, + action.company, action.eInvoiceCertificate, ) .then((company) { store.dispatch(SaveEInvoiceCertificateSuccess(company)); - action.completer!.complete(); + action.completer.complete(); }).catchError((Object error) { print(error); store.dispatch(SaveEInvoiceCertificateFailure(error)); - action.completer!.completeError(error); + action.completer.completeError(error); }); next(action); diff --git a/lib/redux/ui/pref_reducer.dart b/lib/redux/ui/pref_reducer.dart index 35d419d5862..b2c43077c63 100644 --- a/lib/redux/ui/pref_reducer.dart +++ b/lib/redux/ui/pref_reducer.dart @@ -93,6 +93,7 @@ PrefState prefReducer( longPressReducer(state.longPressSelectionIsDefault, action) ..tapSelectedToEdit = tapSelectedToEditReducer(state.tapSelectedToEdit, action) + ..donwloadsFolder = downloadsFolderReducer(state.donwloadsFolder, action) ..requireAuthentication = requireAuthenticationReducer(state.requireAuthentication, action) ..colorTheme = colorThemeReducer(state.colorTheme, action) @@ -412,6 +413,12 @@ Reducer tapSelectedToEditReducer = combineReducers([ }), ]); +Reducer downloadsFolderReducer = combineReducers([ + TypedReducer((downloadsFolder, action) { + return action.downloadsFolder ?? downloadsFolder; + }), +]); + Reducer isPreviewVisibleReducer = combineReducers([ TypedReducer((value, action) { return !value; diff --git a/lib/ui/reports/client_report.dart b/lib/ui/reports/client_report.dart index 5a73a4288d7..79db920e9b5 100644 --- a/lib/ui/reports/client_report.dart +++ b/lib/ui/reports/client_report.dart @@ -78,6 +78,7 @@ enum ClientReportFields { routing_id, tax_exempt, classification, + record_state, } var memoizedClientReport = memo6(( @@ -366,6 +367,9 @@ ReportResult clientReport( value = AppLocalization.of(navigatorKey.currentContext!)! .lookup(client.classification); break; + case ClientReportFields.record_state: + value = AppLocalization.of(navigatorKey.currentContext!)! + .lookup(client.entityState); } if (!ReportResult.matchField( diff --git a/lib/ui/reports/contact_report.dart b/lib/ui/reports/contact_report.dart index 29195e743a1..87873d7d9fa 100644 --- a/lib/ui/reports/contact_report.dart +++ b/lib/ui/reports/contact_report.dart @@ -1,7 +1,9 @@ // Package imports: import 'package:built_collection/built_collection.dart'; import 'package:collection/collection.dart' show IterableNullableExtension; +import 'package:invoiceninja_flutter/main_app.dart'; import 'package:invoiceninja_flutter/redux/reports/reports_selectors.dart'; +import 'package:invoiceninja_flutter/utils/localization.dart'; import 'package:memoize/memoize.dart'; // Project imports: @@ -67,6 +69,7 @@ enum ContactReportFields { is_active, created_at, updated_at, + record_state, } var memoizedContactReport = memo5(( @@ -339,6 +342,10 @@ ReportResult contactReport( case ContactReportFields.created_at: value = convertTimestampToDateString(client.createdAt); break; + case ContactReportFields.record_state: + value = AppLocalization.of(navigatorKey.currentContext!)! + .lookup(client.entityState); + break; } if (!ReportResult.matchField( diff --git a/lib/ui/reports/credit_item_report.dart b/lib/ui/reports/credit_item_report.dart index bd3817899c4..49f60bc194d 100644 --- a/lib/ui/reports/credit_item_report.dart +++ b/lib/ui/reports/credit_item_report.dart @@ -2,6 +2,8 @@ import 'package:built_collection/built_collection.dart'; import 'package:collection/collection.dart' show IterableNullableExtension; import 'package:invoiceninja_flutter/utils/formatting.dart'; +import 'package:invoiceninja_flutter/main_app.dart'; +import 'package:invoiceninja_flutter/utils/localization.dart'; import 'package:memoize/memoize.dart'; // Project imports: @@ -41,6 +43,7 @@ enum CreditItemReportFields { taxAmount, netTotal, currency, + record_state, } var memoizedCreditItemReport = memo6(( @@ -204,6 +207,10 @@ ReportResult lineItemReport( case CreditItemReportFields.clientIdNumber: value = client.idNumber; break; + case CreditItemReportFields.record_state: + value = AppLocalization.of(navigatorKey.currentContext!)! + .lookup(credit.entityState); + break; } if (!ReportResult.matchField( diff --git a/lib/ui/reports/credit_report.dart b/lib/ui/reports/credit_report.dart index 743bbaceb79..b11ad0d8bf5 100644 --- a/lib/ui/reports/credit_report.dart +++ b/lib/ui/reports/credit_report.dart @@ -2,6 +2,8 @@ import 'package:built_collection/built_collection.dart'; import 'package:collection/collection.dart' show IterableNullableExtension; import 'package:invoiceninja_flutter/redux/reports/reports_selectors.dart'; +import 'package:invoiceninja_flutter/main_app.dart'; +import 'package:invoiceninja_flutter/utils/localization.dart'; import 'package:memoize/memoize.dart'; // Project imports: @@ -77,6 +79,7 @@ enum CreditReportFields { contact_email, contact_phone, contact_name, + record_state, } var memoizedCreditReport = memo6(( @@ -357,6 +360,10 @@ ReportResult creditReport( case CreditReportFields.client_id_number: value = client.idNumber; break; + case CreditReportFields.record_state: + value = AppLocalization.of(navigatorKey.currentContext!)! + .lookup(credit.entityState); + break; } if (!ReportResult.matchField( diff --git a/lib/ui/reports/expense_report.dart b/lib/ui/reports/expense_report.dart index 24d0546022b..bbc11b5f74b 100644 --- a/lib/ui/reports/expense_report.dart +++ b/lib/ui/reports/expense_report.dart @@ -3,6 +3,8 @@ import 'package:built_collection/built_collection.dart'; import 'package:collection/collection.dart' show IterableNullableExtension; import 'package:invoiceninja_flutter/redux/reports/reports_selectors.dart'; import 'package:invoiceninja_flutter/utils/formatting.dart'; +import 'package:invoiceninja_flutter/main_app.dart'; +import 'package:invoiceninja_flutter/utils/localization.dart'; import 'package:memoize/memoize.dart'; // Project imports: @@ -54,6 +56,7 @@ enum ExpenseReportFields { updated_at, converted_amount, status, + record_state, } var memoizedExpenseReport = memo10(( @@ -276,6 +279,11 @@ ReportResult expenseReport( break; case ExpenseReportFields.status: value = kExpenseStatuses[expense.calculatedStatusId]; + break; + case ExpenseReportFields.record_state: + value = AppLocalization.of(navigatorKey.currentContext!)! + .lookup(expense.entityState); + break; } if (!ReportResult.matchField( diff --git a/lib/ui/reports/invoice_item_report.dart b/lib/ui/reports/invoice_item_report.dart index f115520a126..3fee07c0b08 100644 --- a/lib/ui/reports/invoice_item_report.dart +++ b/lib/ui/reports/invoice_item_report.dart @@ -2,6 +2,8 @@ import 'package:built_collection/built_collection.dart'; import 'package:collection/collection.dart' show IterableNullableExtension; import 'package:invoiceninja_flutter/utils/formatting.dart'; +import 'package:invoiceninja_flutter/main_app.dart'; +import 'package:invoiceninja_flutter/utils/localization.dart'; import 'package:memoize/memoize.dart'; // Project imports: @@ -41,6 +43,7 @@ enum InvoiceItemReportFields { taxAmount, netTotal, currency, + record_state, } var memoizedInvoiceItemReport = memo6(( @@ -204,6 +207,10 @@ ReportResult lineItemReport( case InvoiceItemReportFields.clientIdNumber: value = client.idNumber; break; + case InvoiceItemReportFields.record_state: + value = AppLocalization.of(navigatorKey.currentContext!)! + .lookup(invoice.entityState); + break; } if (!ReportResult.matchField( diff --git a/lib/ui/reports/invoice_report.dart b/lib/ui/reports/invoice_report.dart index 01310665a46..becf23747ce 100644 --- a/lib/ui/reports/invoice_report.dart +++ b/lib/ui/reports/invoice_report.dart @@ -2,6 +2,8 @@ import 'package:built_collection/built_collection.dart'; import 'package:collection/collection.dart' show IterableNullableExtension; import 'package:invoiceninja_flutter/redux/reports/reports_selectors.dart'; +import 'package:invoiceninja_flutter/main_app.dart'; +import 'package:invoiceninja_flutter/utils/localization.dart'; import 'package:memoize/memoize.dart'; // Project imports: @@ -93,6 +95,7 @@ enum InvoiceReportFields { age_group_60, age_group_90, age_group_120, + record_state, } var memoizedInvoiceReport = memo9(( @@ -468,6 +471,10 @@ ReportResult invoiceReport( case InvoiceReportFields.age_group_120: value = invoice.isPaid || invoice.age < 120 ? 0.0 : invoice.balance; break; + case InvoiceReportFields.record_state: + value = AppLocalization.of(navigatorKey.currentContext!)! + .lookup(invoice.entityState); + break; } if (!ReportResult.matchField( diff --git a/lib/ui/reports/payment_report.dart b/lib/ui/reports/payment_report.dart index d902f29318f..5e64c5f547c 100644 --- a/lib/ui/reports/payment_report.dart +++ b/lib/ui/reports/payment_report.dart @@ -3,6 +3,8 @@ import 'package:built_collection/built_collection.dart'; import 'package:collection/collection.dart' show IterableNullableExtension; import 'package:invoiceninja_flutter/redux/reports/reports_selectors.dart'; import 'package:invoiceninja_flutter/utils/formatting.dart'; +import 'package:invoiceninja_flutter/main_app.dart'; +import 'package:invoiceninja_flutter/utils/localization.dart'; import 'package:memoize/memoize.dart'; // Project imports: @@ -45,6 +47,7 @@ enum PaymentReportFields { converted_amount, invoices, credits, + record_state, } var memoizedPaymentReport = memo8( @@ -270,6 +273,10 @@ ReportResult paymentReport( case PaymentReportFields.credits: value = (paymentCreditMap[payment.id] ?? []).join(', '); break; + case PaymentReportFields.record_state: + value = AppLocalization.of(navigatorKey.currentContext!)! + .lookup(payment.entityState); + break; } if (!ReportResult.matchField( diff --git a/lib/ui/reports/product_report.dart b/lib/ui/reports/product_report.dart index df7ae6c0f9a..0c909e16208 100644 --- a/lib/ui/reports/product_report.dart +++ b/lib/ui/reports/product_report.dart @@ -2,6 +2,8 @@ import 'package:built_collection/built_collection.dart'; import 'package:collection/collection.dart' show IterableNullableExtension; import 'package:invoiceninja_flutter/redux/product/product_selectors.dart'; +import 'package:invoiceninja_flutter/main_app.dart'; +import 'package:invoiceninja_flutter/utils/localization.dart'; import 'package:invoiceninja_flutter/redux/reports/reports_selectors.dart'; import 'package:memoize/memoize.dart'; @@ -32,6 +34,7 @@ enum ProductReportFields { notification_threshold, created_at, updated_at, + record_state, } var memoizedProductReport = memo6(( @@ -161,6 +164,10 @@ ReportResult productReport( case ProductReportFields.created_at: value = convertTimestampToDateString(product.createdAt); break; + case ProductReportFields.record_state: + value = AppLocalization.of(navigatorKey.currentContext!)! + .lookup(product.entityState); + break; } if (!ReportResult.matchField( diff --git a/lib/ui/reports/profit_loss_report.dart b/lib/ui/reports/profit_loss_report.dart index 75698f1c42b..2a4f6bbd99a 100644 --- a/lib/ui/reports/profit_loss_report.dart +++ b/lib/ui/reports/profit_loss_report.dart @@ -35,6 +35,7 @@ enum ProfitAndLossReportFields { category, currency, transaction_reference, + record_state, } var memoizedProfitAndLossReport = memo9(( @@ -174,6 +175,10 @@ ReportResult profitAndLossReport( case ProfitAndLossReportFields.transaction_reference: value = payment.transactionReference; break; + case ProfitAndLossReportFields.record_state: + value = AppLocalization.of(navigatorKey.currentContext!)! + .lookup(payment.entityState); + break; } if (!ReportResult.matchField( @@ -277,6 +282,10 @@ ReportResult profitAndLossReport( case ProfitAndLossReportFields.transaction_reference: value = expense.transactionReference; break; + case ProfitAndLossReportFields.record_state: + value = AppLocalization.of(navigatorKey.currentContext!)! + .lookup(expense.entityState); + break; } if (!ReportResult.matchField( diff --git a/lib/ui/reports/purchase_order_item_report.dart b/lib/ui/reports/purchase_order_item_report.dart index 095cf6b9f78..b83a6f8e8be 100644 --- a/lib/ui/reports/purchase_order_item_report.dart +++ b/lib/ui/reports/purchase_order_item_report.dart @@ -2,6 +2,8 @@ import 'package:built_collection/built_collection.dart'; import 'package:collection/collection.dart' show IterableNullableExtension; import 'package:invoiceninja_flutter/constants.dart'; +import 'package:invoiceninja_flutter/main_app.dart'; +import 'package:invoiceninja_flutter/utils/localization.dart'; import 'package:invoiceninja_flutter/data/models/models.dart'; import 'package:memoize/memoize.dart'; @@ -38,6 +40,7 @@ enum PurchaseOrderItemReportFields { taxAmount, netTotal, currency, + record_state, } var memoizedPurchaseOrderItemReport = memo7(( @@ -203,6 +206,10 @@ ReportResult lineItemReport( case PurchaseOrderItemReportFields.clientIdNumber: value = client.idNumber; break; + case PurchaseOrderItemReportFields.record_state: + value = AppLocalization.of(navigatorKey.currentContext!)! + .lookup(invoice.entityState); + break; } if (!ReportResult.matchField( diff --git a/lib/ui/reports/purchase_order_report.dart b/lib/ui/reports/purchase_order_report.dart index 32250620c1a..bb5217789df 100644 --- a/lib/ui/reports/purchase_order_report.dart +++ b/lib/ui/reports/purchase_order_report.dart @@ -2,6 +2,8 @@ import 'package:built_collection/built_collection.dart'; import 'package:collection/collection.dart' show IterableNullableExtension; import 'package:invoiceninja_flutter/redux/reports/reports_selectors.dart'; +import 'package:invoiceninja_flutter/main_app.dart'; +import 'package:invoiceninja_flutter/utils/localization.dart'; import 'package:invoiceninja_flutter/utils/formatting.dart'; import 'package:memoize/memoize.dart'; @@ -76,6 +78,7 @@ enum PurchaseOrderReportFields { contact_email, contact_phone, contact_name, + record_state, } var memoizedPurchaseOrderReport = memo7(( @@ -357,6 +360,10 @@ ReportResult purchaseOrderReport( case PurchaseOrderReportFields.vendor_number: value = vendor.number; break; + case PurchaseOrderReportFields.record_state: + value = AppLocalization.of(navigatorKey.currentContext!)! + .lookup(purchaseOrder.entityState); + break; } if (!ReportResult.matchField( diff --git a/lib/ui/reports/quote_item_report.dart b/lib/ui/reports/quote_item_report.dart index 66061fa4c1f..5ae8f75462a 100644 --- a/lib/ui/reports/quote_item_report.dart +++ b/lib/ui/reports/quote_item_report.dart @@ -1,6 +1,8 @@ // Package imports: import 'package:built_collection/built_collection.dart'; import 'package:collection/collection.dart' show IterableNullableExtension; +import 'package:invoiceninja_flutter/main_app.dart'; +import 'package:invoiceninja_flutter/utils/localization.dart'; import 'package:invoiceninja_flutter/utils/formatting.dart'; import 'package:memoize/memoize.dart'; @@ -41,6 +43,7 @@ enum QuoteItemReportFields { taxAmount, netTotal, currency, + record_state, } var memoizedQuoteItemReport = memo6(( @@ -200,6 +203,10 @@ ReportResult lineItemReport( case QuoteItemReportFields.clientIdNumber: value = client.idNumber; break; + case QuoteItemReportFields.record_state: + value = AppLocalization.of(navigatorKey.currentContext!)! + .lookup(invoice.entityState); + break; } if (!ReportResult.matchField( diff --git a/lib/ui/reports/quote_report.dart b/lib/ui/reports/quote_report.dart index 96f5780ce6e..9a2efeeb944 100644 --- a/lib/ui/reports/quote_report.dart +++ b/lib/ui/reports/quote_report.dart @@ -2,6 +2,8 @@ import 'package:built_collection/built_collection.dart'; import 'package:collection/collection.dart' show IterableNullableExtension; import 'package:invoiceninja_flutter/redux/reports/reports_selectors.dart'; +import 'package:invoiceninja_flutter/main_app.dart'; +import 'package:invoiceninja_flutter/utils/localization.dart'; import 'package:invoiceninja_flutter/utils/formatting.dart'; import 'package:memoize/memoize.dart'; @@ -75,6 +77,7 @@ enum QuoteReportFields { contact_email, contact_phone, contact_name, + record_state, } var memoizedQuoteReport = memo7(( @@ -351,6 +354,10 @@ ReportResult quoteReport( case QuoteReportFields.client_id_number: value = client.idNumber; break; + case QuoteReportFields.record_state: + value = AppLocalization.of(navigatorKey.currentContext!)! + .lookup(quote.entityState); + break; } if (!ReportResult.matchField( diff --git a/lib/ui/reports/recurring_expense_report.dart b/lib/ui/reports/recurring_expense_report.dart index a972f39bf7e..acb37b2eb7b 100644 --- a/lib/ui/reports/recurring_expense_report.dart +++ b/lib/ui/reports/recurring_expense_report.dart @@ -45,6 +45,7 @@ enum RecurringExpenseReportFields { frequency, start_date, remaining_cycles, + record_state, } var memoizedRecurringExpenseReport = memo9(( @@ -227,6 +228,10 @@ ReportResult recurringExpenseReport( ? localization!.endless : '${invoice.remainingCycles}'; break; + case RecurringExpenseReportFields.record_state: + value = AppLocalization.of(navigatorKey.currentContext!)! + .lookup(expense.entityState); + break; } if (!ReportResult.matchField( diff --git a/lib/ui/reports/recurring_invoice_report.dart b/lib/ui/reports/recurring_invoice_report.dart index dc8c59897ef..2489dc97075 100644 --- a/lib/ui/reports/recurring_invoice_report.dart +++ b/lib/ui/reports/recurring_invoice_report.dart @@ -86,6 +86,7 @@ enum RecurringInvoiceReportFields { due_on, next_send_date, last_sent_date, + record_state, } var memoizedRecurringInvoiceReport = memo8(( @@ -401,6 +402,10 @@ ReportResult recurringInvoiceReport( .replaceFirst(':count', '${invoice.dueDateDays}'); } break; + case RecurringInvoiceReportFields.record_state: + value = AppLocalization.of(navigatorKey.currentContext!)! + .lookup(invoice.entityState); + break; } if (!ReportResult.matchField( diff --git a/lib/ui/reports/task_report.dart b/lib/ui/reports/task_report.dart index 3e3344af1d0..0a0541a9335 100644 --- a/lib/ui/reports/task_report.dart +++ b/lib/ui/reports/task_report.dart @@ -1,6 +1,8 @@ // Package imports: import 'package:built_collection/built_collection.dart'; import 'package:collection/collection.dart' show IterableNullableExtension; +import 'package:invoiceninja_flutter/main_app.dart'; +import 'package:invoiceninja_flutter/utils/localization.dart'; import 'package:invoiceninja_flutter/redux/reports/reports_selectors.dart'; import 'package:memoize/memoize.dart'; @@ -43,6 +45,7 @@ enum TaskReportFields { assigned_to, created_by, amount, + record_state, } var memoizedTaskReport = memo10(( @@ -247,6 +250,9 @@ ReportResult taskReport( )!, ); break; + case TaskReportFields.record_state: + value = AppLocalization.of(navigatorKey.currentContext!)! + .lookup(task.entityState); } if (!ReportResult.matchField( diff --git a/lib/ui/reports/transaction_report.dart b/lib/ui/reports/transaction_report.dart index 62be3c92dce..214602ae134 100644 --- a/lib/ui/reports/transaction_report.dart +++ b/lib/ui/reports/transaction_report.dart @@ -2,6 +2,8 @@ import 'package:built_collection/built_collection.dart'; import 'package:collection/collection.dart' show IterableNullableExtension; import 'package:invoiceninja_flutter/utils/strings.dart'; +import 'package:invoiceninja_flutter/main_app.dart'; +import 'package:invoiceninja_flutter/utils/localization.dart'; import 'package:memoize/memoize.dart'; // Project imports: @@ -31,6 +33,7 @@ enum TransactionReportFields { defaultCategory, created_at, updated_at, + record_state, } var memoizedTransactionReport = memo10(( @@ -172,6 +175,9 @@ ReportResult transactionReport( case TransactionReportFields.created_at: value = convertTimestampToDateString(transaction.createdAt); break; + case TransactionReportFields.record_state: + value = AppLocalization.of(navigatorKey.currentContext!)! + .lookup(transaction.entityState); } if (!ReportResult.matchField( diff --git a/lib/ui/reports/vendor_report.dart b/lib/ui/reports/vendor_report.dart index d67cb89bf4e..2e85e56d212 100644 --- a/lib/ui/reports/vendor_report.dart +++ b/lib/ui/reports/vendor_report.dart @@ -56,6 +56,7 @@ enum VendorReportFields { documents, last_login, classification, + record_state, /* contact_last_login, shipping_address1, @@ -323,6 +324,10 @@ ReportResult vendorReport( value = AppLocalization.of(navigatorKey.currentContext!)! .lookup(vendor.classification); break; + case VendorReportFields.record_state: + value = AppLocalization.of(navigatorKey.currentContext!)! + .lookup(vendor.entityState); + break; } if (!ReportResult.matchField( diff --git a/lib/ui/settings/device_settings.dart b/lib/ui/settings/device_settings.dart index f25122f135a..0707724cdec 100644 --- a/lib/ui/settings/device_settings.dart +++ b/lib/ui/settings/device_settings.dart @@ -1,4 +1,7 @@ // Flutter imports: +import 'dart:io'; + +import 'package:filesystem_picker/filesystem_picker.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart' hide LiveText; @@ -7,6 +10,8 @@ import 'package:flutter/services.dart' hide LiveText; import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_styled_toast/flutter_styled_toast.dart'; import 'package:invoiceninja_flutter/redux/company/company_selectors.dart'; +import 'package:invoiceninja_flutter/ui/app/forms/decorated_form_field.dart'; +import 'package:invoiceninja_flutter/utils/files.dart'; import 'package:invoiceninja_flutter/utils/formatting.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:timeago/timeago.dart' as timeago; @@ -50,6 +55,11 @@ class _DeviceSettingsState extends State TabController? _controller; FocusScopeNode? _focusNode; + String _defaultDownloadsFolder = ''; + + final _downloadsFolderController = TextEditingController(); + + List _controllers = []; @override void initState() { @@ -61,6 +71,37 @@ class _DeviceSettingsState extends State _controller!.addListener(_onTabChanged); } + @override + void didChangeDependencies() async { + super.didChangeDependencies(); + + _controllers = [ + _downloadsFolderController, + ]; + + _controllers + .forEach((dynamic controller) => controller.removeListener(_onChanged)); + + final prefState = widget.viewModel.state.prefState; + _downloadsFolderController.text = prefState.donwloadsFolder; + + _controllers + .forEach((dynamic controller) => controller.addListener(_onChanged)); + + _defaultDownloadsFolder = prefState.donwloadsFolder.isEmpty + ? await getAppDownloadDirectory() ?? '' + : prefState.donwloadsFolder; + } + + void _onChanged() async { + widget.viewModel + .onDownloadsFolderChanged(context, _downloadsFolderController.text); + + _defaultDownloadsFolder = _downloadsFolderController.text.isEmpty + ? await getAppDownloadDirectory() ?? '' + : _downloadsFolderController.text; + } + void _onTabChanged() { final store = StoreProvider.of(context); store.dispatch(UpdateSettingsTab(tabIndex: _controller!.index)); @@ -226,6 +267,41 @@ class _DeviceSettingsState extends State ), FormCard( children: [ + if (!kIsWeb) + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: DecoratedFormField( + label: localization.downloadsFolder, + keyboardType: TextInputType.text, + hint: _defaultDownloadsFolder, + controller: _downloadsFolderController, + ), + ), + SizedBox(width: 20), + OutlinedButton( + onPressed: () async { + final folder = await FilesystemPicker.open( + context: context, + fsType: FilesystemType.folder, + rootDirectory: Directory(Platform.pathSeparator), + directory: Directory(_defaultDownloadsFolder), + title: localization.downloadsFolder, + pickText: localization.saveFilesToThisFolder, + ); + + if ((folder ?? '').isNotEmpty) { + _downloadsFolderController.text = folder!; + } + }, + child: Padding( + padding: const EdgeInsets.all(10), + child: Text(localization.select), + ), + ), + ], + ), Padding( padding: const EdgeInsets.only(bottom: 10), child: AppDropdownButton( diff --git a/lib/ui/settings/device_settings_vm.dart b/lib/ui/settings/device_settings_vm.dart index dfd3de58043..5756d9d0553 100644 --- a/lib/ui/settings/device_settings_vm.dart +++ b/lib/ui/settings/device_settings_vm.dart @@ -61,6 +61,7 @@ class DeviceSettingsVM { required this.onEnableTouchEventsChanged, required this.onEnableTooltipsChanged, required this.onEnableFlexibleSearchChanged, + required this.onDownloadsFolderChanged, }); static DeviceSettingsVM fromStore(Store store) { @@ -98,6 +99,9 @@ class DeviceSettingsVM { onTapSelectedChanged: (context, value) async { store.dispatch(UpdateUserPreferences(tapSelectedToEdit: value)); }, + onDownloadsFolderChanged: (context, value) async { + store.dispatch(UpdateUserPreferences(downloadsFolder: value)); + }, onEnableTouchEventsChanged: (context, value) async { store.dispatch(UpdateUserPreferences(enableTouchEvents: value)); store.dispatch(UpdatedSetting()); @@ -221,5 +225,6 @@ class DeviceSettingsVM { final Function(BuildContext, bool) onEnableTooltipsChanged; final Function(BuildContext, bool) onEnableFlexibleSearchChanged; final Function(BuildContext, double) onTextScaleFactorChanged; + final Function(BuildContext, String) onDownloadsFolderChanged; final Future authenticationSupported; } diff --git a/lib/ui/settings/settings_list.dart b/lib/ui/settings/settings_list.dart index accd23cf8ce..4c6f3ff2890 100644 --- a/lib/ui/settings/settings_list.dart +++ b/lib/ui/settings/settings_list.dart @@ -505,6 +505,7 @@ class SettingsSearch extends StatelessWidget { 'show_pdf_preview', 'pdf_preview_location#2022-10-24', 'refresh_data', + 'downloads_folder#2023-10-29' ], [ 'dark_mode', diff --git a/lib/utils/files.dart b/lib/utils/files.dart index 87e0b250a4e..98057df248a 100644 --- a/lib/utils/files.dart +++ b/lib/utils/files.dart @@ -107,7 +107,7 @@ Future getAppDownloadDirectory() async { if (!Directory(path).existsSync()) { showErrorDialog( message: AppLocalization.of(navigatorKey.currentContext!)! - .directoryDoesNotExist + .downloadsFolderDoesNotExist .replaceFirst(':value', path)); return null; diff --git a/lib/utils/formatting.dart b/lib/utils/formatting.dart index ce12992b57d..8ebb4ca694a 100644 --- a/lib/utils/formatting.dart +++ b/lib/utils/formatting.dart @@ -333,8 +333,9 @@ DateTime convertSqlDateToDateTime([String? date]) { DateTime convertTimestampToDate(int? timestamp) => DateTime.fromMillisecondsSinceEpoch((timestamp ?? 0) * 1000, isUtc: true); -String convertTimestampToDateString(int? timestamp) => - convertTimestampToDate(timestamp).toIso8601String(); +String convertTimestampToDateString(int? timestamp) => (timestamp ?? 0) == 0 + ? '' + : convertTimestampToDate(timestamp).toIso8601String(); String formatDuration(Duration? duration, {bool showSeconds = true}) { final time = duration.toString().split('.')[0]; diff --git a/lib/utils/i18n.dart b/lib/utils/i18n.dart index 1fbff80cd9f..98324019e09 100644 --- a/lib/utils/i18n.dart +++ b/lib/utils/i18n.dart @@ -18,10 +18,14 @@ mixin LocalizationsProvider on LocaleCodeAware { static final Map> _localizedValues = { 'en': { // STARTER: lang key - do not remove comment -'total_invoiced_quotes': 'Invoiced Quotes', + 'record_state': 'Record State', + 'last_login': 'Last Login', + 'save_files_to_this_folder': 'Save files to this folder', + 'downloads_folder': 'Downloads Folder', + 'total_invoiced_quotes': 'Invoiced Quotes', 'total_invoice_paid_quotes': 'Invoice Paid Quotes', - 'directory_does_not_exist': - 'The download directory does not exist :value', + 'downloads_folder_does_not_exist': + 'The downloads folder does not exist :value', 'user_logged_in_notification': 'User Logged in Notification', 'user_logged_in_notification_help': 'Send an email when logging in from a new location', @@ -109902,20 +109906,35 @@ mixin LocalizationsProvider on LocaleCodeAware { _localizedValues[localeCode]!['user_logged_in_notification_help'] ?? _localizedValues['en']!['user_logged_in_notification_help']!; - String get directoryDoesNotExist => - _localizedValues[localeCode]!['directory_does_not_exist'] ?? - _localizedValues['en']!['directory_does_not_exist']!; + String get downloadsFolderDoesNotExist => + _localizedValues[localeCode]!['downloads_folder_does_not_exist'] ?? + _localizedValues['en']!['downloads_folder_does_not_exist']!; -String get totalInvoicedQuotes => + String get totalInvoicedQuotes => _localizedValues[localeCode]!['total_invoiced_quotes'] ?? _localizedValues['en']!['total_invoiced_quotes']!; -String get totalInvoicePaidQuotes => + String get totalInvoicePaidQuotes => _localizedValues[localeCode]!['total_invoice_paid_quotes'] ?? _localizedValues['en']!['total_invoice_paid_quotes']!; - - // STARTER: lang field - do not remove comment + String get downloadsFolder => + _localizedValues[localeCode]!['downloads_folder'] ?? + _localizedValues['en']!['downloads_folder']!; + + String get saveFilesToThisFolder => + _localizedValues[localeCode]!['save_files_to_this_folder'] ?? + _localizedValues['en']!['save_files_to_this_folder']!; + + String get lastLogin => + _localizedValues[localeCode]!['last_login'] ?? + _localizedValues['en']!['last_login']!; + + String get recordState => + _localizedValues[localeCode]!['record_state'] ?? + _localizedValues['en']!['record_state']!; + + // STARTER: lang field - do not remove comment String lookup(String? key) { final lookupKey = toSnakeCase(key); diff --git a/pubspec.foss.yaml b/pubspec.foss.yaml index 4ffdf9dae60..7acc0400db8 100644 --- a/pubspec.foss.yaml +++ b/pubspec.foss.yaml @@ -88,6 +88,7 @@ dependencies: # quick_actions: ^0.2.1 # idb_shim: ^1.11.1+1 collection: ^1.15.0-nullsafety.4 + filesystem_picker: ^4.0.0 dependency_overrides: intl: any diff --git a/pubspec.lock b/pubspec.lock index 0044dfe05ba..6d4e2e58ce7 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -386,6 +386,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.9.3+1" + filesystem_picker: + dependency: "direct main" + description: + name: filesystem_picker + sha256: "37ab68968420c2073b68e002cae786d00ef1cfe18bd2b7255640338a0c47aa9a" + url: "https://pub.dev" + source: hosted + version: "4.0.0" fixnum: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 530fd9779f0..209d8d69ebd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -94,6 +94,7 @@ dependencies: # quick_actions: ^0.2.1 # idb_shim: ^1.11.1+1 collection: ^1.15.0-nullsafety.4 + filesystem_picker: ^4.0.0 dependency_overrides: intl: any