diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 32aac8f519e..1dc8b7fd3b1 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -44,20 +44,17 @@ PODS: - DKImagePickerController/PhotoGallery - Flutter - Flutter (1.0.0) - - FMDB (2.7.5): - - FMDB/standard (= 2.7.5) - - FMDB/standard (2.7.5) - google_sign_in_ios (0.0.1): - Flutter - - GoogleSignIn (~> 6.2) - - GoogleSignIn (6.2.4): + - GoogleSignIn (~> 7.0) + - GoogleSignIn (7.0.0): - AppAuth (~> 1.5) - - GTMAppAuth (~> 1.3) - - GTMSessionFetcher/Core (< 3.0, >= 1.1) - - GTMAppAuth (1.3.1): + - GTMAppAuth (< 3.0, >= 1.3) + - GTMSessionFetcher/Core (< 4.0, >= 1.1) + - GTMAppAuth (2.0.0): - AppAuth/Core (~> 1.6) - - GTMSessionFetcher/Core (< 3.0, >= 1.5) - - GTMSessionFetcher/Core (2.3.0) + - GTMSessionFetcher/Core (< 4.0, >= 1.5) + - GTMSessionFetcher/Core (3.2.0) - image_cropper (0.0.4): - Flutter - TOCropViewController (~> 2.6.1) @@ -81,16 +78,16 @@ PODS: - Flutter - printing (1.0.0): - Flutter - - SDWebImage (5.18.5): - - SDWebImage/Core (= 5.18.5) - - SDWebImage/Core (5.18.5) - - Sentry/HybridSDK (8.14.2): - - SentryPrivate (= 8.14.2) + - SDWebImage (5.18.7): + - SDWebImage/Core (= 5.18.7) + - SDWebImage/Core (5.18.7) + - Sentry/HybridSDK (8.15.2): + - SentryPrivate (= 8.15.2) - sentry_flutter (0.0.1): - Flutter - FlutterMacOS - - Sentry/HybridSDK (= 8.14.2) - - SentryPrivate (8.14.2) + - Sentry/HybridSDK (= 8.15.2) + - SentryPrivate (8.15.2) - share_plus (0.0.1): - Flutter - shared_preferences_foundation (0.0.1): @@ -100,9 +97,6 @@ PODS: - Flutter - smart_auth (0.0.1): - Flutter - - sqflite (0.0.3): - - Flutter - - FMDB (>= 2.7.5) - SwiftyGif (5.4.4) - TOCropViewController (2.6.1) - url_launcher_ios (0.0.1): @@ -133,7 +127,6 @@ DEPENDENCIES: - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - sign_in_with_apple (from `.symlinks/plugins/sign_in_with_apple/ios`) - smart_auth (from `.symlinks/plugins/smart_auth/ios`) - - sqflite (from `.symlinks/plugins/sqflite/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`) - widget_kit_plugin (from `.symlinks/plugins/widget_kit_plugin/ios`) @@ -143,7 +136,6 @@ SPEC REPOS: - AppAuth - DKImagePickerController - DKPhotoGallery - - FMDB - GoogleSignIn - GTMAppAuth - GTMSessionFetcher @@ -194,8 +186,6 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/sign_in_with_apple/ios" smart_auth: :path: ".symlinks/plugins/smart_auth/ios" - sqflite: - :path: ".symlinks/plugins/sqflite/ios" url_launcher_ios: :path: ".symlinks/plugins/url_launcher_ios/ios" webview_flutter_wkwebview: @@ -211,11 +201,10 @@ SPEC CHECKSUMS: DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 file_picker: ce3938a0df3cc1ef404671531facef740d03f920 Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 - FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a - google_sign_in_ios: 1256ff9d941db546373826966720b0c24804bcdd - GoogleSignIn: 5651ce3a61e56ca864160e79b484cd9ed3f49b7a - GTMAppAuth: 0ff230db599948a9ad7470ca667337803b3fc4dd - GTMSessionFetcher: 3a63d75eecd6aa32c2fc79f578064e1214dfdec2 + google_sign_in_ios: 8115e3fbe097e6509beb819ed602d47369d9011f + GoogleSignIn: b232380cf495a429b8095d3178a8d5855b42e842 + GTMAppAuth: 99fb010047ba3973b7026e45393f51f27ab965ae + GTMSessionFetcher: 41b9ef0b4c08a6db4b7eb51a21ae5183ec99a2c8 image_cropper: a3291c624a953049bc6a02e1f8c8ceb162a24b25 image_picker_ios: 4a8aadfbb6dc30ad5141a2ce3832af9214a705b5 in_app_purchase_storekit: 4fb7ee9e824b1f09107fbfbbce8c4b276366dc43 @@ -226,18 +215,17 @@ SPEC CHECKSUMS: path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6 printing: 233e1b73bd1f4a05615548e9b5a324c98588640b - SDWebImage: 7ac2b7ddc5e8484c79aa90fc4e30b149d6a2c88f - Sentry: e0ea366f95ebb68f26d6030d8c22d6b2e6d23dd0 - sentry_flutter: 9a04c51c373d76ee22167bf1e65bc468c0a91fed - SentryPrivate: 949a21fa59872427edc73b524c3ec8456761d97f + SDWebImage: f9258c58221ed854cfa0e2b80ee4033710b1c6d3 + Sentry: 6f5742b4c47c17c9adcf265f6f328cf4a0ed1923 + sentry_flutter: 2c309a1d4b45e59d02cfa15795705687f1e2081b + SentryPrivate: b2f7996f37781080f04a946eb4e377ff63c64195 share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5 shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 sign_in_with_apple: f3bf75217ea4c2c8b91823f225d70230119b8440 smart_auth: 4bedbc118723912d0e45a07e8ab34039c19e04f2 - sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f TOCropViewController: edfd4f25713d56905ad1e0b9f5be3fbe0f59c863 - url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4 + url_launcher_ios: bf5ce03e0e2088bad9cc378ea97fa0ed5b49673b webview_flutter_wkwebview: 2e2d318f21a5e036e2c3f26171342e95908bd60a widget_kit_plugin: a245a5248f0cd2bde580285ebe6bee5c6f1f6ce1 diff --git a/lib/constants.dart b/lib/constants.dart index d88a7c07c8d..cacc9d17654 100644 --- a/lib/constants.dart +++ b/lib/constants.dart @@ -20,7 +20,7 @@ const String kWhiteLabelUrl = 'https://app.invoiceninja.com/buy_now/?account_key=AsFmBAeLXF0IKf7tmi0eiyZfmWW9hxMT&product_id=3'; const String kPrivacyPolicyURL = 'https://www.invoiceninja.com/privacy-policy'; const String kTermsOfServiceURL = 'https://www.invoiceninja.com/terms'; -const String kBankingURL = 'https://invoiceninja.com/banking/'; +const String kBankingURL = 'https://invoiceninja.com/banking'; const String kTransifexURL = 'https://www.transifex.com/invoice-ninja/invoice-ninja'; const String kWebhookSiteURL = 'https://requestcatcher.com'; @@ -53,6 +53,11 @@ const String kFacebookUrl = 'https://www.facebook.com/invoiceninja'; const String kYouTubeUrl = 'https://www.youtube.com/channel/UCXAHcBvhW05PDtWYIq7WDFA/videos'; +const String kYodleeCoverageUrl = + 'https://www.yodlee.com/open-banking/data-connections'; +const String kNordigenCoverageUrl = + 'https://gocardless.com/bank-account-data/coverage'; + const String kTaskExtensionUrl = 'https://chromewebstore.google.com/detail/invoice-ninja-tasks/dlfcbfdpemfnjbjlladogijcchfmmaaf'; const String kTaskExtensionYouTubeUrl = diff --git a/lib/data/models/bank_account_model.dart b/lib/data/models/bank_account_model.dart index bfc117f7406..891d7feb3c9 100644 --- a/lib/data/models/bank_account_model.dart +++ b/lib/data/models/bank_account_model.dart @@ -73,11 +73,15 @@ abstract class BankAccountEntity extends Object disabledUpstream: false, fromDate: '', autoSync: false, + integrationType: '', ); } BankAccountEntity._(); + static const String INTEGRATION_TYPE_YODLEE = 'yodlee'; + static const String INTEGRATION_TYPE_NORDIGEN = 'nordigen'; + @override @memoized int get hashCode; @@ -103,6 +107,9 @@ abstract class BankAccountEntity extends Object @BuiltValueField(wireName: 'disabled_upstream') bool get disabledUpstream; + @BuiltValueField(wireName: 'integration_type') + String get integrationType; + double get balance; String get currency; @@ -203,7 +210,8 @@ abstract class BankAccountEntity extends Object static void _initializeBuilder(BankAccountEntityBuilder builder) => builder ..fromDate = '' ..disabledUpstream = false - ..autoSync = false; + ..autoSync = false + ..integrationType = ''; static Serializer get serializer => _$bankAccountEntitySerializer; diff --git a/lib/data/models/bank_account_model.g.dart b/lib/data/models/bank_account_model.g.dart index 4817ae1b7f7..38ee32df291 100644 --- a/lib/data/models/bank_account_model.g.dart +++ b/lib/data/models/bank_account_model.g.dart @@ -139,6 +139,9 @@ class _$BankAccountEntitySerializer 'disabled_upstream', serializers.serialize(object.disabledUpstream, specifiedType: const FullType(bool)), + 'integration_type', + serializers.serialize(object.integrationType, + specifiedType: const FullType(String)), 'balance', serializers.serialize(object.balance, specifiedType: const FullType(double)), @@ -229,6 +232,10 @@ class _$BankAccountEntitySerializer result.disabledUpstream = serializers.deserialize(value, specifiedType: const FullType(bool))! as bool; break; + case 'integration_type': + result.integrationType = serializers.deserialize(value, + specifiedType: const FullType(String))! as String; + break; case 'balance': result.balance = serializers.deserialize(value, specifiedType: const FullType(double))! as double; @@ -494,6 +501,8 @@ class _$BankAccountEntity extends BankAccountEntity { @override final bool disabledUpstream; @override + final String integrationType; + @override final double balance; @override final String currency; @@ -526,6 +535,7 @@ class _$BankAccountEntity extends BankAccountEntity { required this.fromDate, required this.autoSync, required this.disabledUpstream, + required this.integrationType, required this.balance, required this.currency, this.isChanged, @@ -549,6 +559,8 @@ class _$BankAccountEntity extends BankAccountEntity { autoSync, r'BankAccountEntity', 'autoSync'); BuiltValueNullFieldError.checkNotNull( disabledUpstream, r'BankAccountEntity', 'disabledUpstream'); + BuiltValueNullFieldError.checkNotNull( + integrationType, r'BankAccountEntity', 'integrationType'); BuiltValueNullFieldError.checkNotNull( balance, r'BankAccountEntity', 'balance'); BuiltValueNullFieldError.checkNotNull( @@ -581,6 +593,7 @@ class _$BankAccountEntity extends BankAccountEntity { fromDate == other.fromDate && autoSync == other.autoSync && disabledUpstream == other.disabledUpstream && + integrationType == other.integrationType && balance == other.balance && currency == other.currency && isChanged == other.isChanged && @@ -605,6 +618,7 @@ class _$BankAccountEntity extends BankAccountEntity { _$hash = $jc(_$hash, fromDate.hashCode); _$hash = $jc(_$hash, autoSync.hashCode); _$hash = $jc(_$hash, disabledUpstream.hashCode); + _$hash = $jc(_$hash, integrationType.hashCode); _$hash = $jc(_$hash, balance.hashCode); _$hash = $jc(_$hash, currency.hashCode); _$hash = $jc(_$hash, isChanged.hashCode); @@ -629,6 +643,7 @@ class _$BankAccountEntity extends BankAccountEntity { ..add('fromDate', fromDate) ..add('autoSync', autoSync) ..add('disabledUpstream', disabledUpstream) + ..add('integrationType', integrationType) ..add('balance', balance) ..add('currency', currency) ..add('isChanged', isChanged) @@ -676,6 +691,11 @@ class BankAccountEntityBuilder set disabledUpstream(bool? disabledUpstream) => _$this._disabledUpstream = disabledUpstream; + String? _integrationType; + String? get integrationType => _$this._integrationType; + set integrationType(String? integrationType) => + _$this._integrationType = integrationType; + double? _balance; double? get balance => _$this._balance; set balance(double? balance) => _$this._balance = balance; @@ -732,6 +752,7 @@ class BankAccountEntityBuilder _fromDate = $v.fromDate; _autoSync = $v.autoSync; _disabledUpstream = $v.disabledUpstream; + _integrationType = $v.integrationType; _balance = $v.balance; _currency = $v.currency; _isChanged = $v.isChanged; @@ -778,10 +799,11 @@ class BankAccountEntityBuilder autoSync, r'BankAccountEntity', 'autoSync'), disabledUpstream: BuiltValueNullFieldError.checkNotNull( disabledUpstream, r'BankAccountEntity', 'disabledUpstream'), + integrationType: BuiltValueNullFieldError.checkNotNull( + integrationType, r'BankAccountEntity', 'integrationType'), balance: BuiltValueNullFieldError.checkNotNull( balance, r'BankAccountEntity', 'balance'), - currency: BuiltValueNullFieldError.checkNotNull( - currency, r'BankAccountEntity', 'currency'), + currency: BuiltValueNullFieldError.checkNotNull(currency, r'BankAccountEntity', 'currency'), isChanged: isChanged, createdAt: BuiltValueNullFieldError.checkNotNull(createdAt, r'BankAccountEntity', 'createdAt'), updatedAt: BuiltValueNullFieldError.checkNotNull(updatedAt, r'BankAccountEntity', 'updatedAt'), diff --git a/lib/data/models/transaction_model.dart b/lib/data/models/transaction_model.dart index f3dad3f5a26..68abf1c13d1 100644 --- a/lib/data/models/transaction_model.dart +++ b/lib/data/models/transaction_model.dart @@ -107,6 +107,8 @@ abstract class TransactionEntity extends Object categoryId: '', transactionRuleId: '', paymentId: '', + participant: '', + participantName: '', ); } @@ -161,7 +163,11 @@ abstract class TransactionEntity extends Object @BuiltValueField(wireName: 'bank_transaction_rule_id') String get transactionRuleId; - @BuiltValueField(serialize: false) + @BuiltValueField(wireName: 'participant_name') + String get participantName; + + String get participant; + String? get pendingVendorId; @BuiltValueField(serialize: false) @@ -383,7 +389,9 @@ abstract class TransactionEntity extends Object ..bankAccountId = '' ..transactionRuleId = '' ..paymentId = '' - ..currencyId = ''; + ..currencyId = '' + ..participantName = '' + ..participant = ''; static Serializer get serializer => _$transactionEntitySerializer; diff --git a/lib/data/models/transaction_model.g.dart b/lib/data/models/transaction_model.g.dart index 412d03911c0..3880f6bb4aa 100644 --- a/lib/data/models/transaction_model.g.dart +++ b/lib/data/models/transaction_model.g.dart @@ -166,6 +166,12 @@ class _$TransactionEntitySerializer 'bank_transaction_rule_id', serializers.serialize(object.transactionRuleId, specifiedType: const FullType(String)), + 'participant_name', + serializers.serialize(object.participantName, + specifiedType: const FullType(String)), + 'participant', + serializers.serialize(object.participant, + specifiedType: const FullType(String)), 'created_at', serializers.serialize(object.createdAt, specifiedType: const FullType(int)), @@ -179,6 +185,13 @@ class _$TransactionEntitySerializer serializers.serialize(object.id, specifiedType: const FullType(String)), ]; Object? value; + value = object.pendingVendorId; + if (value != null) { + result + ..add('pendingVendorId') + ..add(serializers.serialize(value, + specifiedType: const FullType(String))); + } value = object.isChanged; if (value != null) { result @@ -282,6 +295,18 @@ class _$TransactionEntitySerializer result.transactionRuleId = serializers.deserialize(value, specifiedType: const FullType(String))! as String; break; + case 'participant_name': + result.participantName = serializers.deserialize(value, + specifiedType: const FullType(String))! as String; + break; + case 'participant': + result.participant = serializers.deserialize(value, + specifiedType: const FullType(String))! as String; + break; + case 'pendingVendorId': + result.pendingVendorId = serializers.deserialize(value, + specifiedType: const FullType(String)) as String?; + break; case 'isChanged': result.isChanged = serializers.deserialize(value, specifiedType: const FullType(bool)) as bool?; @@ -606,6 +631,10 @@ class _$TransactionEntity extends TransactionEntity { @override final String transactionRuleId; @override + final String participantName; + @override + final String participant; + @override final String? pendingVendorId; @override final String? pendingCategoryId; @@ -648,6 +677,8 @@ class _$TransactionEntity extends TransactionEntity { required this.vendorId, required this.transactionId, required this.transactionRuleId, + required this.participantName, + required this.participant, this.pendingVendorId, this.pendingCategoryId, this.pendingExpenseId, @@ -689,6 +720,10 @@ class _$TransactionEntity extends TransactionEntity { transactionId, r'TransactionEntity', 'transactionId'); BuiltValueNullFieldError.checkNotNull( transactionRuleId, r'TransactionEntity', 'transactionRuleId'); + BuiltValueNullFieldError.checkNotNull( + participantName, r'TransactionEntity', 'participantName'); + BuiltValueNullFieldError.checkNotNull( + participant, r'TransactionEntity', 'participant'); BuiltValueNullFieldError.checkNotNull( createdAt, r'TransactionEntity', 'createdAt'); BuiltValueNullFieldError.checkNotNull( @@ -725,6 +760,8 @@ class _$TransactionEntity extends TransactionEntity { vendorId == other.vendorId && transactionId == other.transactionId && transactionRuleId == other.transactionRuleId && + participantName == other.participantName && + participant == other.participant && pendingVendorId == other.pendingVendorId && pendingCategoryId == other.pendingCategoryId && pendingExpenseId == other.pendingExpenseId && @@ -758,6 +795,8 @@ class _$TransactionEntity extends TransactionEntity { _$hash = $jc(_$hash, vendorId.hashCode); _$hash = $jc(_$hash, transactionId.hashCode); _$hash = $jc(_$hash, transactionRuleId.hashCode); + _$hash = $jc(_$hash, participantName.hashCode); + _$hash = $jc(_$hash, participant.hashCode); _$hash = $jc(_$hash, pendingVendorId.hashCode); _$hash = $jc(_$hash, pendingCategoryId.hashCode); _$hash = $jc(_$hash, pendingExpenseId.hashCode); @@ -791,6 +830,8 @@ class _$TransactionEntity extends TransactionEntity { ..add('vendorId', vendorId) ..add('transactionId', transactionId) ..add('transactionRuleId', transactionRuleId) + ..add('participantName', participantName) + ..add('participant', participant) ..add('pendingVendorId', pendingVendorId) ..add('pendingCategoryId', pendingCategoryId) ..add('pendingExpenseId', pendingExpenseId) @@ -873,6 +914,15 @@ class TransactionEntityBuilder set transactionRuleId(String? transactionRuleId) => _$this._transactionRuleId = transactionRuleId; + String? _participantName; + String? get participantName => _$this._participantName; + set participantName(String? participantName) => + _$this._participantName = participantName; + + String? _participant; + String? get participant => _$this._participant; + set participant(String? participant) => _$this._participant = participant; + String? _pendingVendorId; String? get pendingVendorId => _$this._pendingVendorId; set pendingVendorId(String? pendingVendorId) => @@ -944,6 +994,8 @@ class TransactionEntityBuilder _vendorId = $v.vendorId; _transactionId = $v.transactionId; _transactionRuleId = $v.transactionRuleId; + _participantName = $v.participantName; + _participant = $v.participant; _pendingVendorId = $v.pendingVendorId; _pendingCategoryId = $v.pendingCategoryId; _pendingExpenseId = $v.pendingExpenseId; @@ -1001,6 +1053,8 @@ class TransactionEntityBuilder vendorId: BuiltValueNullFieldError.checkNotNull(vendorId, r'TransactionEntity', 'vendorId'), transactionId: BuiltValueNullFieldError.checkNotNull(transactionId, r'TransactionEntity', 'transactionId'), transactionRuleId: BuiltValueNullFieldError.checkNotNull(transactionRuleId, r'TransactionEntity', 'transactionRuleId'), + participantName: BuiltValueNullFieldError.checkNotNull(participantName, r'TransactionEntity', 'participantName'), + participant: BuiltValueNullFieldError.checkNotNull(participant, r'TransactionEntity', 'participant'), pendingVendorId: pendingVendorId, pendingCategoryId: pendingCategoryId, pendingExpenseId: pendingExpenseId, diff --git a/lib/ui/app/buttons/elevated_button.dart b/lib/ui/app/buttons/elevated_button.dart index 30dead72543..895590bcc6e 100644 --- a/lib/ui/app/buttons/elevated_button.dart +++ b/lib/ui/app/buttons/elevated_button.dart @@ -6,6 +6,7 @@ import 'package:flutter_redux/flutter_redux.dart'; import 'package:invoiceninja_flutter/constants.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/ui/app/icon_text.dart'; +import 'package:invoiceninja_flutter/utils/platforms.dart'; class AppButton extends StatelessWidget { const AppButton({ @@ -38,7 +39,7 @@ class AppButton extends StatelessWidget { color: Colors.white, ), ), - child: iconData != null + child: iconData != null && isDesktop(context) ? IconText( icon: iconData, text: label, diff --git a/lib/ui/app/forms/learn_more.dart b/lib/ui/app/forms/learn_more.dart index 24a807e7c91..8f24a94a192 100644 --- a/lib/ui/app/forms/learn_more.dart +++ b/lib/ui/app/forms/learn_more.dart @@ -12,11 +12,13 @@ class LearnMoreUrl extends StatelessWidget { required this.child, required this.url, this.label, + this.isVertical = false, }); final Widget child; final String url; final String? label; + final bool isVertical; @override Widget build(BuildContext context) { @@ -26,26 +28,43 @@ class LearnMoreUrl extends StatelessWidget { return child; } - return Row( - children: [ - Expanded( - flex: 2, - child: child, - ), - SizedBox( - width: 10, - ), - Expanded( - child: TextButton( + if (isVertical) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + child, + SizedBox(height: 8), + OutlinedButton( child: Text( label ?? localization!.learnMore, maxLines: 4, ), onPressed: () => launchUrl(Uri.parse(url)), ), - ), - ], - ); + ], + ); + } else { + return Row( + children: [ + Expanded( + flex: 2, + child: child, + ), + SizedBox( + width: 10, + ), + Expanded( + child: TextButton( + child: Text( + label ?? localization!.learnMore, + maxLines: 4, + ), + onPressed: () => launchUrl(Uri.parse(url)), + ), + ), + ], + ); + } } } diff --git a/lib/ui/bank_account/bank_account_screen.dart b/lib/ui/bank_account/bank_account_screen.dart index 9b2880c4f22..444d1c2e81b 100644 --- a/lib/ui/bank_account/bank_account_screen.dart +++ b/lib/ui/bank_account/bank_account_screen.dart @@ -19,6 +19,7 @@ import 'package:invoiceninja_flutter/ui/bank_account/bank_account_presenter.dart import 'package:invoiceninja_flutter/utils/dialogs.dart'; import 'package:invoiceninja_flutter/utils/formatting.dart'; import 'package:invoiceninja_flutter/utils/localization.dart'; +import 'package:invoiceninja_flutter/utils/platforms.dart'; import 'package:url_launcher/url_launcher.dart'; import 'bank_account_screen_vm.dart'; @@ -34,6 +35,88 @@ class BankAccountScreen extends StatelessWidget { final BankAccountScreenVM viewModel; void connectAccounts(BuildContext context) { + final localization = AppLocalization.of(context)!; + + if (isSelfHosted(context)) { + _connectAccounts(context, BankAccountEntity.INTEGRATION_TYPE_NORDIGEN); + } else { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: Text(localization.selectProvider), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text(localization.close.toUpperCase())) + ], + content: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text('Envestnet - Yodlee'), + Text(localization.yodleeRegions, + style: Theme.of(context).textTheme.bodySmall), + Row( + children: [ + Expanded( + child: AppButton( + label: localization.learnMore.toUpperCase(), + onPressed: () => + launchUrl(Uri.parse(kYodleeCoverageUrl)), + iconData: Icons.info_outline, + ), + ), + SizedBox(width: kTableColumnGap), + Expanded( + child: AppButton( + label: localization.connect.toUpperCase(), + onPressed: () => _connectAccounts( + context, + BankAccountEntity.INTEGRATION_TYPE_YODLEE, + ), + iconData: Icons.link, + ), + ), + ], + ), + SizedBox(height: 30), + Text('GoCardless - Nordigen'), + Text(localization.nordigenRegions, + style: Theme.of(context).textTheme.bodySmall), + Row( + children: [ + Expanded( + child: AppButton( + label: localization.learnMore.toUpperCase(), + onPressed: () => + launchUrl(Uri.parse(kNordigenCoverageUrl)), + iconData: Icons.info_outline, + ), + ), + SizedBox(width: kTableColumnGap), + Expanded( + child: AppButton( + label: localization.connect.toUpperCase(), + onPressed: () => _connectAccounts( + context, + BankAccountEntity.INTEGRATION_TYPE_NORDIGEN, + ), + iconData: Icons.link, + ), + ), + ], + ), + ], + ), + ), + ); + }, + ); + } + } + + void _connectAccounts(BuildContext context, String integrationType) { final store = StoreProvider.of(context); final state = store.state; final webClient = WebClient(); @@ -41,16 +124,25 @@ class BankAccountScreen extends StatelessWidget { final url = '${credentials.url}/one_time_token'; store.dispatch(StartSaving()); - webClient .post(url, credentials.token, data: jsonEncode({ - 'context': {'return_url': ''} + 'context': + integrationType == BankAccountEntity.INTEGRATION_TYPE_YODLEE + ? {'return_url': ''} + : 'nordigen', })) .then((dynamic response) { store.dispatch(StopSaving()); - launchUrl(Uri.parse( - '${cleanApiUrl(credentials.url)}/yodlee/onboard/${response['hash']}')); + + String connectUrl = cleanApiUrl(credentials.url); + if (integrationType == BankAccountEntity.INTEGRATION_TYPE_YODLEE) { + connectUrl += '/yodlee/onboard/${response['hash']}'; + } else { + connectUrl += '/nordigen/connect/${response['hash']}'; + } + + launchUrl(Uri.parse(connectUrl)); }).catchError((dynamic error) { store.dispatch(StopSaving()); showErrorDialog(message: '$error'); @@ -62,7 +154,7 @@ class BankAccountScreen extends StatelessWidget { final store = StoreProvider.of(context); final state = store.state; final userCompany = state.userCompany; - final localization = AppLocalization.of(context); + final localization = AppLocalization.of(context)!; return ListScaffold( entityType: EntityType.bankAccount, @@ -94,63 +186,61 @@ class BankAccountScreen extends StatelessWidget { const EdgeInsets.only(left: 16, top: 8, right: 16, bottom: 10), child: Row( children: [ - if (state.isHosted) ...[ - if (state.isEnterprisePlan) ...[ - Expanded( - child: AppButton( - label: localization!.connect.toUpperCase(), - onPressed: () => connectAccounts(context), - iconData: Icons.link, - ), + if (state.isEnterprisePlan) ...[ + Expanded( + child: AppButton( + label: localization.connect.toUpperCase(), + onPressed: () => connectAccounts(context), + iconData: Icons.link, ), - SizedBox(width: kGutterWidth), - Expanded( - child: AppButton( - label: localization.refresh.toUpperCase(), - onPressed: () => viewModel.onRefreshAccounts(context), - iconData: Icons.refresh, - ), + ), + SizedBox(width: kGutterWidth), + Expanded( + child: AppButton( + label: localization.refresh.toUpperCase(), + onPressed: () => viewModel.onRefreshAccounts(context), + iconData: Icons.refresh, ), - ] else - Expanded( - child: Padding( - padding: const EdgeInsets.only(top: 20, bottom: 8), - child: Center( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - HelpText(localization!.upgradeToConnectBankAccount), - SizedBox(height: 16), - Row( - children: [ - TextButton( - onPressed: () => - launchUrl(Uri.parse(kBankingURL)), - child: Text(localization.learnMore), - ), - TextButton( - onPressed: () { - store.dispatch(ViewSettings( - clearFilter: true, - company: state.company, - user: state.user, - section: kSettingsAccountManagement)); - }, - child: Text(localization.upgrade), - ), - ], - ) - ], - )), - ), + ), + ] else + Expanded( + child: Padding( + padding: const EdgeInsets.only(top: 20, bottom: 8), + child: Center( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + HelpText(localization.upgradeToConnectBankAccount), + SizedBox(height: 16), + Row( + children: [ + TextButton( + onPressed: () => + launchUrl(Uri.parse(kBankingURL)), + child: Text(localization.learnMore), + ), + TextButton( + onPressed: () { + store.dispatch(ViewSettings( + clearFilter: true, + company: state.company, + user: state.user, + section: kSettingsAccountManagement)); + }, + child: Text(localization.upgrade), + ), + ], + ) + ], + )), ), - SizedBox(width: kGutterWidth), - ], + ), + SizedBox(width: kGutterWidth), Expanded( child: AppButton( label: (state.isHosted - ? localization!.rules - : localization!.manageRules) + ? localization.rules + : localization.manageRules) .toUpperCase(), onPressed: () { store.dispatch( diff --git a/lib/ui/invoice/edit/invoice_edit_items_desktop.dart b/lib/ui/invoice/edit/invoice_edit_items_desktop.dart index 43e02c2ebf7..76bd7be4abf 100644 --- a/lib/ui/invoice/edit/invoice_edit_items_desktop.dart +++ b/lib/ui/invoice/edit/invoice_edit_items_desktop.dart @@ -711,7 +711,7 @@ class _InvoiceEditItemsDesktopState extends State { ..notes = item.isTask ? item.notes : product.notes - ..productCost = item.productCost + ..productCost = product.cost ..cost = item.isTask && item.cost != 0 ? item.cost : cost diff --git a/lib/utils/i18n.dart b/lib/utils/i18n.dart index a78c38ab822..b27e1dfec8d 100644 --- a/lib/utils/i18n.dart +++ b/lib/utils/i18n.dart @@ -18,6 +18,9 @@ mixin LocalizationsProvider on LocaleCodeAware { static final Map> _localizedValues = { 'en': { // STARTER: lang key - do not remove comment + 'yodlee_regions': 'Regions: USA, UK, Australia & India', + 'nordigen_regions': 'Regions: Europe & UK', + 'select_provider': 'Select Provider', 'payment_type_credit': 'Payment Type Credit', 'payment_type_debit': 'Payment Type Debit', 'send_emails_to': 'Send Emails To', @@ -111466,6 +111469,18 @@ mixin LocalizationsProvider on LocaleCodeAware { _localizedValues[localeCode]!['send_emails_to'] ?? _localizedValues['en']!['send_emails_to']!; + String get selectProvider => + _localizedValues[localeCode]!['select_provider'] ?? + _localizedValues['en']!['select_provider']!; + + String get yodleeRegions => + _localizedValues[localeCode]!['yodlee_regions'] ?? + _localizedValues['en']!['yodlee_regions']!; + + String get nordigenRegions => + _localizedValues[localeCode]!['nordigen_regions'] ?? + _localizedValues['en']!['nordigen_regions']!; + // STARTER: lang field - do not remove comment String lookup(String? key) { diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 4eabd58bf1c..7630377e229 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -6,9 +6,6 @@ PODS: - file_selector_macos (0.0.1): - FlutterMacOS - FlutterMacOS (1.0.0) - - FMDB (2.7.5): - - FMDB/standard (= 2.7.5) - - FMDB/standard (2.7.5) - in_app_purchase_storekit (0.0.1): - Flutter - FlutterMacOS @@ -25,13 +22,13 @@ PODS: - FlutterMacOS - screen_retriever (0.0.1): - FlutterMacOS - - Sentry/HybridSDK (8.14.2): - - SentryPrivate (= 8.14.2) + - Sentry/HybridSDK (8.15.2): + - SentryPrivate (= 8.15.2) - sentry_flutter (0.0.1): - Flutter - FlutterMacOS - - Sentry/HybridSDK (= 8.14.2) - - SentryPrivate (8.14.2) + - Sentry/HybridSDK (= 8.15.2) + - SentryPrivate (8.15.2) - share_plus (0.0.1): - FlutterMacOS - shared_preferences_foundation (0.0.1): @@ -41,9 +38,6 @@ PODS: - FlutterMacOS - smart_auth (0.0.1): - FlutterMacOS - - sqflite (0.0.2): - - FlutterMacOS - - FMDB (>= 2.7.5) - url_launcher_macos (0.0.1): - FlutterMacOS - widget_kit_plugin (0.0.1): @@ -68,14 +62,12 @@ DEPENDENCIES: - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) - sign_in_with_apple (from `Flutter/ephemeral/.symlinks/plugins/sign_in_with_apple/macos`) - smart_auth (from `Flutter/ephemeral/.symlinks/plugins/smart_auth/macos`) - - sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/macos`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) - widget_kit_plugin (from `Flutter/ephemeral/.symlinks/plugins/widget_kit_plugin/macos`) - window_manager (from `Flutter/ephemeral/.symlinks/plugins/window_manager/macos`) SPEC REPOS: trunk: - - FMDB - Sentry - SentryPrivate @@ -112,8 +104,6 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/sign_in_with_apple/macos smart_auth: :path: Flutter/ephemeral/.symlinks/plugins/smart_auth/macos - sqflite: - :path: Flutter/ephemeral/.symlinks/plugins/sqflite/macos url_launcher_macos: :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos widget_kit_plugin: @@ -126,7 +116,6 @@ SPEC CHECKSUMS: device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f file_selector_macos: 468fb6b81fac7c0e88d71317f3eec34c3b008ff9 FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 - FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a in_app_purchase_storekit: 4fb7ee9e824b1f09107fbfbbce8c4b276366dc43 in_app_review: a850789fad746e89bce03d4aeee8078b45a53fd0 package_info: 6eba2fd8d3371dda2d85c8db6fe97488f24b74b2 @@ -134,14 +123,13 @@ SPEC CHECKSUMS: path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 printing: 1dd6a1fce2209ec240698e2439a4adbb9b427637 screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38 - Sentry: e0ea366f95ebb68f26d6030d8c22d6b2e6d23dd0 - sentry_flutter: 9a04c51c373d76ee22167bf1e65bc468c0a91fed - SentryPrivate: 949a21fa59872427edc73b524c3ec8456761d97f + Sentry: 6f5742b4c47c17c9adcf265f6f328cf4a0ed1923 + sentry_flutter: 2c309a1d4b45e59d02cfa15795705687f1e2081b + SentryPrivate: b2f7996f37781080f04a946eb4e377ff63c64195 share_plus: 76dd39142738f7a68dd57b05093b5e8193f220f7 shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 sign_in_with_apple: a9e97e744e8edc36aefc2723111f652102a7a727 smart_auth: b38e3ab4bfe089eacb1e233aca1a2340f96c28e9 - sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95 widget_kit_plugin: 9658611f1ba5faaaa9e8221d94fce53733a6911c window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8