From 24a9444a45f75669b5aa474a87cdc2b8d11edda8 Mon Sep 17 00:00:00 2001 From: Egor Komarov Date: Thu, 26 Sep 2024 12:51:53 +0200 Subject: [PATCH] feat(EWM-313): simulate transaction tree # Conflicts: # lib/feature/wallet/token_wallet_send/view/token_wallet_send_confirm_view.dart # lib/feature/wallet/ton_wallet_send/view/ton_wallet_send_confirm_view.dart --- assets/translations/en.json | 19 ++- assets/translations/ko.json | 19 ++- .../send_message/send_message_model.dart | 66 +++++--- .../send_message/send_message_widget.dart | 20 ++- .../actions/send_message/send_message_wm.dart | 45 +++++- .../bloc/token_wallet_send_bloc.dart | 12 +- .../bloc/token_wallet_send_bloc.freezed.dart | 138 +++++++++++++---- .../bloc/token_wallet_send_state.dart | 1 + .../view/token_wallet_send_confirm_view.dart | 19 ++- .../view/token_wallet_send_page.dart | 5 +- .../bloc/ton_wallet_send_bloc.dart | 15 +- .../bloc/ton_wallet_send_bloc.freezed.dart | 108 +++++++++---- .../bloc/ton_wallet_send_state.dart | 5 +- .../view/ton_wallet_send_confirm_view.dart | 18 ++- .../view/ton_wallet_send_page.dart | 11 +- lib/generated/locale_keys.g.dart | 21 +++ .../tx_tree_simulation_error_widget.dart | 146 ++++++++++++++++++ lib/widgets/widgets.dart | 5 + 18 files changed, 554 insertions(+), 119 deletions(-) create mode 100644 lib/widgets/tx_tree_simulation_error_widget.dart create mode 100644 lib/widgets/widgets.dart diff --git a/assets/translations/en.json b/assets/translations/en.json index 4de96a27c..63de179a1 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -571,5 +571,22 @@ "invalidReceiverAddress": "Invalid receiver address", "deleteBookmarksQuestion": "Are you sure you want\nto delete all bookmarks?", "deleteBookmarksDescription": "All bookmarks will be permanently deleted", - "save": "Save" + "save": "Save", + "txTreeSimulationErrorComputePhase": { + "0": "Execution failed on ", + "1": " with exit code {}." + }, + "txTreeSimulationErrorActionPhase": { + "0": "Action phase failed on ", + "1": " with exit code {}." + }, + "txTreeSimulationErrorFrozen": { + "0": "Account ", + "1": " will be frozen due to storage fee debt." + }, + "txTreeSimulationErrorDeleted": { + "0": "Account ", + "1": " will be deleted due to storage fee debt." + }, + "txTreeSimulationErrorHint": "Please contact technical support." } diff --git a/assets/translations/ko.json b/assets/translations/ko.json index 40b6bd2e7..75d6b5392 100644 --- a/assets/translations/ko.json +++ b/assets/translations/ko.json @@ -571,5 +571,22 @@ "invalidReceiverAddress": "Invalid receiver address", "deleteBookmarksQuestion": "Are you sure you want\nto delete all bookmarks?", "deleteBookmarksDescription": "All bookmarks will be permanently deleted", - "save": "Save" + "save": "Save", + "txTreeSimulationErrorComputePhase": { + "0": "Execution failed on ", + "1": " with exit code {}." + }, + "txTreeSimulationErrorActionPhase": { + "0": "Action phase failed on ", + "1": " with exit code {}." + }, + "txTreeSimulationErrorFrozen": { + "0": "Account ", + "1": " will be frozen due to storage fee debt." + }, + "txTreeSimulationErrorDeleted": { + "0": "Account ", + "1": " will be deleted due to storage fee debt." + }, + "txTreeSimulationErrorHint": "Please contact technical support." } diff --git a/lib/feature/browser/approvals_listener/actions/send_message/send_message_model.dart b/lib/feature/browser/approvals_listener/actions/send_message/send_message_model.dart index 83e1ed8d3..5fea2a053 100644 --- a/lib/feature/browser/approvals_listener/actions/send_message/send_message_model.dart +++ b/lib/feature/browser/approvals_listener/actions/send_message/send_message_model.dart @@ -1,17 +1,21 @@ +import 'package:app/app/service/service.dart'; import 'package:app/utils/constants.dart'; import 'package:app/utils/utils.dart'; import 'package:collection/collection.dart'; import 'package:elementary/elementary.dart'; -import 'package:nekoton_repository/nekoton_repository.dart'; +import 'package:flutter/material.dart'; +import 'package:nekoton_repository/nekoton_repository.dart' hide Message; import 'package:rxdart/rxdart.dart'; class SendMessageModel extends ElementaryModel { SendMessageModel( ErrorHandler errorHandler, this._nekotonRepository, + this._messengerService, ) : super(errorHandler: errorHandler); final NekotonRepository _nekotonRepository; + final MessengerService _messengerService; TransportStrategy get transport => _nekotonRepository.currentTransport; @@ -32,7 +36,7 @@ class SendMessageModel extends ElementaryModel { Future?> getLocalCustodiansAsync(Address address) => _nekotonRepository.getLocalCustodiansAsync(address); - Future estimateFees({ + Future prepareTransfer({ required Address address, required Address destination, required PublicKey? publicKey, @@ -40,35 +44,42 @@ class SendMessageModel extends ElementaryModel { required FunctionCall? payload, required bool bounce, }) async { - UnsignedMessage? message; + final body = await payload?.let( + (value) => encodeInternalInput( + contractAbi: payload.abi, + method: payload.method, + input: payload.params, + ), + ); - try { - final body = await payload?.let( - (value) => encodeInternalInput( - contractAbi: payload.abi, - method: payload.method, - input: payload.params, - ), - ); + return _nekotonRepository.prepareTransfer( + address: address, + publicKey: publicKey, + destination: destination, + amount: amount, + body: body, + bounce: bounce, + expiration: defaultSendTimeout, + ); + } - message = await _nekotonRepository.prepareTransfer( + Future estimateFees({ + required Address address, + required UnsignedMessage message, + }) => + _nekotonRepository.estimateFees( address: address, - publicKey: publicKey, - destination: destination, - amount: amount, - body: body, - bounce: bounce, - expiration: defaultSendTimeout, + message: message, ); - return await _nekotonRepository.estimateFees( + Future> simulateTransactionTree({ + required Address address, + required UnsignedMessage message, + }) => + _nekotonRepository.simulateTransactionTree( address: address, message: message, ); - } finally { - message?.dispose(); - } - } String? getSeedName(PublicKey custodian) => _nekotonRepository.seedList.findSeedKey(custodian)?.name; @@ -82,4 +93,13 @@ class SendMessageModel extends ElementaryModel { return (details.item1, details.item2); } + + void showError(BuildContext context, String message) { + _messengerService.show( + Message.error( + context: context, + message: message, + ), + ); + } } diff --git a/lib/feature/browser/approvals_listener/actions/send_message/send_message_widget.dart b/lib/feature/browser/approvals_listener/actions/send_message/send_message_widget.dart index 2a626f08d..f8d15abed 100644 --- a/lib/feature/browser/approvals_listener/actions/send_message/send_message_widget.dart +++ b/lib/feature/browser/approvals_listener/actions/send_message/send_message_widget.dart @@ -4,6 +4,7 @@ import 'package:app/feature/profile/profile.dart'; import 'package:app/feature/wallet/wallet.dart'; import 'package:app/generated/generated.dart'; import 'package:app/utils/utils.dart'; +import 'package:app/widgets/widgets.dart'; import 'package:elementary/elementary.dart'; import 'package:elementary_helper/elementary_helper.dart'; import 'package:flutter/material.dart'; @@ -132,10 +133,21 @@ class SendMessageWidget extends ElementaryWidget { ), ), if (wm.account != null) - EnterPasswordWidgetV2( - publicKey: wm.account!.publicKey, - title: LocaleKeys.sendWord.tr(), - onPasswordEntered: wm.onSubmit, + DoubleSourceBuilder( + firstSource: wm.isLoading, + secondSource: wm.txErrors, + builder: (_, isLoading, txErrors) { + if (txErrors?.isNotEmpty ?? false) { + return TxTreeSimulationErrorWidget(txErrors: txErrors!); + } + + return EnterPasswordWidgetV2( + isLoading: isLoading, + publicKey: wm.account!.publicKey, + title: LocaleKeys.sendWord.tr(), + onPasswordEntered: wm.onSubmit, + ); + }, ), ], ); diff --git a/lib/feature/browser/approvals_listener/actions/send_message/send_message_wm.dart b/lib/feature/browser/approvals_listener/actions/send_message/send_message_wm.dart index 3c8ed881f..eb32edf40 100644 --- a/lib/feature/browser/approvals_listener/actions/send_message/send_message_wm.dart +++ b/lib/feature/browser/approvals_listener/actions/send_message/send_message_wm.dart @@ -6,6 +6,7 @@ import 'package:app/core/wm/custom_wm.dart'; import 'package:app/di/di.dart'; import 'package:app/feature/browser/approvals_listener/actions/send_message/send_message_model.dart'; import 'package:app/feature/browser/approvals_listener/actions/send_message/send_message_widget.dart'; +import 'package:app/utils/utils.dart'; import 'package:elementary_helper/elementary_helper.dart'; import 'package:flutter/material.dart'; import 'package:nekoton_repository/nekoton_repository.dart'; @@ -30,6 +31,7 @@ SendMessageWidgetModel defaultSendMessageWidgetModelFactory( SendMessageModel( createPrimaryErrorHandler(context), inject(), + inject(), ), ); @@ -42,9 +44,11 @@ class SendMessageWidgetModel late final _data = createNotifier(); late final _fee = createNotifier(); late final _feeError = createNotifier(); + late final _txErrors = createNotifier>(); late final _publicKey = createNotifier(account?.publicKey); late final _custodians = createNotifier>(); late final _balance = createNotifier(); + late final _isLoading = createNotifier(false); late final StreamSubscription _subscription; ListenableState get data => _data; @@ -53,12 +57,16 @@ class SendMessageWidgetModel ListenableState get feeError => _feeError; + ListenableState> get txErrors => _txErrors; + ListenableState get publicKey => _publicKey; ListenableState> get custodians => _custodians; ListenableState get balance => _balance; + ListenableState get isLoading => _isLoading; + Currency get nativeCurrency => Currencies()[model.transport.nativeTokenTicker]!; @@ -87,7 +95,7 @@ class SendMessageWidgetModel model.getBalanceStream(widget.sender).listen(_balance.accept); _getCustodians(); - _estimateFees(); + _prepareTransfer(); } @override @@ -128,9 +136,11 @@ class SendMessageWidgetModel _custodians.accept(custodians); } - Future _estimateFees() async { + Future _prepareTransfer() async { + UnsignedMessage? message; + try { - final fee = await model.estimateFees( + message = await model.prepareTransfer( address: widget.sender, destination: widget.recipient, publicKey: account?.publicKey, @@ -139,6 +149,20 @@ class SendMessageWidgetModel bounce: widget.bounce, ); + await _estimateFees(message); + await _simulateTransactionTree(message); + } finally { + message?.dispose(); + } + } + + Future _estimateFees(UnsignedMessage message) async { + try { + final fee = await model.estimateFees( + address: widget.sender, + message: message, + ); + _fee.accept(fee); } on FfiException catch (e) { _feeError.accept(e.message); @@ -146,4 +170,19 @@ class SendMessageWidgetModel _feeError.accept(e.toString()); } } + + Future _simulateTransactionTree(UnsignedMessage message) async { + try { + final fee = await model.simulateTransactionTree( + address: widget.sender, + message: message, + ); + + _txErrors.accept(fee); + } catch (e) { + contextSafe?.let( + (context) => model.showError(context, e.toString()), + ); + } + } } diff --git a/lib/feature/wallet/token_wallet_send/bloc/token_wallet_send_bloc.dart b/lib/feature/wallet/token_wallet_send/bloc/token_wallet_send_bloc.dart index 64331de88..5896f4e96 100644 --- a/lib/feature/wallet/token_wallet_send/bloc/token_wallet_send_bloc.dart +++ b/lib/feature/wallet/token_wallet_send/bloc/token_wallet_send_bloc.dart @@ -76,6 +76,8 @@ class TokenWalletSendBloc late UnsignedMessage unsignedMessage; UnsignedMessage? _unsignedMessage; + List? txErrors; + TransportStrategy get transport => nekotonRepository.currentTransport; Currency get currency => Currencies()[transport.nativeTokenTicker]!; @@ -143,6 +145,10 @@ class TokenWalletSendBloc address: owner, message: unsignedMessage, ); + txErrors = await nekotonRepository.simulateTransactionTree( + address: owner, + message: unsignedMessage, + ); final walletState = await nekotonRepository.walletsStream .expand((e) => e) @@ -170,7 +176,7 @@ class TokenWalletSendBloc return; } - emit(TokenWalletSendState.readyToSend(fees!, sendAmount)); + emit(TokenWalletSendState.readyToSend(fees!, sendAmount, txErrors)); } on FfiException catch (e, t) { _logger.severe('_handleSend', e, t); emit(TokenWalletSendState.calculatingError(e.message)); @@ -226,12 +232,12 @@ class TokenWalletSendBloc message: e.message, ), ); - emit(TokenWalletSendState.readyToSend(fees!, sendAmount)); + emit(TokenWalletSendState.readyToSend(fees!, sendAmount, txErrors)); } on Exception catch (e, t) { _logger.severe('_handleSend', e, t); messengerService .show(Message.error(context: context, message: e.toString())); - emit(TokenWalletSendState.readyToSend(fees!, sendAmount)); + emit(TokenWalletSendState.readyToSend(fees!, sendAmount, txErrors)); } } diff --git a/lib/feature/wallet/token_wallet_send/bloc/token_wallet_send_bloc.freezed.dart b/lib/feature/wallet/token_wallet_send/bloc/token_wallet_send_bloc.freezed.dart index e0f26b74c..caa5dec5c 100644 --- a/lib/feature/wallet/token_wallet_send/bloc/token_wallet_send_bloc.freezed.dart +++ b/lib/feature/wallet/token_wallet_send/bloc/token_wallet_send_bloc.freezed.dart @@ -650,7 +650,9 @@ mixin _$TokenWalletSendState { required TResult Function(Object error) subscribeError, required TResult Function() loading, required TResult Function(String error, BigInt? fee) calculatingError, - required TResult Function(BigInt fee, BigInt attachedAmount) readyToSend, + required TResult Function(BigInt fee, BigInt attachedAmount, + List? txErrors) + readyToSend, required TResult Function(bool canClose) sending, required TResult Function(BigInt fee, Transaction transaction) sent, }) => @@ -661,7 +663,9 @@ mixin _$TokenWalletSendState { TResult? Function(Object error)? subscribeError, TResult? Function()? loading, TResult? Function(String error, BigInt? fee)? calculatingError, - TResult? Function(BigInt fee, BigInt attachedAmount)? readyToSend, + TResult? Function(BigInt fee, BigInt attachedAmount, + List? txErrors)? + readyToSend, TResult? Function(bool canClose)? sending, TResult? Function(BigInt fee, Transaction transaction)? sent, }) => @@ -672,7 +676,9 @@ mixin _$TokenWalletSendState { TResult Function(Object error)? subscribeError, TResult Function()? loading, TResult Function(String error, BigInt? fee)? calculatingError, - TResult Function(BigInt fee, BigInt attachedAmount)? readyToSend, + TResult Function(BigInt fee, BigInt attachedAmount, + List? txErrors)? + readyToSend, TResult Function(bool canClose)? sending, TResult Function(BigInt fee, Transaction transaction)? sent, required TResult orElse(), @@ -780,7 +786,9 @@ class _$InitImpl implements _Init { required TResult Function(Object error) subscribeError, required TResult Function() loading, required TResult Function(String error, BigInt? fee) calculatingError, - required TResult Function(BigInt fee, BigInt attachedAmount) readyToSend, + required TResult Function(BigInt fee, BigInt attachedAmount, + List? txErrors) + readyToSend, required TResult Function(bool canClose) sending, required TResult Function(BigInt fee, Transaction transaction) sent, }) { @@ -794,7 +802,9 @@ class _$InitImpl implements _Init { TResult? Function(Object error)? subscribeError, TResult? Function()? loading, TResult? Function(String error, BigInt? fee)? calculatingError, - TResult? Function(BigInt fee, BigInt attachedAmount)? readyToSend, + TResult? Function(BigInt fee, BigInt attachedAmount, + List? txErrors)? + readyToSend, TResult? Function(bool canClose)? sending, TResult? Function(BigInt fee, Transaction transaction)? sent, }) { @@ -808,7 +818,9 @@ class _$InitImpl implements _Init { TResult Function(Object error)? subscribeError, TResult Function()? loading, TResult Function(String error, BigInt? fee)? calculatingError, - TResult Function(BigInt fee, BigInt attachedAmount)? readyToSend, + TResult Function(BigInt fee, BigInt attachedAmount, + List? txErrors)? + readyToSend, TResult Function(bool canClose)? sending, TResult Function(BigInt fee, Transaction transaction)? sent, required TResult orElse(), @@ -941,7 +953,9 @@ class _$SubscribeErrorImpl implements _SubscribeError { required TResult Function(Object error) subscribeError, required TResult Function() loading, required TResult Function(String error, BigInt? fee) calculatingError, - required TResult Function(BigInt fee, BigInt attachedAmount) readyToSend, + required TResult Function(BigInt fee, BigInt attachedAmount, + List? txErrors) + readyToSend, required TResult Function(bool canClose) sending, required TResult Function(BigInt fee, Transaction transaction) sent, }) { @@ -955,7 +969,9 @@ class _$SubscribeErrorImpl implements _SubscribeError { TResult? Function(Object error)? subscribeError, TResult? Function()? loading, TResult? Function(String error, BigInt? fee)? calculatingError, - TResult? Function(BigInt fee, BigInt attachedAmount)? readyToSend, + TResult? Function(BigInt fee, BigInt attachedAmount, + List? txErrors)? + readyToSend, TResult? Function(bool canClose)? sending, TResult? Function(BigInt fee, Transaction transaction)? sent, }) { @@ -969,7 +985,9 @@ class _$SubscribeErrorImpl implements _SubscribeError { TResult Function(Object error)? subscribeError, TResult Function()? loading, TResult Function(String error, BigInt? fee)? calculatingError, - TResult Function(BigInt fee, BigInt attachedAmount)? readyToSend, + TResult Function(BigInt fee, BigInt attachedAmount, + List? txErrors)? + readyToSend, TResult Function(bool canClose)? sending, TResult Function(BigInt fee, Transaction transaction)? sent, required TResult orElse(), @@ -1084,7 +1102,9 @@ class _$LoadingImpl implements _Loading { required TResult Function(Object error) subscribeError, required TResult Function() loading, required TResult Function(String error, BigInt? fee) calculatingError, - required TResult Function(BigInt fee, BigInt attachedAmount) readyToSend, + required TResult Function(BigInt fee, BigInt attachedAmount, + List? txErrors) + readyToSend, required TResult Function(bool canClose) sending, required TResult Function(BigInt fee, Transaction transaction) sent, }) { @@ -1098,7 +1118,9 @@ class _$LoadingImpl implements _Loading { TResult? Function(Object error)? subscribeError, TResult? Function()? loading, TResult? Function(String error, BigInt? fee)? calculatingError, - TResult? Function(BigInt fee, BigInt attachedAmount)? readyToSend, + TResult? Function(BigInt fee, BigInt attachedAmount, + List? txErrors)? + readyToSend, TResult? Function(bool canClose)? sending, TResult? Function(BigInt fee, Transaction transaction)? sent, }) { @@ -1112,7 +1134,9 @@ class _$LoadingImpl implements _Loading { TResult Function(Object error)? subscribeError, TResult Function()? loading, TResult Function(String error, BigInt? fee)? calculatingError, - TResult Function(BigInt fee, BigInt attachedAmount)? readyToSend, + TResult Function(BigInt fee, BigInt attachedAmount, + List? txErrors)? + readyToSend, TResult Function(bool canClose)? sending, TResult Function(BigInt fee, Transaction transaction)? sent, required TResult orElse(), @@ -1255,7 +1279,9 @@ class _$CalculatingErrorImpl implements _CalculatingError { required TResult Function(Object error) subscribeError, required TResult Function() loading, required TResult Function(String error, BigInt? fee) calculatingError, - required TResult Function(BigInt fee, BigInt attachedAmount) readyToSend, + required TResult Function(BigInt fee, BigInt attachedAmount, + List? txErrors) + readyToSend, required TResult Function(bool canClose) sending, required TResult Function(BigInt fee, Transaction transaction) sent, }) { @@ -1269,7 +1295,9 @@ class _$CalculatingErrorImpl implements _CalculatingError { TResult? Function(Object error)? subscribeError, TResult? Function()? loading, TResult? Function(String error, BigInt? fee)? calculatingError, - TResult? Function(BigInt fee, BigInt attachedAmount)? readyToSend, + TResult? Function(BigInt fee, BigInt attachedAmount, + List? txErrors)? + readyToSend, TResult? Function(bool canClose)? sending, TResult? Function(BigInt fee, Transaction transaction)? sent, }) { @@ -1283,7 +1311,9 @@ class _$CalculatingErrorImpl implements _CalculatingError { TResult Function(Object error)? subscribeError, TResult Function()? loading, TResult Function(String error, BigInt? fee)? calculatingError, - TResult Function(BigInt fee, BigInt attachedAmount)? readyToSend, + TResult Function(BigInt fee, BigInt attachedAmount, + List? txErrors)? + readyToSend, TResult Function(bool canClose)? sending, TResult Function(BigInt fee, Transaction transaction)? sent, required TResult orElse(), @@ -1361,7 +1391,10 @@ abstract class _$$ReadyImplCopyWith<$Res> { _$ReadyImpl value, $Res Function(_$ReadyImpl) then) = __$$ReadyImplCopyWithImpl<$Res>; @useResult - $Res call({BigInt fee, BigInt attachedAmount}); + $Res call( + {BigInt fee, + BigInt attachedAmount, + List? txErrors}); } /// @nodoc @@ -1379,6 +1412,7 @@ class __$$ReadyImplCopyWithImpl<$Res> $Res call({ Object? fee = null, Object? attachedAmount = null, + Object? txErrors = freezed, }) { return _then(_$ReadyImpl( null == fee @@ -1389,6 +1423,10 @@ class __$$ReadyImplCopyWithImpl<$Res> ? _value.attachedAmount : attachedAmount // ignore: cast_nullable_to_non_nullable as BigInt, + freezed == txErrors + ? _value._txErrors + : txErrors // ignore: cast_nullable_to_non_nullable + as List?, )); } } @@ -1396,16 +1434,27 @@ class __$$ReadyImplCopyWithImpl<$Res> /// @nodoc class _$ReadyImpl implements _Ready { - const _$ReadyImpl(this.fee, this.attachedAmount); + const _$ReadyImpl(this.fee, this.attachedAmount, + final List? txErrors) + : _txErrors = txErrors; @override final BigInt fee; @override final BigInt attachedAmount; + final List? _txErrors; + @override + List? get txErrors { + final value = _txErrors; + if (value == null) return null; + if (_txErrors is EqualUnmodifiableListView) return _txErrors; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } @override String toString() { - return 'TokenWalletSendState.readyToSend(fee: $fee, attachedAmount: $attachedAmount)'; + return 'TokenWalletSendState.readyToSend(fee: $fee, attachedAmount: $attachedAmount, txErrors: $txErrors)'; } @override @@ -1415,11 +1464,13 @@ class _$ReadyImpl implements _Ready { other is _$ReadyImpl && (identical(other.fee, fee) || other.fee == fee) && (identical(other.attachedAmount, attachedAmount) || - other.attachedAmount == attachedAmount)); + other.attachedAmount == attachedAmount) && + const DeepCollectionEquality().equals(other._txErrors, _txErrors)); } @override - int get hashCode => Object.hash(runtimeType, fee, attachedAmount); + int get hashCode => Object.hash(runtimeType, fee, attachedAmount, + const DeepCollectionEquality().hash(_txErrors)); /// Create a copy of TokenWalletSendState /// with the given fields replaced by the non-null parameter values. @@ -1436,11 +1487,13 @@ class _$ReadyImpl implements _Ready { required TResult Function(Object error) subscribeError, required TResult Function() loading, required TResult Function(String error, BigInt? fee) calculatingError, - required TResult Function(BigInt fee, BigInt attachedAmount) readyToSend, + required TResult Function(BigInt fee, BigInt attachedAmount, + List? txErrors) + readyToSend, required TResult Function(bool canClose) sending, required TResult Function(BigInt fee, Transaction transaction) sent, }) { - return readyToSend(fee, attachedAmount); + return readyToSend(fee, attachedAmount, txErrors); } @override @@ -1450,11 +1503,13 @@ class _$ReadyImpl implements _Ready { TResult? Function(Object error)? subscribeError, TResult? Function()? loading, TResult? Function(String error, BigInt? fee)? calculatingError, - TResult? Function(BigInt fee, BigInt attachedAmount)? readyToSend, + TResult? Function(BigInt fee, BigInt attachedAmount, + List? txErrors)? + readyToSend, TResult? Function(bool canClose)? sending, TResult? Function(BigInt fee, Transaction transaction)? sent, }) { - return readyToSend?.call(fee, attachedAmount); + return readyToSend?.call(fee, attachedAmount, txErrors); } @override @@ -1464,13 +1519,15 @@ class _$ReadyImpl implements _Ready { TResult Function(Object error)? subscribeError, TResult Function()? loading, TResult Function(String error, BigInt? fee)? calculatingError, - TResult Function(BigInt fee, BigInt attachedAmount)? readyToSend, + TResult Function(BigInt fee, BigInt attachedAmount, + List? txErrors)? + readyToSend, TResult Function(bool canClose)? sending, TResult Function(BigInt fee, Transaction transaction)? sent, required TResult orElse(), }) { if (readyToSend != null) { - return readyToSend(fee, attachedAmount); + return readyToSend(fee, attachedAmount, txErrors); } return orElse(); } @@ -1523,11 +1580,12 @@ class _$ReadyImpl implements _Ready { } abstract class _Ready implements TokenWalletSendState { - const factory _Ready(final BigInt fee, final BigInt attachedAmount) = - _$ReadyImpl; + const factory _Ready(final BigInt fee, final BigInt attachedAmount, + final List? txErrors) = _$ReadyImpl; BigInt get fee; BigInt get attachedAmount; + List? get txErrors; /// Create a copy of TokenWalletSendState /// with the given fields replaced by the non-null parameter values. @@ -1609,7 +1667,9 @@ class _$SendingImpl implements _Sending { required TResult Function(Object error) subscribeError, required TResult Function() loading, required TResult Function(String error, BigInt? fee) calculatingError, - required TResult Function(BigInt fee, BigInt attachedAmount) readyToSend, + required TResult Function(BigInt fee, BigInt attachedAmount, + List? txErrors) + readyToSend, required TResult Function(bool canClose) sending, required TResult Function(BigInt fee, Transaction transaction) sent, }) { @@ -1623,7 +1683,9 @@ class _$SendingImpl implements _Sending { TResult? Function(Object error)? subscribeError, TResult? Function()? loading, TResult? Function(String error, BigInt? fee)? calculatingError, - TResult? Function(BigInt fee, BigInt attachedAmount)? readyToSend, + TResult? Function(BigInt fee, BigInt attachedAmount, + List? txErrors)? + readyToSend, TResult? Function(bool canClose)? sending, TResult? Function(BigInt fee, Transaction transaction)? sent, }) { @@ -1637,7 +1699,9 @@ class _$SendingImpl implements _Sending { TResult Function(Object error)? subscribeError, TResult Function()? loading, TResult Function(String error, BigInt? fee)? calculatingError, - TResult Function(BigInt fee, BigInt attachedAmount)? readyToSend, + TResult Function(BigInt fee, BigInt attachedAmount, + List? txErrors)? + readyToSend, TResult Function(bool canClose)? sending, TResult Function(BigInt fee, Transaction transaction)? sent, required TResult orElse(), @@ -1799,7 +1863,9 @@ class _$SentImpl implements _Sent { required TResult Function(Object error) subscribeError, required TResult Function() loading, required TResult Function(String error, BigInt? fee) calculatingError, - required TResult Function(BigInt fee, BigInt attachedAmount) readyToSend, + required TResult Function(BigInt fee, BigInt attachedAmount, + List? txErrors) + readyToSend, required TResult Function(bool canClose) sending, required TResult Function(BigInt fee, Transaction transaction) sent, }) { @@ -1813,7 +1879,9 @@ class _$SentImpl implements _Sent { TResult? Function(Object error)? subscribeError, TResult? Function()? loading, TResult? Function(String error, BigInt? fee)? calculatingError, - TResult? Function(BigInt fee, BigInt attachedAmount)? readyToSend, + TResult? Function(BigInt fee, BigInt attachedAmount, + List? txErrors)? + readyToSend, TResult? Function(bool canClose)? sending, TResult? Function(BigInt fee, Transaction transaction)? sent, }) { @@ -1827,7 +1895,9 @@ class _$SentImpl implements _Sent { TResult Function(Object error)? subscribeError, TResult Function()? loading, TResult Function(String error, BigInt? fee)? calculatingError, - TResult Function(BigInt fee, BigInt attachedAmount)? readyToSend, + TResult Function(BigInt fee, BigInt attachedAmount, + List? txErrors)? + readyToSend, TResult Function(bool canClose)? sending, TResult Function(BigInt fee, Transaction transaction)? sent, required TResult orElse(), diff --git a/lib/feature/wallet/token_wallet_send/bloc/token_wallet_send_state.dart b/lib/feature/wallet/token_wallet_send/bloc/token_wallet_send_state.dart index 785d3903a..4bae20b5d 100644 --- a/lib/feature/wallet/token_wallet_send/bloc/token_wallet_send_state.dart +++ b/lib/feature/wallet/token_wallet_send/bloc/token_wallet_send_state.dart @@ -20,6 +20,7 @@ class TokenWalletSendState with _$TokenWalletSendState { const factory TokenWalletSendState.readyToSend( BigInt fee, BigInt attachedAmount, + List? txErrors, ) = _Ready; /// Transaction is sending. diff --git a/lib/feature/wallet/token_wallet_send/view/token_wallet_send_confirm_view.dart b/lib/feature/wallet/token_wallet_send/view/token_wallet_send_confirm_view.dart index 61250b43c..5c832895c 100644 --- a/lib/feature/wallet/token_wallet_send/view/token_wallet_send_confirm_view.dart +++ b/lib/feature/wallet/token_wallet_send/view/token_wallet_send_confirm_view.dart @@ -1,6 +1,7 @@ import 'package:app/feature/profile/profile.dart'; import 'package:app/feature/wallet/wallet.dart'; import 'package:app/generated/generated.dart'; +import 'package:app/widgets/widgets.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:nekoton_repository/nekoton_repository.dart'; @@ -16,6 +17,7 @@ class TokenWalletSendConfirmView extends StatelessWidget { this.fee, this.feeError, this.attachedAmount, + this.txErrors, super.key, }); @@ -26,6 +28,7 @@ class TokenWalletSendConfirmView extends StatelessWidget { final String? comment; final String? feeError; final PublicKey publicKey; + final List? txErrors; @override Widget build(BuildContext context) { @@ -35,6 +38,7 @@ class TokenWalletSendConfirmView extends StatelessWidget { amount, bloc.tokenCurrency, ); + final hasTxError = txErrors?.isNotEmpty ?? false; return SeparatedColumn( crossAxisAlignment: CrossAxisAlignment.start, @@ -59,12 +63,15 @@ class TokenWalletSendConfirmView extends StatelessWidget { ), ), ), - EnterPasswordWidgetV2( - publicKey: publicKey, - title: LocaleKeys.confirm.tr(), - isLoading: isLoading, - onPasswordEntered: (pwd) => bloc.add(TokenWalletSendEvent.send(pwd)), - ), + if (hasTxError) TxTreeSimulationErrorWidget(txErrors: txErrors!), + if (!hasTxError) + EnterPasswordWidgetV2( + publicKey: publicKey, + title: LocaleKeys.confirm.tr(), + isLoading: isLoading, + onPasswordEntered: (pwd) => + bloc.add(TokenWalletSendEvent.send(pwd)), + ), const SizedBox(height: DimensSize.d16), ], ); diff --git a/lib/feature/wallet/token_wallet_send/view/token_wallet_send_page.dart b/lib/feature/wallet/token_wallet_send/view/token_wallet_send_page.dart index c2b741e35..852929c26 100644 --- a/lib/feature/wallet/token_wallet_send/view/token_wallet_send_page.dart +++ b/lib/feature/wallet/token_wallet_send/view/token_wallet_send_page.dart @@ -89,9 +89,10 @@ class TokenWalletSendPage extends StatelessWidget { fee: fee, error: error, ), - readyToSend: (fee, attachedAmount) => _confirmPage( + readyToSend: (fee, attachedAmount, txErrors) => _confirmPage( fee: fee, attachedAmount: attachedAmount, + txErrors: txErrors, ), sending: _sendingPage, sent: (_, __) => _sendingPage(false), @@ -105,6 +106,7 @@ class TokenWalletSendPage extends StatelessWidget { BigInt? fee, String? error, BigInt? attachedAmount, + List? txErrors, }) => Scaffold( appBar: DefaultAppBar( @@ -123,6 +125,7 @@ class TokenWalletSendPage extends StatelessWidget { fee: fee, feeError: error, attachedAmount: attachedAmount, + txErrors: txErrors, ), ), ); diff --git a/lib/feature/wallet/ton_wallet_send/bloc/ton_wallet_send_bloc.dart b/lib/feature/wallet/ton_wallet_send/bloc/ton_wallet_send_bloc.dart index e07c03763..83bac2e2e 100644 --- a/lib/feature/wallet/ton_wallet_send/bloc/ton_wallet_send_bloc.dart +++ b/lib/feature/wallet/ton_wallet_send/bloc/ton_wallet_send_bloc.dart @@ -10,7 +10,9 @@ import 'package:logging/logging.dart'; import 'package:nekoton_repository/nekoton_repository.dart' hide Message; part 'ton_wallet_send_bloc.freezed.dart'; + part 'ton_wallet_send_event.dart'; + part 'ton_wallet_send_state.dart'; /// Bloc that allows prepare transaction to send native funds from [TonWallet] @@ -69,6 +71,8 @@ class TonWalletSendBloc extends Bloc { late UnsignedMessage unsignedMessage; UnsignedMessage? _unsignedMessage; + List? txErrors; + TransportStrategy get transport => nekotonRepository.currentTransport; Currency get currency => Currencies()[transport.nativeTokenTicker]!; @@ -102,11 +106,14 @@ class TonWalletSendBloc extends Bloc { expiration: defaultSendTimeout, ); _unsignedMessage = unsignedMessage; - fees = await nekotonRepository.estimateFees( address: address, message: unsignedMessage, ); + txErrors = await nekotonRepository.simulateTransactionTree( + address: address, + message: unsignedMessage, + ); final walletState = await nekotonRepository.walletsStream .expand((e) => e) @@ -133,7 +140,7 @@ class TonWalletSendBloc extends Bloc { return; } - emit(TonWalletSendState.readyToSend(fees!)); + emit(TonWalletSendState.readyToSend(fees!, txErrors)); } on FfiException catch (e, t) { _logger.severe('_handleSend', e, t); emit(TonWalletSendState.calculatingError(e.message)); @@ -181,12 +188,12 @@ class TonWalletSendBloc extends Bloc { _logger.severe('_handleSend', e, t); messengerService .show(Message.error(context: context, message: e.message)); - emit(TonWalletSendState.readyToSend(fees!)); + emit(TonWalletSendState.readyToSend(fees!, txErrors)); } on Exception catch (e, t) { _logger.severe('_handleSend', e, t); messengerService .show(Message.error(context: context, message: e.toString())); - emit(TonWalletSendState.readyToSend(fees!)); + emit(TonWalletSendState.readyToSend(fees!, txErrors)); } } diff --git a/lib/feature/wallet/ton_wallet_send/bloc/ton_wallet_send_bloc.freezed.dart b/lib/feature/wallet/ton_wallet_send/bloc/ton_wallet_send_bloc.freezed.dart index 9a73bfd29..6b1848a28 100644 --- a/lib/feature/wallet/ton_wallet_send/bloc/ton_wallet_send_bloc.freezed.dart +++ b/lib/feature/wallet/ton_wallet_send/bloc/ton_wallet_send_bloc.freezed.dart @@ -648,7 +648,9 @@ mixin _$TonWalletSendState { required TResult Function() loading, required TResult Function(String error, BigInt? fee) calculatingError, required TResult Function(Object error) subscribeError, - required TResult Function(BigInt fee) readyToSend, + required TResult Function( + BigInt fee, List? txErrors) + readyToSend, required TResult Function(bool canClose) sending, required TResult Function(BigInt fee, Transaction transaction) sent, }) => @@ -658,7 +660,8 @@ mixin _$TonWalletSendState { TResult? Function()? loading, TResult? Function(String error, BigInt? fee)? calculatingError, TResult? Function(Object error)? subscribeError, - TResult? Function(BigInt fee)? readyToSend, + TResult? Function(BigInt fee, List? txErrors)? + readyToSend, TResult? Function(bool canClose)? sending, TResult? Function(BigInt fee, Transaction transaction)? sent, }) => @@ -668,7 +671,8 @@ mixin _$TonWalletSendState { TResult Function()? loading, TResult Function(String error, BigInt? fee)? calculatingError, TResult Function(Object error)? subscribeError, - TResult Function(BigInt fee)? readyToSend, + TResult Function(BigInt fee, List? txErrors)? + readyToSend, TResult Function(bool canClose)? sending, TResult Function(BigInt fee, Transaction transaction)? sent, required TResult orElse(), @@ -772,7 +776,9 @@ class _$LoadingImpl implements _Loading { required TResult Function() loading, required TResult Function(String error, BigInt? fee) calculatingError, required TResult Function(Object error) subscribeError, - required TResult Function(BigInt fee) readyToSend, + required TResult Function( + BigInt fee, List? txErrors) + readyToSend, required TResult Function(bool canClose) sending, required TResult Function(BigInt fee, Transaction transaction) sent, }) { @@ -785,7 +791,8 @@ class _$LoadingImpl implements _Loading { TResult? Function()? loading, TResult? Function(String error, BigInt? fee)? calculatingError, TResult? Function(Object error)? subscribeError, - TResult? Function(BigInt fee)? readyToSend, + TResult? Function(BigInt fee, List? txErrors)? + readyToSend, TResult? Function(bool canClose)? sending, TResult? Function(BigInt fee, Transaction transaction)? sent, }) { @@ -798,7 +805,8 @@ class _$LoadingImpl implements _Loading { TResult Function()? loading, TResult Function(String error, BigInt? fee)? calculatingError, TResult Function(Object error)? subscribeError, - TResult Function(BigInt fee)? readyToSend, + TResult Function(BigInt fee, List? txErrors)? + readyToSend, TResult Function(bool canClose)? sending, TResult Function(BigInt fee, Transaction transaction)? sent, required TResult orElse(), @@ -937,7 +945,9 @@ class _$CalculatingErrorImpl implements _CalculatingError { required TResult Function() loading, required TResult Function(String error, BigInt? fee) calculatingError, required TResult Function(Object error) subscribeError, - required TResult Function(BigInt fee) readyToSend, + required TResult Function( + BigInt fee, List? txErrors) + readyToSend, required TResult Function(bool canClose) sending, required TResult Function(BigInt fee, Transaction transaction) sent, }) { @@ -950,7 +960,8 @@ class _$CalculatingErrorImpl implements _CalculatingError { TResult? Function()? loading, TResult? Function(String error, BigInt? fee)? calculatingError, TResult? Function(Object error)? subscribeError, - TResult? Function(BigInt fee)? readyToSend, + TResult? Function(BigInt fee, List? txErrors)? + readyToSend, TResult? Function(bool canClose)? sending, TResult? Function(BigInt fee, Transaction transaction)? sent, }) { @@ -963,7 +974,8 @@ class _$CalculatingErrorImpl implements _CalculatingError { TResult Function()? loading, TResult Function(String error, BigInt? fee)? calculatingError, TResult Function(Object error)? subscribeError, - TResult Function(BigInt fee)? readyToSend, + TResult Function(BigInt fee, List? txErrors)? + readyToSend, TResult Function(bool canClose)? sending, TResult Function(BigInt fee, Transaction transaction)? sent, required TResult orElse(), @@ -1102,7 +1114,9 @@ class _$SubscribeErrorImpl implements _SubscribeError { required TResult Function() loading, required TResult Function(String error, BigInt? fee) calculatingError, required TResult Function(Object error) subscribeError, - required TResult Function(BigInt fee) readyToSend, + required TResult Function( + BigInt fee, List? txErrors) + readyToSend, required TResult Function(bool canClose) sending, required TResult Function(BigInt fee, Transaction transaction) sent, }) { @@ -1115,7 +1129,8 @@ class _$SubscribeErrorImpl implements _SubscribeError { TResult? Function()? loading, TResult? Function(String error, BigInt? fee)? calculatingError, TResult? Function(Object error)? subscribeError, - TResult? Function(BigInt fee)? readyToSend, + TResult? Function(BigInt fee, List? txErrors)? + readyToSend, TResult? Function(bool canClose)? sending, TResult? Function(BigInt fee, Transaction transaction)? sent, }) { @@ -1128,7 +1143,8 @@ class _$SubscribeErrorImpl implements _SubscribeError { TResult Function()? loading, TResult Function(String error, BigInt? fee)? calculatingError, TResult Function(Object error)? subscribeError, - TResult Function(BigInt fee)? readyToSend, + TResult Function(BigInt fee, List? txErrors)? + readyToSend, TResult Function(bool canClose)? sending, TResult Function(BigInt fee, Transaction transaction)? sent, required TResult orElse(), @@ -1201,7 +1217,7 @@ abstract class _$$ReadyImplCopyWith<$Res> { _$ReadyImpl value, $Res Function(_$ReadyImpl) then) = __$$ReadyImplCopyWithImpl<$Res>; @useResult - $Res call({BigInt fee}); + $Res call({BigInt fee, List? txErrors}); } /// @nodoc @@ -1218,12 +1234,17 @@ class __$$ReadyImplCopyWithImpl<$Res> @override $Res call({ Object? fee = null, + Object? txErrors = freezed, }) { return _then(_$ReadyImpl( null == fee ? _value.fee : fee // ignore: cast_nullable_to_non_nullable as BigInt, + freezed == txErrors + ? _value._txErrors + : txErrors // ignore: cast_nullable_to_non_nullable + as List?, )); } } @@ -1231,14 +1252,24 @@ class __$$ReadyImplCopyWithImpl<$Res> /// @nodoc class _$ReadyImpl implements _Ready { - const _$ReadyImpl(this.fee); + const _$ReadyImpl(this.fee, final List? txErrors) + : _txErrors = txErrors; @override final BigInt fee; + final List? _txErrors; + @override + List? get txErrors { + final value = _txErrors; + if (value == null) return null; + if (_txErrors is EqualUnmodifiableListView) return _txErrors; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } @override String toString() { - return 'TonWalletSendState.readyToSend(fee: $fee)'; + return 'TonWalletSendState.readyToSend(fee: $fee, txErrors: $txErrors)'; } @override @@ -1246,11 +1277,13 @@ class _$ReadyImpl implements _Ready { return identical(this, other) || (other.runtimeType == runtimeType && other is _$ReadyImpl && - (identical(other.fee, fee) || other.fee == fee)); + (identical(other.fee, fee) || other.fee == fee) && + const DeepCollectionEquality().equals(other._txErrors, _txErrors)); } @override - int get hashCode => Object.hash(runtimeType, fee); + int get hashCode => Object.hash( + runtimeType, fee, const DeepCollectionEquality().hash(_txErrors)); /// Create a copy of TonWalletSendState /// with the given fields replaced by the non-null parameter values. @@ -1266,11 +1299,13 @@ class _$ReadyImpl implements _Ready { required TResult Function() loading, required TResult Function(String error, BigInt? fee) calculatingError, required TResult Function(Object error) subscribeError, - required TResult Function(BigInt fee) readyToSend, + required TResult Function( + BigInt fee, List? txErrors) + readyToSend, required TResult Function(bool canClose) sending, required TResult Function(BigInt fee, Transaction transaction) sent, }) { - return readyToSend(fee); + return readyToSend(fee, txErrors); } @override @@ -1279,11 +1314,12 @@ class _$ReadyImpl implements _Ready { TResult? Function()? loading, TResult? Function(String error, BigInt? fee)? calculatingError, TResult? Function(Object error)? subscribeError, - TResult? Function(BigInt fee)? readyToSend, + TResult? Function(BigInt fee, List? txErrors)? + readyToSend, TResult? Function(bool canClose)? sending, TResult? Function(BigInt fee, Transaction transaction)? sent, }) { - return readyToSend?.call(fee); + return readyToSend?.call(fee, txErrors); } @override @@ -1292,13 +1328,14 @@ class _$ReadyImpl implements _Ready { TResult Function()? loading, TResult Function(String error, BigInt? fee)? calculatingError, TResult Function(Object error)? subscribeError, - TResult Function(BigInt fee)? readyToSend, + TResult Function(BigInt fee, List? txErrors)? + readyToSend, TResult Function(bool canClose)? sending, TResult Function(BigInt fee, Transaction transaction)? sent, required TResult orElse(), }) { if (readyToSend != null) { - return readyToSend(fee); + return readyToSend(fee, txErrors); } return orElse(); } @@ -1348,9 +1385,12 @@ class _$ReadyImpl implements _Ready { } abstract class _Ready implements TonWalletSendState { - const factory _Ready(final BigInt fee) = _$ReadyImpl; + const factory _Ready( + final BigInt fee, final List? txErrors) = + _$ReadyImpl; BigInt get fee; + List? get txErrors; /// Create a copy of TonWalletSendState /// with the given fields replaced by the non-null parameter values. @@ -1431,7 +1471,9 @@ class _$SendingImpl implements _Sending { required TResult Function() loading, required TResult Function(String error, BigInt? fee) calculatingError, required TResult Function(Object error) subscribeError, - required TResult Function(BigInt fee) readyToSend, + required TResult Function( + BigInt fee, List? txErrors) + readyToSend, required TResult Function(bool canClose) sending, required TResult Function(BigInt fee, Transaction transaction) sent, }) { @@ -1444,7 +1486,8 @@ class _$SendingImpl implements _Sending { TResult? Function()? loading, TResult? Function(String error, BigInt? fee)? calculatingError, TResult? Function(Object error)? subscribeError, - TResult? Function(BigInt fee)? readyToSend, + TResult? Function(BigInt fee, List? txErrors)? + readyToSend, TResult? Function(bool canClose)? sending, TResult? Function(BigInt fee, Transaction transaction)? sent, }) { @@ -1457,7 +1500,8 @@ class _$SendingImpl implements _Sending { TResult Function()? loading, TResult Function(String error, BigInt? fee)? calculatingError, TResult Function(Object error)? subscribeError, - TResult Function(BigInt fee)? readyToSend, + TResult Function(BigInt fee, List? txErrors)? + readyToSend, TResult Function(bool canClose)? sending, TResult Function(BigInt fee, Transaction transaction)? sent, required TResult orElse(), @@ -1615,7 +1659,9 @@ class _$SentImpl implements _Sent { required TResult Function() loading, required TResult Function(String error, BigInt? fee) calculatingError, required TResult Function(Object error) subscribeError, - required TResult Function(BigInt fee) readyToSend, + required TResult Function( + BigInt fee, List? txErrors) + readyToSend, required TResult Function(bool canClose) sending, required TResult Function(BigInt fee, Transaction transaction) sent, }) { @@ -1628,7 +1674,8 @@ class _$SentImpl implements _Sent { TResult? Function()? loading, TResult? Function(String error, BigInt? fee)? calculatingError, TResult? Function(Object error)? subscribeError, - TResult? Function(BigInt fee)? readyToSend, + TResult? Function(BigInt fee, List? txErrors)? + readyToSend, TResult? Function(bool canClose)? sending, TResult? Function(BigInt fee, Transaction transaction)? sent, }) { @@ -1641,7 +1688,8 @@ class _$SentImpl implements _Sent { TResult Function()? loading, TResult Function(String error, BigInt? fee)? calculatingError, TResult Function(Object error)? subscribeError, - TResult Function(BigInt fee)? readyToSend, + TResult Function(BigInt fee, List? txErrors)? + readyToSend, TResult Function(bool canClose)? sending, TResult Function(BigInt fee, Transaction transaction)? sent, required TResult orElse(), diff --git a/lib/feature/wallet/ton_wallet_send/bloc/ton_wallet_send_state.dart b/lib/feature/wallet/ton_wallet_send/bloc/ton_wallet_send_state.dart index 15f101639..a3ab1b32b 100644 --- a/lib/feature/wallet/ton_wallet_send/bloc/ton_wallet_send_state.dart +++ b/lib/feature/wallet/ton_wallet_send/bloc/ton_wallet_send_state.dart @@ -14,7 +14,10 @@ class TonWalletSendState with _$TonWalletSendState { _SubscribeError; /// Blockchain fee loaded, allow user send transaction - const factory TonWalletSendState.readyToSend(BigInt fee) = _Ready; + const factory TonWalletSendState.readyToSend( + BigInt fee, + List? txErrors, + ) = _Ready; /// Transaction is sending. /// [canClose] needs to allow user close transaction right after it was sent diff --git a/lib/feature/wallet/ton_wallet_send/view/ton_wallet_send_confirm_view.dart b/lib/feature/wallet/ton_wallet_send/view/ton_wallet_send_confirm_view.dart index efe8e8ef4..6554868aa 100644 --- a/lib/feature/wallet/ton_wallet_send/view/ton_wallet_send_confirm_view.dart +++ b/lib/feature/wallet/ton_wallet_send/view/ton_wallet_send_confirm_view.dart @@ -1,6 +1,7 @@ import 'package:app/feature/profile/profile.dart'; import 'package:app/feature/wallet/wallet.dart'; import 'package:app/generated/generated.dart'; +import 'package:app/widgets/widgets.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -17,6 +18,7 @@ class TonWalletSendConfirmView extends StatelessWidget { this.fee, this.attachedAmount, this.feeError, + this.txErrors, super.key, }); @@ -27,12 +29,14 @@ class TonWalletSendConfirmView extends StatelessWidget { final String? comment; final String? feeError; final PublicKey publicKey; + final List? txErrors; @override Widget build(BuildContext context) { final bloc = context.read(); final isLoading = fee == null && feeError == null; final amountMoney = Money.fromBigIntWithCurrency(amount, bloc.currency); + final hasTxError = txErrors?.isNotEmpty ?? false; return SeparatedColumn( crossAxisAlignment: CrossAxisAlignment.start, @@ -55,12 +59,14 @@ class TonWalletSendConfirmView extends StatelessWidget { ), ), ), - EnterPasswordWidgetV2( - publicKey: publicKey, - title: LocaleKeys.confirm.tr(), - isLoading: isLoading, - onPasswordEntered: (pwd) => bloc.add(TonWalletSendEvent.send(pwd)), - ), + if (hasTxError) TxTreeSimulationErrorWidget(txErrors: txErrors!), + if (!hasTxError) + EnterPasswordWidgetV2( + publicKey: publicKey, + title: LocaleKeys.confirm.tr(), + isLoading: isLoading, + onPasswordEntered: (pwd) => bloc.add(TonWalletSendEvent.send(pwd)), + ), const SizedBox(height: DimensSize.d16), ], ); diff --git a/lib/feature/wallet/ton_wallet_send/view/ton_wallet_send_page.dart b/lib/feature/wallet/ton_wallet_send/view/ton_wallet_send_page.dart index dc1ec27c9..024f8fecd 100644 --- a/lib/feature/wallet/ton_wallet_send/view/ton_wallet_send_page.dart +++ b/lib/feature/wallet/ton_wallet_send/view/ton_wallet_send_page.dart @@ -89,7 +89,8 @@ class TonWalletSendPage extends StatelessWidget { loading: _confirmPage, calculatingError: (error, fee) => _confirmPage(fee: fee, error: error), - readyToSend: (fee) => _confirmPage(fee: fee), + readyToSend: (fee, txErrors) => + _confirmPage(fee: fee, txErrors: txErrors), sending: _sendingPage, sent: (fee, _) => _sendingPage(true), ); @@ -98,7 +99,12 @@ class TonWalletSendPage extends StatelessWidget { ); } - Widget _confirmPage({BigInt? fee, String? error}) => Scaffold( + Widget _confirmPage({ + BigInt? fee, + String? error, + List? txErrors, + }) => + Scaffold( appBar: DefaultAppBar( onClosePressed: (context) => context.pop(), titleText: LocaleKeys.confirmTransaction.tr(), @@ -114,6 +120,7 @@ class TonWalletSendPage extends StatelessWidget { publicKey: publicKey, fee: fee, feeError: error, + txErrors: txErrors, ), ), ); diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 9a9ae8502..2cf5d03be 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -588,4 +588,25 @@ abstract class LocaleKeys { static const deleteBookmarksQuestion = 'deleteBookmarksQuestion'; static const deleteBookmarksDescription = 'deleteBookmarksDescription'; static const save = 'save'; + static const txTreeSimulationErrorComputePhase_0 = + 'txTreeSimulationErrorComputePhase.0'; + static const txTreeSimulationErrorComputePhase_1 = + 'txTreeSimulationErrorComputePhase.1'; + static const txTreeSimulationErrorComputePhase = + 'txTreeSimulationErrorComputePhase'; + static const txTreeSimulationErrorActionPhase_0 = + 'txTreeSimulationErrorActionPhase.0'; + static const txTreeSimulationErrorActionPhase_1 = + 'txTreeSimulationErrorActionPhase.1'; + static const txTreeSimulationErrorActionPhase = + 'txTreeSimulationErrorActionPhase'; + static const txTreeSimulationErrorFrozen_0 = 'txTreeSimulationErrorFrozen.0'; + static const txTreeSimulationErrorFrozen_1 = 'txTreeSimulationErrorFrozen.1'; + static const txTreeSimulationErrorFrozen = 'txTreeSimulationErrorFrozen'; + static const txTreeSimulationErrorDeleted_0 = + 'txTreeSimulationErrorDeleted.0'; + static const txTreeSimulationErrorDeleted_1 = + 'txTreeSimulationErrorDeleted.1'; + static const txTreeSimulationErrorDeleted = 'txTreeSimulationErrorDeleted'; + static const txTreeSimulationErrorHint = 'txTreeSimulationErrorHint'; } diff --git a/lib/widgets/tx_tree_simulation_error_widget.dart b/lib/widgets/tx_tree_simulation_error_widget.dart new file mode 100644 index 000000000..4761be878 --- /dev/null +++ b/lib/widgets/tx_tree_simulation_error_widget.dart @@ -0,0 +1,146 @@ +import 'package:app/app/service/service.dart'; +import 'package:app/di/di.dart'; +import 'package:app/generated/generated.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:lucide_icons_flutter/lucide_icons.dart'; +import 'package:nekoton_repository/nekoton_repository.dart' hide Message; +import 'package:ui_components_lib/ui_components_lib.dart'; +import 'package:ui_components_lib/v2/ui_components_lib_v2.dart'; + +class TxTreeSimulationErrorWidget extends StatelessWidget { + const TxTreeSimulationErrorWidget({ + required this.txErrors, + super.key, + }); + + final List txErrors; + + @override + Widget build(BuildContext context) { + final theme = context.themeStyleV2; + + return PrimaryCard( + color: theme.colors.backgroundNegative, + borderRadius: BorderRadius.circular(DimensRadiusV2.radius12), + padding: const EdgeInsets.all(DimensSizeV2.d16), + child: SeparatedRow( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon( + LucideIcons.triangleAlert, + size: DimensSizeV2.d20, + color: theme.colors.contentNegative, + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + for (final item in txErrors) _ErrorMessage(item: item), + Text( + LocaleKeys.txTreeSimulationErrorHint.tr(), + style: theme.textStyles.paragraphSmall.copyWith( + color: theme.colors.contentNegative, + ), + ), + ], + ), + ), + ], + ), + ); + } +} + +class _ErrorMessage extends StatefulWidget { + const _ErrorMessage({ + required this.item, + }); + + final TxTreeSimulationErrorItem item; + + @override + State<_ErrorMessage> createState() => _ErrorMessageState(); +} + +class _ErrorMessageState extends State<_ErrorMessage> { + @override + Widget build(BuildContext context) { + final theme = context.themeStyleV2; + final address = TextSpan( + text: widget.item.address.toEllipseString(), + style: theme.textStyles.paragraphSmall.copyWith( + color: theme.colors.contentNegative1, + ), + recognizer: TapGestureRecognizer()..onTap = _onTap, + ); + + return RichText( + text: TextSpan( + style: theme.textStyles.paragraphSmall.copyWith( + color: theme.colors.contentNegative, + ), + children: switch (widget.item.error.type) { + TxTreeSimulationErrorType.computePhase => [ + TextSpan( + text: LocaleKeys.txTreeSimulationErrorComputePhase_0.tr(), + ), + address, + TextSpan( + text: LocaleKeys.txTreeSimulationErrorComputePhase_0.tr( + args: [widget.item.error.code?.toString() ?? ''], + ), + ), + ], + TxTreeSimulationErrorType.actionPhase => [ + TextSpan( + text: LocaleKeys.txTreeSimulationErrorActionPhase_0.tr(), + ), + address, + TextSpan( + text: LocaleKeys.txTreeSimulationErrorActionPhase_1.tr( + args: [widget.item.error.code?.toString() ?? ''], + ), + ), + ], + TxTreeSimulationErrorType.frozen => [ + TextSpan( + text: LocaleKeys.txTreeSimulationErrorFrozen_0.tr(), + ), + address, + TextSpan( + text: LocaleKeys.txTreeSimulationErrorFrozen_1.tr( + args: [widget.item.error.code?.toString() ?? ''], + ), + ), + ], + TxTreeSimulationErrorType.deleted => [ + TextSpan( + text: LocaleKeys.txTreeSimulationErrorDeleted_0.tr(), + ), + address, + TextSpan( + text: LocaleKeys.txTreeSimulationErrorDeleted_1.tr( + args: [widget.item.error.code?.toString() ?? ''], + ), + ), + ], + }, + ), + ); + } + + void _onTap() { + Clipboard.setData(ClipboardData(text: widget.item.address.address)); + inject().show( + Message.successful( + context: context, + message: LocaleKeys.valueCopiedExclamation.tr( + args: [widget.item.address.toEllipseString()], + ), + ), + ); + } +} diff --git a/lib/widgets/widgets.dart b/lib/widgets/widgets.dart new file mode 100644 index 000000000..eadb7db11 --- /dev/null +++ b/lib/widgets/widgets.dart @@ -0,0 +1,5 @@ +export 'amount_input/amount_input.dart'; +export 'barcode_address.dart'; +export 'change_notifier_listener.dart'; +export 'tx_tree_simulation_error_widget.dart'; +export 'user_avatar/user_avatar.dart';