From c22d0a6919e343794a5761df98e8f25d3c5b9ffe Mon Sep 17 00:00:00 2001 From: Anirudh Singh Date: Wed, 14 Feb 2024 19:50:17 +0530 Subject: [PATCH 01/21] Remove duplicate initialization of shared_preference - Moved SharedPreferences.getInstance() in main outside isDesktopOS() check - Update _initialState method to take in prefs and use that instead of initializing sp again --- lib/main.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 689ca09b554..7ea85e3ffa5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -110,10 +110,11 @@ void main({bool isTesting = false}) async { // Ignore CERT_ALREADY_IN_HASH_TABLE } + final prefs = await SharedPreferences.getInstance(); + if (isDesktopOS()) { await windowManager.ensureInitialized(); - final prefs = await SharedPreferences.getInstance(); windowManager.waitUntilReadyToShow( WindowOptions( center: true, @@ -128,7 +129,7 @@ void main({bool isTesting = false}) async { } final store = Store(appReducer, - initialState: await _initialState(isTesting), + initialState: await _initialState(isTesting, prefs), middleware: [] ..addAll(createStoreAuthMiddleware()) ..addAll(createStoreDocumentsMiddleware()) @@ -219,8 +220,7 @@ void main({bool isTesting = false}) async { */ } -Future _initialState(bool isTesting) async { - final prefs = await SharedPreferences.getInstance(); +Future _initialState(bool isTesting, SharedPreferences prefs) async { final prefString = prefs.getString(kSharedPrefs); final url = WebUtils.apiUrl ?? prefs.getString(kSharedPrefUrl) ?? ''; From 9a5f9107782a86d2f5f8501d66f9a291368b7b4f Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Sun, 18 Feb 2024 15:49:54 +0200 Subject: [PATCH 02/21] Support SMTP credentials --- lib/ui/settings/email_settings.dart | 29 ++++++++++++++++++++++++++++- lib/utils/i18n.dart | 10 ++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/lib/ui/settings/email_settings.dart b/lib/ui/settings/email_settings.dart index f807e0a98e1..6ad658ecf4a 100644 --- a/lib/ui/settings/email_settings.dart +++ b/lib/ui/settings/email_settings.dart @@ -1,9 +1,13 @@ // Flutter imports: +import 'dart:convert'; + import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; // Package imports: import 'package:flutter_redux/flutter_redux.dart'; +import 'package:invoiceninja_flutter/data/web_client.dart'; +import 'package:invoiceninja_flutter/utils/dialogs.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'; @@ -235,7 +239,7 @@ class _EmailSettingsState extends State { DropdownMenuItem( child: Text(localization.defaultWord), value: SettingsEntity.EMAIL_SENDING_METHOD_DEFAULT), - if (!kReleaseMode) + if (supportsLatestFeatures('5.8.0')) DropdownMenuItem( child: Text('SMTP'), value: SettingsEntity.EMAIL_SENDING_METHOD_SMTP), @@ -446,6 +450,29 @@ class _EmailSettingsState extends State { (b) => b..smtpVerifyPeer = value, )); }), + SizedBox(height: 20), + OutlinedButton( + onPressed: () async { + final credentials = state.credentials; + final url = '${credentials.url}/smtp/check'; + final data = { + 'smtp_host': company.smtpHost, + 'smtp_port': company.smtpPort, + 'smtp_encryption': company.smtpEncryption, + 'smtp_username': company.smtpUsername, + 'smtp_password': company.smtpPassword, + 'smtp_local_domain': company.smtpLocalDomain, + 'smtp_verify_peer': company.smtpVerifyPeer, + }; + print('## DATA: $data'); + await WebClient().post(url, credentials.token, + data: json.encode(data)); + showMessageDialog(message: localization.testEmailSent); + }, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 12), + child: Text(localization.sendTestEmail.toUpperCase()), + )), ], ], ), diff --git a/lib/utils/i18n.dart b/lib/utils/i18n.dart index 360f8ca40d3..072be47b9ae 100644 --- a/lib/utils/i18n.dart +++ b/lib/utils/i18n.dart @@ -18,6 +18,8 @@ mixin LocalizationsProvider on LocaleCodeAware { static final Map> _localizedValues = { 'en': { // STARTER: lang key - do not remove comment + 'test_email_sent': 'Successfully sent email', + 'send_test_email': 'Send Test Email', 'gateway_type': 'Gateway Type', 'please_select_an_invoice_or_credit': 'Please select an invoice or credit', @@ -114858,6 +114860,14 @@ mixin LocalizationsProvider on LocaleCodeAware { _localizedValues[localeCode]!['please_select_an_invoice_or_credit'] ?? _localizedValues['en']!['please_select_an_invoice_or_credit']!; + String get sendTestEmail => + _localizedValues[localeCode]!['send_test_email'] ?? + _localizedValues['en']!['send_test_email']!; + + String get testEmailSent => + _localizedValues[localeCode]!['test_email_sent'] ?? + _localizedValues['en']!['test_email_sent']!; + // STARTER: lang field - do not remove comment String lookup(String? key, {String? overrideLocaleCode}) { From 0147b9f4352b7294be1b3df9f257beb9d0b115c2 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Sun, 18 Feb 2024 15:59:56 +0200 Subject: [PATCH 03/21] Support SMTP credentials --- lib/ui/settings/email_settings.dart | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/lib/ui/settings/email_settings.dart b/lib/ui/settings/email_settings.dart index 6ad658ecf4a..5e3efc2462a 100644 --- a/lib/ui/settings/email_settings.dart +++ b/lib/ui/settings/email_settings.dart @@ -7,6 +7,7 @@ import 'package:flutter/material.dart'; // Package imports: import 'package:flutter_redux/flutter_redux.dart'; import 'package:invoiceninja_flutter/data/web_client.dart'; +import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; import 'package:invoiceninja_flutter/utils/dialogs.dart'; import 'package:invoiceninja_flutter/utils/files.dart'; import 'package:invoiceninja_flutter/utils/formatting.dart'; @@ -455,6 +456,8 @@ class _EmailSettingsState extends State { onPressed: () async { final credentials = state.credentials; final url = '${credentials.url}/smtp/check'; + final company = viewModel.company; + final data = { 'smtp_host': company.smtpHost, 'smtp_port': company.smtpPort, @@ -464,10 +467,22 @@ class _EmailSettingsState extends State { 'smtp_local_domain': company.smtpLocalDomain, 'smtp_verify_peer': company.smtpVerifyPeer, }; + print('## DATA: $data'); - await WebClient().post(url, credentials.token, - data: json.encode(data)); - showMessageDialog(message: localization.testEmailSent); + final store = StoreProvider.of(context); + store.dispatch(StartSaving()); + + try { + final response = await WebClient().post( + url, credentials.token, + data: json.encode(data)); + store.dispatch(StopSaving()); + //showMessageDialog(message: localization.testEmailSent); + showMessageDialog(message: '$response'); + } catch (error) { + store.dispatch(StopSaving()); + showErrorDialog(message: '$error'); + } }, child: Padding( padding: const EdgeInsets.symmetric(vertical: 12), From a0990599d4e79b836d66aa0c9ff63ca5cbd1be7c Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Sun, 18 Feb 2024 18:58:55 +0200 Subject: [PATCH 04/21] Add payment setting --- lib/data/models/settings_model.dart | 3 +++ lib/data/models/settings_model.g.dart | 32 +++++++++++++++++++++++---- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/lib/data/models/settings_model.dart b/lib/data/models/settings_model.dart index 6e1ec64f9eb..511d6270c48 100644 --- a/lib/data/models/settings_model.dart +++ b/lib/data/models/settings_model.dart @@ -818,6 +818,9 @@ abstract class SettingsEntity @BuiltValueField(wireName: 'show_pdfhtml_on_mobile') bool? get showPdfhtmlOnMobile; + @BuiltValueField(wireName: 'use_unapplied_payment') + bool? get useUnappliedPayment; + bool get hasAddress => address1 != null && address1!.isNotEmpty; bool get hasLogo => companyLogo != null && companyLogo!.isNotEmpty; diff --git a/lib/data/models/settings_model.g.dart b/lib/data/models/settings_model.g.dart index 3b8399a29dc..b8b566cb00d 100644 --- a/lib/data/models/settings_model.g.dart +++ b/lib/data/models/settings_model.g.dart @@ -1585,6 +1585,13 @@ class _$SettingsEntitySerializer ..add( serializers.serialize(value, specifiedType: const FullType(bool))); } + value = object.useUnappliedPayment; + if (value != null) { + result + ..add('use_unapplied_payment') + ..add( + serializers.serialize(value, specifiedType: const FullType(bool))); + } return result; } @@ -2507,6 +2514,10 @@ class _$SettingsEntitySerializer result.showPdfhtmlOnMobile = serializers.deserialize(value, specifiedType: const FullType(bool)) as bool?; break; + case 'use_unapplied_payment': + result.useUnappliedPayment = serializers.deserialize(value, + specifiedType: const FullType(bool)) as bool?; + break; } } @@ -3036,6 +3047,8 @@ class _$SettingsEntity extends SettingsEntity { final bool? paymentEmailAllContacts; @override final bool? showPdfhtmlOnMobile; + @override + final bool? useUnappliedPayment; factory _$SettingsEntity([void Function(SettingsEntityBuilder)? updates]) => (new SettingsEntityBuilder()..update(updates))._build(); @@ -3265,7 +3278,8 @@ class _$SettingsEntity extends SettingsEntity { this.defaultExpensePaymentTypeId, this.classification, this.paymentEmailAllContacts, - this.showPdfhtmlOnMobile}) + this.showPdfhtmlOnMobile, + this.useUnappliedPayment}) : super._(); @override @@ -3509,7 +3523,8 @@ class _$SettingsEntity extends SettingsEntity { defaultExpensePaymentTypeId == other.defaultExpensePaymentTypeId && classification == other.classification && paymentEmailAllContacts == other.paymentEmailAllContacts && - showPdfhtmlOnMobile == other.showPdfhtmlOnMobile; + showPdfhtmlOnMobile == other.showPdfhtmlOnMobile && + useUnappliedPayment == other.useUnappliedPayment; } int? __hashCode; @@ -3742,6 +3757,7 @@ class _$SettingsEntity extends SettingsEntity { _$hash = $jc(_$hash, classification.hashCode); _$hash = $jc(_$hash, paymentEmailAllContacts.hashCode); _$hash = $jc(_$hash, showPdfhtmlOnMobile.hashCode); + _$hash = $jc(_$hash, useUnappliedPayment.hashCode); _$hash = $jf(_$hash); return __hashCode ??= _$hash; } @@ -3978,7 +3994,8 @@ class _$SettingsEntity extends SettingsEntity { ..add('defaultExpensePaymentTypeId', defaultExpensePaymentTypeId) ..add('classification', classification) ..add('paymentEmailAllContacts', paymentEmailAllContacts) - ..add('showPdfhtmlOnMobile', showPdfhtmlOnMobile)) + ..add('showPdfhtmlOnMobile', showPdfhtmlOnMobile) + ..add('useUnappliedPayment', useUnappliedPayment)) .toString(); } } @@ -5099,6 +5116,11 @@ class SettingsEntityBuilder set showPdfhtmlOnMobile(bool? showPdfhtmlOnMobile) => _$this._showPdfhtmlOnMobile = showPdfhtmlOnMobile; + bool? _useUnappliedPayment; + bool? get useUnappliedPayment => _$this._useUnappliedPayment; + set useUnappliedPayment(bool? useUnappliedPayment) => + _$this._useUnappliedPayment = useUnappliedPayment; + SettingsEntityBuilder(); SettingsEntityBuilder get _$this { @@ -5329,6 +5351,7 @@ class SettingsEntityBuilder _classification = $v.classification; _paymentEmailAllContacts = $v.paymentEmailAllContacts; _showPdfhtmlOnMobile = $v.showPdfhtmlOnMobile; + _useUnappliedPayment = $v.useUnappliedPayment; _$v = null; } return this; @@ -5578,7 +5601,8 @@ class SettingsEntityBuilder defaultExpensePaymentTypeId: defaultExpensePaymentTypeId, classification: classification, paymentEmailAllContacts: paymentEmailAllContacts, - showPdfhtmlOnMobile: showPdfhtmlOnMobile); + showPdfhtmlOnMobile: showPdfhtmlOnMobile, + useUnappliedPayment: useUnappliedPayment); } catch (_) { late String _$failedField; try { From fea74815defddc27e506f04226f0065c09075af7 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Sun, 18 Feb 2024 19:03:04 +0200 Subject: [PATCH 05/21] Add payment setting --- lib/data/models/company_model.dart | 6 +++--- lib/ui/settings/payment_settings.dart | 27 ++++++++++++++++++++++++--- lib/utils/i18n.dart | 5 +++++ 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/lib/data/models/company_model.dart b/lib/data/models/company_model.dart index 78195252ce8..3e7df4792bc 100644 --- a/lib/data/models/company_model.dart +++ b/lib/data/models/company_model.dart @@ -180,9 +180,9 @@ abstract class CompanyEntity extends Object CompanyEntity._(); - static const USE_CREDITS_ALWAYS = 'always'; - static const USE_CREDITS_OPTION = 'option'; - static const USE_CREDITS_OFF = 'off'; + static const USE_ALWAYS = 'always'; + static const USE_OPTION = 'option'; + static const USE_OFF = 'off'; static const SMTP_ENCRYPTION_TLS = 'TLS'; static const SMTP_ENCRYPTION_STARTTLS = 'STARTTLS'; diff --git a/lib/ui/settings/payment_settings.dart b/lib/ui/settings/payment_settings.dart index d1b76a870c4..b13b121f3bd 100644 --- a/lib/ui/settings/payment_settings.dart +++ b/lib/ui/settings/payment_settings.dart @@ -162,6 +162,27 @@ class _PaymentSettingsState extends State { ), ], ), + AppDropdownButton( + labelText: localization.useAvailablePayments, + value: settings.useUnappliedPayment, + onChanged: (dynamic value) { + viewModel.onSettingsChanged(settings + .rebuild((b) => b..useUnappliedPayment = value)); + }, + items: [ + DropdownMenuItem( + child: Text(localization.always), + value: CompanyEntity.USE_ALWAYS, + ), + DropdownMenuItem( + child: Text(localization.showOption), + value: CompanyEntity.USE_OPTION, + ), + DropdownMenuItem( + child: Text(localization.off), + value: CompanyEntity.USE_OFF, + ), + ]), AppDropdownButton( labelText: localization.useAvailableCredits, value: settings.useCreditsPayment, @@ -172,15 +193,15 @@ class _PaymentSettingsState extends State { items: [ DropdownMenuItem( child: Text(localization.always), - value: CompanyEntity.USE_CREDITS_ALWAYS, + value: CompanyEntity.USE_ALWAYS, ), DropdownMenuItem( child: Text(localization.showOption), - value: CompanyEntity.USE_CREDITS_OPTION, + value: CompanyEntity.USE_OPTION, ), DropdownMenuItem( child: Text(localization.off), - value: CompanyEntity.USE_CREDITS_OFF, + value: CompanyEntity.USE_OFF, ), ]), EntityDropdown( diff --git a/lib/utils/i18n.dart b/lib/utils/i18n.dart index 072be47b9ae..6b36097c600 100644 --- a/lib/utils/i18n.dart +++ b/lib/utils/i18n.dart @@ -18,6 +18,7 @@ mixin LocalizationsProvider on LocaleCodeAware { static final Map> _localizedValues = { 'en': { // STARTER: lang key - do not remove comment + 'use_available_payments': 'Use Available Payments', 'test_email_sent': 'Successfully sent email', 'send_test_email': 'Send Test Email', 'gateway_type': 'Gateway Type', @@ -114868,6 +114869,10 @@ mixin LocalizationsProvider on LocaleCodeAware { _localizedValues[localeCode]!['test_email_sent'] ?? _localizedValues['en']!['test_email_sent']!; + String get useAvailablePayments => + _localizedValues[localeCode]!['use_available_payments'] ?? _localizedValues['en']!['use_available_payments']!; + + // STARTER: lang field - do not remove comment String lookup(String? key, {String? overrideLocaleCode}) { From 0ab6cb81c51a448072070664fad8acf9ffbdca7e Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Mon, 19 Feb 2024 12:27:24 +0200 Subject: [PATCH 06/21] Code formatting --- lib/utils/i18n.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/utils/i18n.dart b/lib/utils/i18n.dart index 6b36097c600..aba514dfdc3 100644 --- a/lib/utils/i18n.dart +++ b/lib/utils/i18n.dart @@ -114870,8 +114870,8 @@ mixin LocalizationsProvider on LocaleCodeAware { _localizedValues['en']!['test_email_sent']!; String get useAvailablePayments => - _localizedValues[localeCode]!['use_available_payments'] ?? _localizedValues['en']!['use_available_payments']!; - + _localizedValues[localeCode]!['use_available_payments'] ?? + _localizedValues['en']!['use_available_payments']!; // STARTER: lang field - do not remove comment From c4e011d7f36e9df46ab47d766182a24da5430403 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Mon, 19 Feb 2024 12:27:43 +0200 Subject: [PATCH 07/21] Add new payment setting to settings list --- lib/ui/settings/settings_list.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/ui/settings/settings_list.dart b/lib/ui/settings/settings_list.dart index a5a4c4ac4e3..967efb117e9 100644 --- a/lib/ui/settings/settings_list.dart +++ b/lib/ui/settings/settings_list.dart @@ -423,6 +423,7 @@ class SettingsSearch extends StatelessWidget { 'auto_bill_standard_invoices#2023-01-17', 'client_initiated_payments#2023-03-20', 'send_emails_to#2023-11-30', + 'use_available_payments#2024-02-19', ] ], kSettingsTaxSettings: [ From 677c1d1c8eac97b3f6bbeadf005d2c0d87f66341 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Mon, 19 Feb 2024 12:35:05 +0200 Subject: [PATCH 08/21] Fix for new payment settings field --- lib/data/models/settings_model.dart | 2 +- lib/data/models/settings_model.g.dart | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/data/models/settings_model.dart b/lib/data/models/settings_model.dart index 511d6270c48..2c79fddd9e2 100644 --- a/lib/data/models/settings_model.dart +++ b/lib/data/models/settings_model.dart @@ -819,7 +819,7 @@ abstract class SettingsEntity bool? get showPdfhtmlOnMobile; @BuiltValueField(wireName: 'use_unapplied_payment') - bool? get useUnappliedPayment; + String? get useUnappliedPayment; bool get hasAddress => address1 != null && address1!.isNotEmpty; diff --git a/lib/data/models/settings_model.g.dart b/lib/data/models/settings_model.g.dart index b8b566cb00d..b2a43f4e993 100644 --- a/lib/data/models/settings_model.g.dart +++ b/lib/data/models/settings_model.g.dart @@ -1589,8 +1589,8 @@ class _$SettingsEntitySerializer if (value != null) { result ..add('use_unapplied_payment') - ..add( - serializers.serialize(value, specifiedType: const FullType(bool))); + ..add(serializers.serialize(value, + specifiedType: const FullType(String))); } return result; } @@ -2516,7 +2516,7 @@ class _$SettingsEntitySerializer break; case 'use_unapplied_payment': result.useUnappliedPayment = serializers.deserialize(value, - specifiedType: const FullType(bool)) as bool?; + specifiedType: const FullType(String)) as String?; break; } } @@ -3048,7 +3048,7 @@ class _$SettingsEntity extends SettingsEntity { @override final bool? showPdfhtmlOnMobile; @override - final bool? useUnappliedPayment; + final String? useUnappliedPayment; factory _$SettingsEntity([void Function(SettingsEntityBuilder)? updates]) => (new SettingsEntityBuilder()..update(updates))._build(); @@ -5116,9 +5116,9 @@ class SettingsEntityBuilder set showPdfhtmlOnMobile(bool? showPdfhtmlOnMobile) => _$this._showPdfhtmlOnMobile = showPdfhtmlOnMobile; - bool? _useUnappliedPayment; - bool? get useUnappliedPayment => _$this._useUnappliedPayment; - set useUnappliedPayment(bool? useUnappliedPayment) => + String? _useUnappliedPayment; + String? get useUnappliedPayment => _$this._useUnappliedPayment; + set useUnappliedPayment(String? useUnappliedPayment) => _$this._useUnappliedPayment = useUnappliedPayment; SettingsEntityBuilder(); From 16863a55122f10ca3a4cae9f393c81cbef9c29b4 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 20 Feb 2024 12:27:21 +0200 Subject: [PATCH 09/21] Code cleanup --- lib/ui/app/portal_links.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/ui/app/portal_links.dart b/lib/ui/app/portal_links.dart index 340c86c189c..d904e758aab 100644 --- a/lib/ui/app/portal_links.dart +++ b/lib/ui/app/portal_links.dart @@ -37,7 +37,10 @@ class PortalLinks extends StatelessWidget { viewLinkWithHash += '&client_hash=${client!.clientHash}'; } - final viewLinkPressed = () => launchUrl(Uri.parse(viewLinkWithHash)); + final viewLinkPressed = () { + launchUrl(Uri.parse(viewLinkWithHash)); + }; + final copyLinkPressed = () { Clipboard.setData(ClipboardData(text: copyLink)); showToast(localization!.copiedToClipboard.replaceFirst(':value ', '')); From 9ff87e1716caf5000ce16ee8d0077c6504f6d3d8 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 20 Feb 2024 12:29:06 +0200 Subject: [PATCH 10/21] Limit SMTP feature to pro plan on hosted --- lib/ui/settings/email_settings.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/ui/settings/email_settings.dart b/lib/ui/settings/email_settings.dart index 5e3efc2462a..dadc5969d52 100644 --- a/lib/ui/settings/email_settings.dart +++ b/lib/ui/settings/email_settings.dart @@ -240,7 +240,7 @@ class _EmailSettingsState extends State { DropdownMenuItem( child: Text(localization.defaultWord), value: SettingsEntity.EMAIL_SENDING_METHOD_DEFAULT), - if (supportsLatestFeatures('5.8.0')) + if (supportsLatestFeatures('5.8.0') && state.isProPlan) DropdownMenuItem( child: Text('SMTP'), value: SettingsEntity.EMAIL_SENDING_METHOD_SMTP), @@ -468,7 +468,6 @@ class _EmailSettingsState extends State { 'smtp_verify_peer': company.smtpVerifyPeer, }; - print('## DATA: $data'); final store = StoreProvider.of(context); store.dispatch(StartSaving()); From c0eaaae405bc37968382175c6d47d8e26d64c49a Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 20 Feb 2024 13:05:54 +0200 Subject: [PATCH 11/21] Open subscriptions page on mobile --- lib/ui/app/edit_scaffold.dart | 15 ++-------- lib/ui/app/invoice/invoice_email_view.dart | 16 ++--------- lib/ui/reports/reports_screen.dart | 18 ++---------- lib/ui/settings/account_management.dart | 33 ++++++++-------------- lib/utils/platforms.dart | 33 ++++++++++++++++++++++ pubspec.foss.yaml | 1 + pubspec.lock | 8 ++++++ pubspec.yaml | 1 + 8 files changed, 62 insertions(+), 63 deletions(-) diff --git a/lib/ui/app/edit_scaffold.dart b/lib/ui/app/edit_scaffold.dart index 0075f4b5371..c75327f3f36 100644 --- a/lib/ui/app/edit_scaffold.dart +++ b/lib/ui/app/edit_scaffold.dart @@ -8,10 +8,8 @@ import 'package:flutter_redux/flutter_redux.dart'; import 'package:invoiceninja_flutter/ui/app/forms/save_cancel_buttons.dart'; import 'package:invoiceninja_flutter/ui/app/icon_text.dart'; import 'package:invoiceninja_flutter/ui/app/loading_indicator.dart'; -import 'package:invoiceninja_flutter/ui/app/upgrade_dialog.dart'; import 'package:invoiceninja_flutter/ui/transaction/edit/transaction_edit_vm.dart'; import 'package:overflow_view/overflow_view.dart'; -import 'package:url_launcher/url_launcher.dart'; // Project imports: import 'package:invoiceninja_flutter/constants.dart'; @@ -137,9 +135,7 @@ class EditScaffold extends StatelessWidget { children: [ Column( children: [ - if (showUpgradeBanner && - state.userCompany.isOwner && - (!isApple() || supportsInAppPurchase())) + if (showUpgradeBanner && state.userCompany.isOwner) InkWell( child: IconMessage( upgradeMessage, @@ -148,15 +144,8 @@ class EditScaffold extends StatelessWidget { onTap: () async { if (bannerClick != null) { bannerClick(); - } else if (supportsInAppPurchase() && - account.canMakeIAP) { - showDialog( - context: context, - builder: (context) => UpgradeDialog(), - ); } else { - launchUrl(Uri.parse( - state.userCompany.ninjaPortalUrl)); + initiatePurchase(); } }, ), diff --git a/lib/ui/app/invoice/invoice_email_view.dart b/lib/ui/app/invoice/invoice_email_view.dart index 22bf80d3c81..882677699a2 100644 --- a/lib/ui/app/invoice/invoice_email_view.dart +++ b/lib/ui/app/invoice/invoice_email_view.dart @@ -16,7 +16,6 @@ import 'package:invoiceninja_flutter/ui/app/icon_message.dart'; import 'package:invoiceninja_flutter/ui/app/lists/activity_list_tile.dart'; import 'package:invoiceninja_flutter/ui/app/loading_indicator.dart'; import 'package:invoiceninja_flutter/ui/app/scrollable_listview.dart'; -import 'package:invoiceninja_flutter/ui/app/upgrade_dialog.dart'; import 'package:invoiceninja_flutter/ui/credit/credit_pdf_vm.dart'; import 'package:invoiceninja_flutter/ui/invoice/invoice_email_vm.dart'; import 'package:invoiceninja_flutter/ui/invoice/invoice_pdf_vm.dart'; @@ -29,7 +28,6 @@ import 'package:invoiceninja_flutter/utils/localization.dart'; import 'package:invoiceninja_flutter/utils/platforms.dart'; import 'package:invoiceninja_flutter/utils/super_editor/super_editor.dart'; import 'package:invoiceninja_flutter/utils/templates.dart'; -import 'package:url_launcher/url_launcher.dart'; class InvoiceEmailView extends StatefulWidget { const InvoiceEmailView({ @@ -337,18 +335,8 @@ class _InvoiceEmailViewState extends State color: Colors.white, ), ), - onPressed: () { - if (supportsInAppPurchase() && - state.account.canMakeIAP) { - showDialog( - context: context, - builder: (context) => UpgradeDialog(), - ); - } else { - launchUrl( - Uri.parse(state.userCompany.ninjaPortalUrl)); - } - }), + onPressed: () => initiatePurchase(), + ), ), ), ColoredBox( diff --git a/lib/ui/reports/reports_screen.dart b/lib/ui/reports/reports_screen.dart index 1f4f1b1ee5b..fbdd9fa53fd 100644 --- a/lib/ui/reports/reports_screen.dart +++ b/lib/ui/reports/reports_screen.dart @@ -34,7 +34,6 @@ import 'package:invoiceninja_flutter/ui/app/menu_drawer_vm.dart'; import 'package:invoiceninja_flutter/ui/app/presenters/entity_presenter.dart'; import 'package:invoiceninja_flutter/ui/app/scrollable_listview.dart'; import 'package:invoiceninja_flutter/ui/app/tables/app_paginated_data_table.dart'; -import 'package:invoiceninja_flutter/ui/app/upgrade_dialog.dart'; import 'package:invoiceninja_flutter/ui/reports/report_charts.dart'; import 'package:invoiceninja_flutter/ui/reports/reports_screen_vm.dart'; import 'package:invoiceninja_flutter/utils/colors.dart'; @@ -44,7 +43,6 @@ import 'package:invoiceninja_flutter/utils/formatting.dart'; import 'package:invoiceninja_flutter/utils/localization.dart'; import 'package:invoiceninja_flutter/utils/platforms.dart'; import 'package:invoiceninja_flutter/utils/strings.dart'; -import 'package:url_launcher/url_launcher.dart'; class ReportsScreen extends StatelessWidget { const ReportsScreen({ @@ -450,19 +448,9 @@ class ReportsScreen extends StatelessWidget { HelpText(localization.upgradeToViewReports), SizedBox(height: 10), AppButton( - label: localization.upgrade.toUpperCase(), - onPressed: () { - if (supportsInAppPurchase() && - state.account.canMakeIAP) { - showDialog( - context: context, - builder: (context) => UpgradeDialog(), - ); - } else { - launchUrl( - Uri.parse(state.userCompany.ninjaPortalUrl)); - } - }) + label: localization.upgrade.toUpperCase(), + onPressed: () => initiatePurchase(), + ) ], ), ), diff --git a/lib/ui/settings/account_management.dart b/lib/ui/settings/account_management.dart index af9552d0bca..8f9f917d4cb 100644 --- a/lib/ui/settings/account_management.dart +++ b/lib/ui/settings/account_management.dart @@ -5,7 +5,6 @@ import 'package:flutter/material.dart'; // Package imports: import 'package:flutter_redux/flutter_redux.dart'; import 'package:invoiceninja_flutter/main_app.dart'; -import 'package:invoiceninja_flutter/ui/app/upgrade_dialog.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:sign_in_with_apple/sign_in_with_apple.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -377,27 +376,19 @@ class _AccountOverview extends StatelessWidget { Padding( padding: const EdgeInsets.only(left: 16, top: 16, right: 16), child: OutlinedButton( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: IconText( - icon: MdiIcons.openInNew, - text: (account.isEligibleForTrial && - !supportsInAppPurchase() - ? localization.startFreeTrial - : localization.changePlan) - .toUpperCase(), - ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: IconText( + icon: MdiIcons.openInNew, + text: + (account.isEligibleForTrial && !supportsInAppPurchase() + ? localization.startFreeTrial + : localization.changePlan) + .toUpperCase(), ), - onPressed: () { - if (supportsInAppPurchase() && account.canMakeIAP) { - showDialog( - context: context, - builder: (context) => UpgradeDialog(), - ); - } else { - launchUrl(Uri.parse(state.userCompany.ninjaPortalUrl)); - } - }), + ), + onPressed: () => initiatePurchase(), + ), ), ], FormCard(children: [ diff --git a/lib/utils/platforms.dart b/lib/utils/platforms.dart index 9de2c369a49..87e938cf18f 100644 --- a/lib/utils/platforms.dart +++ b/lib/utils/platforms.dart @@ -8,6 +8,8 @@ import 'package:flutter/material.dart'; // Package imports: import 'package:flutter_redux/flutter_redux.dart'; import 'package:invoiceninja_flutter/main_app.dart'; +import 'package:invoiceninja_flutter/ui/app/upgrade_dialog.dart'; +import 'package:ios_open_subscriptions_settings/ios_open_subscriptions_settings.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; // Project imports: @@ -18,6 +20,7 @@ import 'package:invoiceninja_flutter/utils/localization.dart'; import 'package:invoiceninja_flutter/utils/web_stub.dart' if (dart.library.html) 'package:invoiceninja_flutter/utils/web.dart'; +import 'package:url_launcher/url_launcher.dart'; import 'package:version/version.dart'; // TODO remove this function @@ -79,6 +82,36 @@ bool supportsInAppPurchase() { return isIOS() || isAndroid() || isMacOS(); } +void initiatePurchase() { + final context = navigatorKey.currentContext!; + final store = StoreProvider.of(context); + final state = store.state; + final account = state.account; + + if (supportsInAppPurchase()) { + if (account.hasIapPlan) { + if (isIOS()) { + IosOpenSubscriptionsSettings.openSubscriptionsSettings(); + } else if (isAndroid()) { + launchUrl( + Uri.parse('http://play.google.com/store/account/subscriptions')); + } else { + // TODO support viewing plans on macOS + launchUrl(Uri.parse(state.userCompany.ninjaPortalUrl)); + } + } else if (state.isProPlan) { + launchUrl(Uri.parse(state.userCompany.ninjaPortalUrl)); + } else { + showDialog( + context: context, + builder: (context) => UpgradeDialog(), + ); + } + } else { + launchUrl(Uri.parse(state.userCompany.ninjaPortalUrl)); + } +} + bool isDesktopOS() => isMacOS() || isWindows() || isLinux(); bool isMobileOS() => isAndroid() || isIOS(); diff --git a/pubspec.foss.yaml b/pubspec.foss.yaml index 92307bd80f4..3fc5803bd19 100644 --- a/pubspec.foss.yaml +++ b/pubspec.foss.yaml @@ -90,6 +90,7 @@ dependencies: collection: ^1.15.0-nullsafety.4 filesystem_picker: ^4.0.0 device_info_plus: ^9.1.0 + ios_open_subscriptions_settings: ^0.0.4 dependency_overrides: intl: any diff --git a/pubspec.lock b/pubspec.lock index 364601b0297..789b0cc3194 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -801,6 +801,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" + ios_open_subscriptions_settings: + dependency: "direct main" + description: + name: ios_open_subscriptions_settings + sha256: "9ec8b1b87587287930f6968dcbc6cdfc3c9ad2f09c13a97818dfb525ddde8cce" + url: "https://pub.dev" + source: hosted + version: "0.0.4" js: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index b32515f19a9..f919dd104d3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -96,6 +96,7 @@ dependencies: collection: ^1.15.0-nullsafety.4 filesystem_picker: ^4.0.0 device_info_plus: ^9.1.0 + ios_open_subscriptions_settings: ^0.0.4 dependency_overrides: intl: any From 53efcb2958c215afa9cc7c772ba15a644a59b82d Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 20 Feb 2024 13:18:49 +0200 Subject: [PATCH 12/21] Open subscriptions page on mobile --- lib/utils/platforms.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utils/platforms.dart b/lib/utils/platforms.dart index 87e938cf18f..95ceddec84d 100644 --- a/lib/utils/platforms.dart +++ b/lib/utils/platforms.dart @@ -94,7 +94,7 @@ void initiatePurchase() { IosOpenSubscriptionsSettings.openSubscriptionsSettings(); } else if (isAndroid()) { launchUrl( - Uri.parse('http://play.google.com/store/account/subscriptions')); + Uri.parse('https://play.google.com/store/account/subscriptions')); } else { // TODO support viewing plans on macOS launchUrl(Uri.parse(state.userCompany.ninjaPortalUrl)); From 470bb628da082d6801e2475abfa85f942a90ad1b Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 20 Feb 2024 13:19:50 +0200 Subject: [PATCH 13/21] Open subscriptions page on mobile --- lib/data/models/account_model.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/data/models/account_model.dart b/lib/data/models/account_model.dart index 1cc6a7612b8..0ce9ebe50da 100644 --- a/lib/data/models/account_model.dart +++ b/lib/data/models/account_model.dart @@ -119,8 +119,6 @@ abstract class AccountEntity @BuiltValueField(wireName: 'nordigen_enabled') bool get nordigenEnabled; - bool get canMakeIAP => !hasIapPlan && paymentId.isEmpty; - bool get isUpdateAvailable { if (disableAutoUpdate) { return false; From fdef89df2f6031a1aacf99ce7f9f0d709ea234f5 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Tue, 20 Feb 2024 15:19:49 +0200 Subject: [PATCH 14/21] Tax-Inclusive UI inconsistency #634 --- lib/ui/invoice/edit/invoice_edit_desktop.dart | 6 +++--- lib/ui/invoice/edit/invoice_edit_details.dart | 6 +++--- lib/ui/invoice/edit/invoice_edit_items.dart | 7 ++++--- lib/ui/invoice/edit/invoice_edit_items_desktop.dart | 4 +--- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/lib/ui/invoice/edit/invoice_edit_desktop.dart b/lib/ui/invoice/edit/invoice_edit_desktop.dart index 2684fec94dd..0a3760fca91 100644 --- a/lib/ui/invoice/edit/invoice_edit_desktop.dart +++ b/lib/ui/invoice/edit/invoice_edit_desktop.dart @@ -1034,7 +1034,7 @@ class InvoiceEditDesktopState extends State invoice.applyTax(taxRate)); }, labelText: localization.tax + - (company.settings.enableInclusiveTaxes! + (invoice.usesInclusiveTaxes ? ' - ${localization.inclusive}' : ''), initialTaxName: invoice.taxName1, @@ -1048,7 +1048,7 @@ class InvoiceEditDesktopState extends State .applyTax(taxRate, isSecond: true)); }, labelText: localization.tax + - (company.settings.enableInclusiveTaxes! + (invoice.usesInclusiveTaxes ? ' - ${localization.inclusive}' : ''), initialTaxName: invoice.taxName2, @@ -1062,7 +1062,7 @@ class InvoiceEditDesktopState extends State .applyTax(taxRate, isThird: true)); }, labelText: localization.tax + - (company.settings.enableInclusiveTaxes! + (invoice.usesInclusiveTaxes ? ' - ${localization.inclusive}' : ''), initialTaxName: invoice.taxName3, diff --git a/lib/ui/invoice/edit/invoice_edit_details.dart b/lib/ui/invoice/edit/invoice_edit_details.dart index 2c0930b3e24..aad555097be 100644 --- a/lib/ui/invoice/edit/invoice_edit_details.dart +++ b/lib/ui/invoice/edit/invoice_edit_details.dart @@ -426,7 +426,7 @@ class InvoiceEditDetailsState extends State { onSelected: (taxRate) => viewModel.onChanged!(invoice.applyTax(taxRate)), labelText: localization.tax + - (company.settings.enableInclusiveTaxes! + (invoice.usesInclusiveTaxes ? ' - ${localization.inclusive}' : ''), initialTaxName: invoice.taxName1, @@ -438,7 +438,7 @@ class InvoiceEditDetailsState extends State { onSelected: (taxRate) => viewModel .onChanged!(invoice.applyTax(taxRate, isSecond: true)), labelText: localization.tax + - (company.settings.enableInclusiveTaxes! + (invoice.usesInclusiveTaxes ? ' - ${localization.inclusive}' : ''), initialTaxName: invoice.taxName2, @@ -450,7 +450,7 @@ class InvoiceEditDetailsState extends State { onSelected: (taxRate) => viewModel .onChanged!(invoice.applyTax(taxRate, isThird: true)), labelText: localization.tax + - (company.settings.enableInclusiveTaxes! + (invoice.usesInclusiveTaxes ? ' - ${localization.inclusive}' : ''), initialTaxName: invoice.taxName3, diff --git a/lib/ui/invoice/edit/invoice_edit_items.dart b/lib/ui/invoice/edit/invoice_edit_items.dart index a60ad8ebd2a..31f90f37a05 100644 --- a/lib/ui/invoice/edit/invoice_edit_items.dart +++ b/lib/ui/invoice/edit/invoice_edit_items.dart @@ -228,6 +228,7 @@ class ItemEditDetailsState extends State { final localization = AppLocalization.of(context)!; final viewModel = widget.viewModel; final company = viewModel.company!; + final invoice = viewModel.invoice; return AlertDialog( actions: [ @@ -355,7 +356,7 @@ class ItemEditDetailsState extends State { }); }, labelText: localization.tax + - (company.settings.enableInclusiveTaxes! + (invoice?.usesInclusiveTaxes == true ? ' - ${localization.inclusive}' : ''), initialTaxName: _taxRate1!.name, @@ -370,7 +371,7 @@ class ItemEditDetailsState extends State { }); }, labelText: localization.tax + - (company.settings.enableInclusiveTaxes! + (invoice?.usesInclusiveTaxes == true ? ' - ${localization.inclusive}' : ''), initialTaxName: _taxRate2!.name, @@ -385,7 +386,7 @@ class ItemEditDetailsState extends State { }); }, labelText: localization.tax + - (company.settings.enableInclusiveTaxes! + (invoice?.usesInclusiveTaxes == true ? ' - ${localization.inclusive}' : ''), initialTaxName: _taxRate3!.name, diff --git a/lib/ui/invoice/edit/invoice_edit_items_desktop.dart b/lib/ui/invoice/edit/invoice_edit_items_desktop.dart index 945a88cb054..e94d4a4b888 100644 --- a/lib/ui/invoice/edit/invoice_edit_items_desktop.dart +++ b/lib/ui/invoice/edit/invoice_edit_items_desktop.dart @@ -349,9 +349,7 @@ class _InvoiceEditItemsDesktopState extends State { label = company.getCustomFieldLabel(customField4); } else if ([COLUMN_TAX1, COLUMN_TAX2, COLUMN_TAX3].contains(column)) { label = localization!.tax + - (company.settings.enableInclusiveTaxes! - ? ' - ${localization.inclusive}' - : ''); + (invoice.usesInclusiveTaxes ? ' - ${localization.inclusive}' : ''); } else if (column == COLUMN_TAX_CATEGORY) { label = localization!.taxCategory; } else if (column == COLUMN_QUANTITY) { From e13e6ab50c6809362099f5440f729662da7814ec Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 21 Feb 2024 13:51:03 +0200 Subject: [PATCH 15/21] SMTP check --- lib/ui/settings/email_settings.dart | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/ui/settings/email_settings.dart b/lib/ui/settings/email_settings.dart index dadc5969d52..460cf72fed7 100644 --- a/lib/ui/settings/email_settings.dart +++ b/lib/ui/settings/email_settings.dart @@ -462,8 +462,10 @@ class _EmailSettingsState extends State { 'smtp_host': company.smtpHost, 'smtp_port': company.smtpPort, 'smtp_encryption': company.smtpEncryption, - 'smtp_username': company.smtpUsername, - 'smtp_password': company.smtpPassword, + if (company.smtpUsername != '********') + 'smtp_username': company.smtpUsername, + if (company.smtpPassword != '********') + 'smtp_password': company.smtpPassword, 'smtp_local_domain': company.smtpLocalDomain, 'smtp_verify_peer': company.smtpVerifyPeer, }; @@ -476,8 +478,8 @@ class _EmailSettingsState extends State { url, credentials.token, data: json.encode(data)); store.dispatch(StopSaving()); - //showMessageDialog(message: localization.testEmailSent); - showMessageDialog(message: '$response'); + showMessageDialog(message: localization.testEmailSent); + //showMessageDialog(message: '$response'); } catch (error) { store.dispatch(StopSaving()); showErrorDialog(message: '$error'); From c1ff92ec9b99debc64d10ed873968e9ea522533a Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 21 Feb 2024 14:08:57 +0200 Subject: [PATCH 16/21] Missing fields in edit client with flutter that we have in react #632 --- lib/data/models/client_model.dart | 5 +++++ lib/data/models/client_model.g.dart | 22 +++++++++++++++++++++ lib/ui/client/edit/client_edit_details.dart | 11 ++++++++++- lib/utils/i18n.dart | 5 +++++ 4 files changed, 42 insertions(+), 1 deletion(-) diff --git a/lib/data/models/client_model.dart b/lib/data/models/client_model.dart index e8657a86bc1..9dc3612a406 100644 --- a/lib/data/models/client_model.dart +++ b/lib/data/models/client_model.dart @@ -158,6 +158,7 @@ abstract class ClientEntity extends Object customValue4: '', routingId: '', isTaxExempt: false, + hasValidVatNumber: false, classification: '', taxData: TaxDataEntity(), contacts: BuiltList( @@ -316,6 +317,9 @@ abstract class ClientEntity extends Object @BuiltValueField(wireName: 'is_tax_exempt') bool get isTaxExempt; + @BuiltValueField(wireName: 'has_valid_vat_number') + bool get hasValidVatNumber; + @BuiltValueField(wireName: 'tax_info') TaxDataEntity get taxData; @@ -794,6 +798,7 @@ abstract class ClientEntity extends Object ..number = '' ..routingId = '' ..isTaxExempt = false + ..hasValidVatNumber = false ..taxData.replace(TaxDataEntity()) ..paymentBalance = 0 ..classification = ''; diff --git a/lib/data/models/client_model.g.dart b/lib/data/models/client_model.g.dart index d5d9ab8be69..ecb71a1f4a8 100644 --- a/lib/data/models/client_model.g.dart +++ b/lib/data/models/client_model.g.dart @@ -223,6 +223,9 @@ class _$ClientEntitySerializer implements StructuredSerializer { 'is_tax_exempt', serializers.serialize(object.isTaxExempt, specifiedType: const FullType(bool)), + 'has_valid_vat_number', + serializers.serialize(object.hasValidVatNumber, + specifiedType: const FullType(bool)), 'tax_info', serializers.serialize(object.taxData, specifiedType: const FullType(TaxDataEntity)), @@ -468,6 +471,10 @@ class _$ClientEntitySerializer implements StructuredSerializer { result.isTaxExempt = serializers.deserialize(value, specifiedType: const FullType(bool))! as bool; break; + case 'has_valid_vat_number': + result.hasValidVatNumber = serializers.deserialize(value, + specifiedType: const FullType(bool))! as bool; + break; case 'tax_info': result.taxData.replace(serializers.deserialize(value, specifiedType: const FullType(TaxDataEntity))! as TaxDataEntity); @@ -1033,6 +1040,8 @@ class _$ClientEntity extends ClientEntity { @override final bool isTaxExempt; @override + final bool hasValidVatNumber; + @override final TaxDataEntity taxData; @override final String classification; @@ -1107,6 +1116,7 @@ class _$ClientEntity extends ClientEntity { required this.customValue4, required this.routingId, required this.isTaxExempt, + required this.hasValidVatNumber, required this.taxData, required this.classification, required this.contacts, @@ -1189,6 +1199,8 @@ class _$ClientEntity extends ClientEntity { routingId, r'ClientEntity', 'routingId'); BuiltValueNullFieldError.checkNotNull( isTaxExempt, r'ClientEntity', 'isTaxExempt'); + BuiltValueNullFieldError.checkNotNull( + hasValidVatNumber, r'ClientEntity', 'hasValidVatNumber'); BuiltValueNullFieldError.checkNotNull(taxData, r'ClientEntity', 'taxData'); BuiltValueNullFieldError.checkNotNull( classification, r'ClientEntity', 'classification'); @@ -1260,6 +1272,7 @@ class _$ClientEntity extends ClientEntity { customValue4 == other.customValue4 && routingId == other.routingId && isTaxExempt == other.isTaxExempt && + hasValidVatNumber == other.hasValidVatNumber && taxData == other.taxData && classification == other.classification && contacts == other.contacts && @@ -1320,6 +1333,7 @@ class _$ClientEntity extends ClientEntity { _$hash = $jc(_$hash, customValue4.hashCode); _$hash = $jc(_$hash, routingId.hashCode); _$hash = $jc(_$hash, isTaxExempt.hashCode); + _$hash = $jc(_$hash, hasValidVatNumber.hashCode); _$hash = $jc(_$hash, taxData.hashCode); _$hash = $jc(_$hash, classification.hashCode); _$hash = $jc(_$hash, contacts.hashCode); @@ -1381,6 +1395,7 @@ class _$ClientEntity extends ClientEntity { ..add('customValue4', customValue4) ..add('routingId', routingId) ..add('isTaxExempt', isTaxExempt) + ..add('hasValidVatNumber', hasValidVatNumber) ..add('taxData', taxData) ..add('classification', classification) ..add('contacts', contacts) @@ -1565,6 +1580,11 @@ class ClientEntityBuilder bool? get isTaxExempt => _$this._isTaxExempt; set isTaxExempt(bool? isTaxExempt) => _$this._isTaxExempt = isTaxExempt; + bool? _hasValidVatNumber; + bool? get hasValidVatNumber => _$this._hasValidVatNumber; + set hasValidVatNumber(bool? hasValidVatNumber) => + _$this._hasValidVatNumber = hasValidVatNumber; + TaxDataEntityBuilder? _taxData; TaxDataEntityBuilder get taxData => _$this._taxData ??= new TaxDataEntityBuilder(); @@ -1689,6 +1709,7 @@ class ClientEntityBuilder _customValue4 = $v.customValue4; _routingId = $v.routingId; _isTaxExempt = $v.isTaxExempt; + _hasValidVatNumber = $v.hasValidVatNumber; _taxData = $v.taxData.toBuilder(); _classification = $v.classification; _contacts = $v.contacts.toBuilder(); @@ -1775,6 +1796,7 @@ class ClientEntityBuilder customValue4: BuiltValueNullFieldError.checkNotNull(customValue4, r'ClientEntity', 'customValue4'), routingId: BuiltValueNullFieldError.checkNotNull(routingId, r'ClientEntity', 'routingId'), isTaxExempt: BuiltValueNullFieldError.checkNotNull(isTaxExempt, r'ClientEntity', 'isTaxExempt'), + hasValidVatNumber: BuiltValueNullFieldError.checkNotNull(hasValidVatNumber, r'ClientEntity', 'hasValidVatNumber'), taxData: taxData.build(), classification: BuiltValueNullFieldError.checkNotNull(classification, r'ClientEntity', 'classification'), contacts: contacts.build(), diff --git a/lib/ui/client/edit/client_edit_details.dart b/lib/ui/client/edit/client_edit_details.dart index 68cd901cae4..dcc1a85f185 100644 --- a/lib/ui/client/edit/client_edit_details.dart +++ b/lib/ui/client/edit/client_edit_details.dart @@ -319,7 +319,16 @@ class ClientEditDetailsState extends State { viewModel .onChanged(client.rebuild((b) => b..isTaxExempt = value)); }, - ) + ), + if (state.company.calculateTaxes) + SwitchListTile( + title: Text(localization.validVatNumber), + value: client.hasValidVatNumber, + onChanged: (value) { + viewModel.onChanged( + client.rebuild((b) => b..hasValidVatNumber = value)); + }, + ), ], ], ), diff --git a/lib/utils/i18n.dart b/lib/utils/i18n.dart index aba514dfdc3..95b4c03aec0 100644 --- a/lib/utils/i18n.dart +++ b/lib/utils/i18n.dart @@ -18,6 +18,7 @@ mixin LocalizationsProvider on LocaleCodeAware { static final Map> _localizedValues = { 'en': { // STARTER: lang key - do not remove comment + 'valid_vat_number': 'Valid VAT Number', 'use_available_payments': 'Use Available Payments', 'test_email_sent': 'Successfully sent email', 'send_test_email': 'Send Test Email', @@ -114873,6 +114874,10 @@ mixin LocalizationsProvider on LocaleCodeAware { _localizedValues[localeCode]!['use_available_payments'] ?? _localizedValues['en']!['use_available_payments']!; + String get validVatNumber => + _localizedValues[localeCode]!['valid_vat_number'] ?? + _localizedValues['en']!['valid_vat_number']!; + // STARTER: lang field - do not remove comment String lookup(String? key, {String? overrideLocaleCode}) { From 09f07cd78ea6ec74b702d82f3be46870ded077dd Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 21 Feb 2024 14:12:03 +0200 Subject: [PATCH 17/21] Adjust edit client fields --- lib/ui/client/edit/client_edit_notes.dart | 26 -------------------- lib/ui/client/edit/client_edit_settings.dart | 22 +++++++++++++++++ 2 files changed, 22 insertions(+), 26 deletions(-) diff --git a/lib/ui/client/edit/client_edit_notes.dart b/lib/ui/client/edit/client_edit_notes.dart index be4522fda50..2954a9ebf88 100644 --- a/lib/ui/client/edit/client_edit_notes.dart +++ b/lib/ui/client/edit/client_edit_notes.dart @@ -4,10 +4,7 @@ import 'package:flutter/material.dart'; // Project imports: import 'package:invoiceninja_flutter/constants.dart'; import 'package:invoiceninja_flutter/data/models/entities.dart'; -import 'package:invoiceninja_flutter/redux/static/static_selectors.dart'; -import 'package:invoiceninja_flutter/ui/app/entity_dropdown.dart'; import 'package:invoiceninja_flutter/ui/app/form_card.dart'; -import 'package:invoiceninja_flutter/ui/app/forms/app_dropdown_button.dart'; import 'package:invoiceninja_flutter/ui/app/forms/decorated_form_field.dart'; import 'package:invoiceninja_flutter/ui/client/edit/client_edit_vm.dart'; import 'package:invoiceninja_flutter/utils/completers.dart'; @@ -79,7 +76,6 @@ class ClientEditNotesState extends State { final localization = AppLocalization.of(context)!; final viewModel = widget.viewModel; final state = viewModel.state; - final client = viewModel.client; final isFullscreen = state.prefState.isEditorFullScreen(EntityType.client); return FormCard( @@ -104,28 +100,6 @@ class ClientEditNotesState extends State { keyboardType: TextInputType.multiline, label: localization.privateNotes, ), - AppDropdownButton( - value: client.sizeId, - labelText: localization.size, - items: memoizedSizeList(state.staticState.sizeMap) - .map((sizeId) => DropdownMenuItem( - child: Text(state.staticState.sizeMap[sizeId]!.name), - value: sizeId, - )) - .toList(), - onChanged: (dynamic sizeId) => viewModel.onChanged( - client.rebuild((b) => b..sizeId = sizeId), - ), - showBlank: true, - ), - EntityDropdown( - entityType: EntityType.industry, - entityList: memoizedIndustryList(viewModel.staticState.industryMap), - labelText: localization.industry, - entityId: client.industryId, - onSelected: (SelectableEntity? industry) => viewModel.onChanged( - client.rebuild((b) => b..industryId = industry?.id ?? '')), - ), ], ); } diff --git a/lib/ui/client/edit/client_edit_settings.dart b/lib/ui/client/edit/client_edit_settings.dart index 3d18dfcdca4..fcd349e22db 100644 --- a/lib/ui/client/edit/client_edit_settings.dart +++ b/lib/ui/client/edit/client_edit_settings.dart @@ -185,6 +185,28 @@ class ClientEditSettingsState extends State { keyboardType: TextInputType.numberWithOptions(decimal: true, signed: true), ), + AppDropdownButton( + value: client.sizeId, + labelText: localization.size, + items: memoizedSizeList(state.staticState.sizeMap) + .map((sizeId) => DropdownMenuItem( + child: Text(state.staticState.sizeMap[sizeId]!.name), + value: sizeId, + )) + .toList(), + onChanged: (dynamic sizeId) => viewModel.onChanged( + client.rebuild((b) => b..sizeId = sizeId), + ), + showBlank: true, + ), + EntityDropdown( + entityType: EntityType.industry, + entityList: memoizedIndustryList(viewModel.staticState.industryMap), + labelText: localization.industry, + entityId: client.industryId, + onSelected: (SelectableEntity? industry) => viewModel.onChanged( + client.rebuild((b) => b..industryId = industry?.id ?? '')), + ), ], ], ); From 5bf56c99ce9551ec8f555a880c538906f5d7de76 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 21 Feb 2024 14:14:15 +0200 Subject: [PATCH 18/21] Adjust edit client fields --- lib/ui/client/edit/client_edit_notes.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/ui/client/edit/client_edit_notes.dart b/lib/ui/client/edit/client_edit_notes.dart index 2954a9ebf88..862d381ffe8 100644 --- a/lib/ui/client/edit/client_edit_notes.dart +++ b/lib/ui/client/edit/client_edit_notes.dart @@ -9,6 +9,7 @@ import 'package:invoiceninja_flutter/ui/app/forms/decorated_form_field.dart'; import 'package:invoiceninja_flutter/ui/client/edit/client_edit_vm.dart'; import 'package:invoiceninja_flutter/utils/completers.dart'; import 'package:invoiceninja_flutter/utils/localization.dart'; +import 'package:invoiceninja_flutter/utils/platforms.dart'; class ClientEditNotes extends StatefulWidget { const ClientEditNotes({ @@ -89,13 +90,13 @@ class ClientEditNotesState extends State { : null, children: [ DecoratedFormField( - maxLines: 4, + maxLines: isMobile(context) || !isFullscreen ? 8 : 4, controller: _publicNotesController, keyboardType: TextInputType.multiline, label: localization.publicNotes, ), DecoratedFormField( - maxLines: 4, + maxLines: isMobile(context) || !isFullscreen ? 8 : 4, controller: _privateNotesController, keyboardType: TextInputType.multiline, label: localization.privateNotes, From 2c43e920d1b3094c5af52ac88e4903528b916169 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 21 Feb 2024 14:35:08 +0200 Subject: [PATCH 19/21] Fix for report export --- lib/ui/settings/import_export.dart | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/ui/settings/import_export.dart b/lib/ui/settings/import_export.dart index f78692d5b06..46bd8a1de1c 100644 --- a/lib/ui/settings/import_export.dart +++ b/lib/ui/settings/import_export.dart @@ -322,7 +322,28 @@ class _ImportExportState extends State { if (_exportFormat == ImportType.json) { url = '$url/export'; } else { - url = '$url/reports/$_exportType'; + // Workaround for mismatch in report + // names in export vs schedules + if (ExportType.ar_detailed == _exportType) { + url = + '$url/reports/aged_receivable_detailed_report'; + } else if (ExportType.ar_summary == _exportType) { + url = + '$url/reports/aged_receivable_summary_report'; + } else if (ExportType.client_balance == + _exportType) { + url = '$url/reports/client_balance_report'; + } else if (ExportType.client_sales == + _exportType) { + url = '$url/reports/client_sales_report'; + } else if (ExportType.tax_summary == + _exportType) { + url = '$url/reports/tax_summary_report'; + } else if (ExportType.user_sales == _exportType) { + url = '$url/reports/user_sales_report'; + } else { + url = '$url/reports/$_exportType'; + } } setState(() => _isExporting = true); From 3cc1c0b11190d396000c4ea61ca271884dcd20c3 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 21 Feb 2024 14:42:14 +0200 Subject: [PATCH 20/21] Update version --- .github/workflows/flatpak.yml | 2 +- flatpak/com.invoiceninja.InvoiceNinja.metainfo.xml | 1 + lib/constants.dart | 2 +- pubspec.foss.yaml | 2 +- pubspec.yaml | 2 +- snap/snapcraft.yaml | 2 +- 6 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/flatpak.yml b/.github/workflows/flatpak.yml index 89662ece1f7..00245a0f38e 100644 --- a/.github/workflows/flatpak.yml +++ b/.github/workflows/flatpak.yml @@ -86,7 +86,7 @@ jobs: draft: false prerelease: false title: "Latest Release" - automatic_release_tag: "v5.0.155" + automatic_release_tag: "v5.0.156" files: | ${{ github.workspace }}/artifacts/Invoice-Ninja-Archive ${{ github.workspace }}/artifacts/Invoice-Ninja-Hash diff --git a/flatpak/com.invoiceninja.InvoiceNinja.metainfo.xml b/flatpak/com.invoiceninja.InvoiceNinja.metainfo.xml index 3038d086c0c..902d0c17f2a 100644 --- a/flatpak/com.invoiceninja.InvoiceNinja.metainfo.xml +++ b/flatpak/com.invoiceninja.InvoiceNinja.metainfo.xml @@ -50,6 +50,7 @@ + diff --git a/lib/constants.dart b/lib/constants.dart index 8ecf076b124..34d062a99fc 100644 --- a/lib/constants.dart +++ b/lib/constants.dart @@ -6,7 +6,7 @@ class Constants { } // TODO remove version once #46609 is fixed -const String kClientVersion = '5.0.155'; +const String kClientVersion = '5.0.156'; const String kMinServerVersion = '5.0.4'; const String kAppName = 'Invoice Ninja'; diff --git a/pubspec.foss.yaml b/pubspec.foss.yaml index 3fc5803bd19..c4239a094a8 100644 --- a/pubspec.foss.yaml +++ b/pubspec.foss.yaml @@ -1,6 +1,6 @@ name: invoiceninja_flutter description: Client for Invoice Ninja -version: 5.0.155+155 +version: 5.0.156+156 homepage: https://invoiceninja.com documentation: https://invoiceninja.github.io publish_to: none diff --git a/pubspec.yaml b/pubspec.yaml index f919dd104d3..cc8c6f0efe0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: invoiceninja_flutter description: Client for Invoice Ninja -version: 5.0.155+155 +version: 5.0.156+156 homepage: https://invoiceninja.com documentation: https://invoiceninja.github.io publish_to: none diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 1df84c537ce..4a06a6154ca 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: invoiceninja -version: '5.0.155' +version: '5.0.156' summary: Create invoices, accept payments, track expenses & time tasks description: "### Note: if the app fails to run using `snap run invoiceninja` it may help to run `/snap/invoiceninja/current/bin/invoiceninja` instead From 12f68cd74c5eff30f7d7626eeae2d8a29f028f0c Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Wed, 21 Feb 2024 14:46:33 +0200 Subject: [PATCH 21/21] Fix for report export --- lib/ui/settings/import_export.dart | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/ui/settings/import_export.dart b/lib/ui/settings/import_export.dart index 46bd8a1de1c..4d0e0f25a2c 100644 --- a/lib/ui/settings/import_export.dart +++ b/lib/ui/settings/import_export.dart @@ -325,11 +325,9 @@ class _ImportExportState extends State { // Workaround for mismatch in report // names in export vs schedules if (ExportType.ar_detailed == _exportType) { - url = - '$url/reports/aged_receivable_detailed_report'; + url = '$url/reports/ar_detail_report'; } else if (ExportType.ar_summary == _exportType) { - url = - '$url/reports/aged_receivable_summary_report'; + url = '$url/reports/ar_summary_report'; } else if (ExportType.client_balance == _exportType) { url = '$url/reports/client_balance_report';