diff --git a/app/src/external/res/xml/network_security_config.xml b/app/src/external/res/xml/network_security_config.xml new file mode 100644 index 0000000000..894912ac61 --- /dev/null +++ b/app/src/external/res/xml/network_security_config.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt b/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt index 4f9ab200f1..e99dcef57b 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt @@ -37,6 +37,7 @@ fun Blockchain.getGreyedOutIconRes(): Int { Blockchain.Optimism, Blockchain.OptimismTestnet -> R.drawable.ic_optimism_no_color Blockchain.Dash -> R.drawable.ic_dash_no_color Blockchain.Kaspa -> R.drawable.ic_kaspa_no_color + Blockchain.TON, Blockchain.TONTestnet -> R.drawable.ic_ton_no_color else -> R.drawable.ic_tangem_logo } } diff --git a/app/src/main/java/com/tangem/tap/domain/totalBalance/implementation/DefaultTotalFiatBalanceCalculator.kt b/app/src/main/java/com/tangem/tap/domain/totalBalance/implementation/DefaultTotalFiatBalanceCalculator.kt index 9136094e8b..fd5bf29aa9 100644 --- a/app/src/main/java/com/tangem/tap/domain/totalBalance/implementation/DefaultTotalFiatBalanceCalculator.kt +++ b/app/src/main/java/com/tangem/tap/domain/totalBalance/implementation/DefaultTotalFiatBalanceCalculator.kt @@ -50,7 +50,7 @@ internal class DefaultTotalFiatBalanceCalculator : TotalFiatBalanceCalculator { is WalletDataModel.SameCurrencyTransactionInProgress, is WalletDataModel.TransactionInProgress, is WalletDataModel.NoAccount, - -> if (walletData.isCustom || walletData.fiatRate == null) { + -> if (walletData.isCustom && walletData.fiatRate == null) { TotalFiatBalanceStatus.Error } else { TotalFiatBalanceStatus.Loaded @@ -65,7 +65,7 @@ internal class DefaultTotalFiatBalanceCalculator : TotalFiatBalanceCalculator { private fun Sequence.calculateTotalFiatAmount(): BigDecimal? { return this - .filterNot { it.isCustom } + .filterNot { it.isCustom && it.fiatRate == null } .map { walletData -> walletData.fiatRate ?.takeUnless { walletData.status.isErrorStatus } diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt index 5ca27d5e6c..00342aaddb 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/SendScreenAction.kt @@ -65,6 +65,10 @@ sealed class TransactionExtrasAction : SendScreenActionUi { sealed class XrpDestinationTag : TransactionExtrasAction() { data class HandleUserInput(val data: String) : XrpDestinationTag() } + + sealed class TonMemo : TransactionExtrasAction() { + data class HandleUserInput(val data: String) : TonMemo() + } } sealed class AddressPayIdVerifyAction : SendScreenAction { diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt index b87cc3c49b..0a528bd856 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/middlewares/SendMiddleware.kt @@ -4,6 +4,7 @@ import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tangem.blockchain.blockchains.binance.BinanceTransactionExtras import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider import com.tangem.blockchain.blockchains.stellar.StellarTransactionExtras +import com.tangem.blockchain.blockchains.ton.TonTransactionExtras import com.tangem.blockchain.blockchains.xrp.XrpTransactionBuilder import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.BlockchainSdkError @@ -170,6 +171,7 @@ private fun sendTransaction( transactionExtras.xrpDestinationTag?.tag?.let { txData = txData.copy(extras = XrpTransactionBuilder.XrpTransactionExtras(it)) } + transactionExtras.tonMemoState?.memo?.let { txData = txData.copy(extras = TonTransactionExtras(it)) } scope.launch { val updateWalletResult = walletManager.safeUpdate() diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/TransactionExtrasReducer.kt b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/TransactionExtrasReducer.kt index 583e9f0439..a62a6e361e 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/reducers/TransactionExtrasReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/reducers/TransactionExtrasReducer.kt @@ -6,11 +6,13 @@ import com.tangem.tap.features.send.redux.SendScreenAction import com.tangem.tap.features.send.redux.TransactionExtrasAction.BinanceMemo import com.tangem.tap.features.send.redux.TransactionExtrasAction.Prepare import com.tangem.tap.features.send.redux.TransactionExtrasAction.Release +import com.tangem.tap.features.send.redux.TransactionExtrasAction.TonMemo import com.tangem.tap.features.send.redux.TransactionExtrasAction.XlmMemo import com.tangem.tap.features.send.redux.TransactionExtrasAction.XrpDestinationTag import com.tangem.tap.features.send.redux.states.BinanceMemoState import com.tangem.tap.features.send.redux.states.InputViewValue import com.tangem.tap.features.send.redux.states.SendState +import com.tangem.tap.features.send.redux.states.TonMemoState import com.tangem.tap.features.send.redux.states.TransactionExtraError import com.tangem.tap.features.send.redux.states.TransactionExtrasState import com.tangem.tap.features.send.redux.states.XlmMemoState @@ -28,6 +30,7 @@ class TransactionExtrasReducer : SendInternalReducer { is XlmMemo -> handleXlmMemo(action, sendState, sendState.transactionExtrasState) is BinanceMemo -> handleBinanceMemo(action, sendState, sendState.transactionExtrasState) is XrpDestinationTag -> handleXrpTag(action, sendState, sendState.transactionExtrasState) + is TonMemo -> handleTonMemo(action, sendState, sendState.transactionExtrasState) else -> sendState } } @@ -56,6 +59,7 @@ class TransactionExtrasReducer : SendInternalReducer { } Blockchain.Stellar -> TransactionExtrasState(xlmMemo = XlmMemoState()) Blockchain.Binance -> TransactionExtrasState(binanceMemo = BinanceMemoState()) + Blockchain.TON, Blockchain.TONTestnet -> TransactionExtrasState(tonMemoState = TonMemoState()) else -> emptyResult } return updateLastState(sendState.copy(transactionExtrasState = result), result) @@ -140,4 +144,15 @@ class TransactionExtrasReducer : SendInternalReducer { } return updateLastState(sendState.copy(transactionExtrasState = result), result) } + + private fun handleTonMemo(action: TonMemo, sendState: SendState, infoState: TransactionExtrasState): SendState { + val result = when (action) { + is TonMemo.HandleUserInput -> { + val memo = action.data + val input = InputViewValue(memo, true) + infoState.copy(tonMemoState = TonMemoState(input, memo)) + } + } + return updateLastState(sendState.copy(transactionExtrasState = result), result) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt index 10ffa2dc51..3ff920281d 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/AddressPayIdState.kt @@ -30,6 +30,7 @@ data class TransactionExtrasState( val xlmMemo: XlmMemoState? = null, val binanceMemo: BinanceMemoState? = null, val xrpDestinationTag: XrpDestinationTagState? = null, + val tonMemoState: TonMemoState? = null, ) : IdStateHolder { override val stateId: StateId = StateId.TRANSACTION_EXTRAS } @@ -98,6 +99,12 @@ data class XrpDestinationTagState( } } +data class TonMemoState( + val viewFieldValue: InputViewValue = InputViewValue(""), + val memo: String? = null, + val error: TransactionExtraError? = null, +) + enum class TransactionExtraError { INVALID_DESTINATION_TAG, INVALID_XLM_MEMO, diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt index ecc6d5d00e..519556af28 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/SendFragment.kt @@ -60,7 +60,6 @@ import com.tangem.tap.mainScope import com.tangem.tap.store import com.tangem.wallet.R import com.tangem.wallet.databinding.FragmentSendBinding -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow @@ -173,6 +172,15 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { } .onEach { store.dispatch(TransactionExtrasAction.BinanceMemo.HandleUserInput(it)) } .launchIn(mainScope) + + etTonMemo.inputtedTextAsFlow() + .debounce(400) + .filter { + val info = store.state.sendState.transactionExtrasState + info.tonMemoState?.viewFieldValue?.value != it + } + .onEach { store.dispatch(TransactionExtrasAction.TonMemo.HandleUserInput(it)) } + .launchIn(mainScope) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { @@ -331,7 +339,6 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) { } } -@ExperimentalCoroutinesApi fun EditText.inputtedTextAsFlow(): Flow = callbackFlow { val watcher = addTextChangedListener { editable -> trySend(editable?.toString() ?: "") } awaitClose { removeTextChangedListener(watcher) } diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/KaspaWarningDialog.kt b/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/KaspaWarningDialog.kt index 52d29b6d91..5e9f50747f 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/KaspaWarningDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/dialogs/KaspaWarningDialog.kt @@ -2,7 +2,6 @@ package com.tangem.tap.features.send.ui.dialogs import android.content.Context import androidx.appcompat.app.AlertDialog -import com.tangem.tap.common.extensions.dispatchDialogHide import com.tangem.tap.features.send.redux.SendAction import com.tangem.tap.store import com.tangem.wallet.R @@ -22,7 +21,7 @@ object KaspaWarningDialog { dialog.onOk() } setOnDismissListener { - store.dispatchDialogHide() + store.dispatch(SendAction.Dialog.Hide) } } .create() diff --git a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt index 91376439fc..e250ad075e 100644 --- a/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt +++ b/app/src/main/java/com/tangem/tap/features/send/ui/stateSubscribers/SendStateSubscriber.kt @@ -46,8 +46,8 @@ import com.tangem.wallet.R /** [REDACTED_AUTHOR] */ -class SendStateSubscriber(fragment: BaseStoreFragment) : - FragmentStateSubscriber(fragment) { +@Suppress("LargeClass") +class SendStateSubscriber(fragment: BaseStoreFragment) : FragmentStateSubscriber(fragment) { private var dialog: Dialog? = null @@ -79,6 +79,7 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : showView(xlmMemoContainer, infoState.xlmMemo) showView(xrpDestinationTagContainer, infoState.xrpDestinationTag) showView(binanceMemoContainer, infoState.binanceMemo) + showView(tonMemoContainer, infoState.tonMemoState) infoState.xlmMemo?.let { if (!it.viewFieldValue.isFromUserInput) etXlmMemo.setText(it.viewFieldValue.value) @@ -115,6 +116,16 @@ class SendStateSubscriber(fragment: BaseStoreFragment) : etBinanceMemo.setText(it.viewFieldValue.value) } } + infoState.tonMemoState?.let { + if (infoState.tonMemoState.error != null) { + tilTonMemo.error = fg.getText(R.string.send_extras_error_invalid_memo) + } else { + tilBinanceMemo.error = null + } + if (!it.viewFieldValue.isFromUserInput) { + etTonMemo.setText(it.viewFieldValue.value) + } + } } private fun handleSendScreen(fg: SendFragment, state: SendState) = with(fg.binding) { diff --git a/app/src/main/res/drawable/ic_ton_no_color.xml b/app/src/main/res/drawable/ic_ton_no_color.xml new file mode 100644 index 0000000000..c3d5d856db --- /dev/null +++ b/app/src/main/res/drawable/ic_ton_no_color.xml @@ -0,0 +1,13 @@ + + + + diff --git a/app/src/main/res/layout/layout_send_address_payid.xml b/app/src/main/res/layout/layout_send_address_payid.xml index b16e59b974..f16f48a119 100644 --- a/app/src/main/res/layout/layout_send_address_payid.xml +++ b/app/src/main/res/layout/layout_send_address_payid.xml @@ -228,6 +228,37 @@ + + + + + + + + + + - \ No newline at end of file + diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt index f3d1e037fd..9c11cb37dc 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt @@ -33,6 +33,7 @@ fun getActiveIconRes(blockchainId: String): Int { "OPTIMISM", "OPTIMISM/test" -> R.drawable.img_optimism_22 "DASH" -> R.drawable.img_dash_22 "KAS" -> R.drawable.img_kaspa_22 + "The-Open-Network", "The-Open-Network/test" -> R.drawable.img_ton_22 else -> R.drawable.ic_alert_24 } } @@ -72,6 +73,7 @@ fun getActiveIconResByCoinId(coinId: String, networkId: String): Int { "kusama" -> R.drawable.img_kusama_22 "dash" -> R.drawable.img_dash_22 "kaspa" -> R.drawable.img_kaspa_22 + "ton" -> R.drawable.img_ton_22 else -> R.drawable.ic_alert_24 } } \ No newline at end of file diff --git a/core/ui/src/main/res/drawable/img_ton_22.xml b/core/ui/src/main/res/drawable/img_ton_22.xml new file mode 100644 index 0000000000..a5eae04964 --- /dev/null +++ b/core/ui/src/main/res/drawable/img_ton_22.xml @@ -0,0 +1,13 @@ + + + + diff --git a/fastlane/Fastfile b/fastlane/Fastfile index d1867f7c33..67b386e4e9 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -44,11 +44,11 @@ platform :android do }) end - desc "Build internal and release APKs" + desc "Build external and release APKs" lane :build do |options| gradle( task: "clean assemble", - build_type: "Internal", + build_type: "External", properties: { 'versionCode' => options[:versionCode], 'versionName' => options[:versionName], diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index b955369d7e..cdca70f863 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -244,7 +244,7 @@ internal class SwapInteractorImpl @Inject constructor( return repository.getTangemFee() } - fun getTokenDecimals(token: Currency): Int { + private fun getTokenDecimals(token: Currency): Int { return if (token is Currency.NonNativeToken) { token.decimalCount } else { @@ -351,15 +351,15 @@ internal class SwapInteractorImpl @Inject constructor( swapStateData = null, formattedFee = null, ) - return updatePermissionState( + val quotesState = updatePermissionState( networkId = networkId, fromToken = fromToken, quotesLoadedState = swapState, - ).copy( - preparedSwapConfigState = PreparedSwapConfigState( + ) + return quotesState.copy( + preparedSwapConfigState = quotesState.preparedSwapConfigState.copy( isAllowedToSpend = isAllowedToSpend, isBalanceEnough = isBalanceWithoutFeeEnough, - isFeeEnough = true, ), ) } else { @@ -533,7 +533,14 @@ internal class SwapInteractorImpl @Inject constructor( decimals = transactionManager.getNativeTokenDecimals(networkId), currency = userWalletManager.getNetworkCurrency(networkId), ) + feeFiat + val isFeeEnough = checkFeeIsEnough( + fee = feeData.fee.value, + spendAmount = SwapAmount.zeroSwapAmount(), + networkId = networkId, + fromToken = fromToken, + ) return quotesLoadedState.copy( + fee = formattedFee, permissionState = PermissionDataState.PermissionReadyForRequest( currency = fromToken.symbol, amount = INFINITY_SYMBOL, @@ -546,6 +553,9 @@ internal class SwapInteractorImpl @Inject constructor( approveModel = transactionData, ), ), + preparedSwapConfigState = quotesLoadedState.preparedSwapConfigState.copy( + isFeeEnough = isFeeEnough, + ), ) } diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapAmount.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapAmount.kt index 342ce251e7..c37e6fbc96 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapAmount.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/SwapAmount.kt @@ -6,7 +6,14 @@ import java.math.BigDecimal data class SwapAmount( val value: BigDecimal, val decimals: Int, -) +) { + + companion object { + fun zeroSwapAmount(): SwapAmount { + return SwapAmount(BigDecimal.ZERO, 0) + } + } +} fun SwapAmount.formatToUIRepresentation(): String { return value.toFormattedString(decimals = decimals) diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/PreparedSwapConfigState.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/PreparedSwapConfigState.kt index 29515eef16..8b9f1c963b 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/PreparedSwapConfigState.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/PreparedSwapConfigState.kt @@ -1,5 +1,12 @@ package com.tangem.feature.swap.domain.models.domain +/** + * Prepared swap config state that contains flags to determine + * + * @property isAllowedToSpend shows is token allowed to spend + * @property isBalanceEnough shows is balance of token enough + * @property isFeeEnough shows is amount of main coin enough for fee + */ data class PreparedSwapConfigState( val isAllowedToSpend: Boolean, val isBalanceEnough: Boolean, diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 940a09f956..f6c1447110 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -71,9 +71,9 @@ kotlinSerialization = "1.4.1" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "develop-185" +tangemBlockchainSdk = "develop-189" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "develop-208" +tangemCardSdk = "develop-209" # tangemCardSdk = "0.0.1" # Keep it! - used for local builds # endregion Tangem