diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index ac3eeb6f5d..7fc608245b 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -205,4 +205,4 @@ - \ No newline at end of file + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 48ca967d94..682ebe6588 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -49,6 +49,7 @@ android:launchMode="singleTop" android:screenOrientation="portrait" android:windowSoftInputMode="adjustResize"> + @@ -58,40 +59,25 @@ - - - - - - + + + - - - - + + + - + + + - - - + + + + + + + diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt index 15c29ad5d0..7a788fd5c6 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/AnalyticsParam.kt @@ -87,12 +87,54 @@ sealed class AnalyticsParam { } sealed class TxSentFrom(val value: String) { - object Send : TxSentFrom("Send") - object Swap : TxSentFrom("Swap") + data class Send( + override val blockchain: String, + override val token: String, + override val feeType: FeeType, + ) : TxSentFrom("Send"), TxData + + data class Swap( + override val blockchain: String, + override val token: String, + override val feeType: FeeType, + ) : TxSentFrom("Swap"), TxData + + data class Approve( + override val blockchain: String, + override val token: String, + override val feeType: FeeType, + val permissionType: String, + ) : TxSentFrom("Approve"), TxData + object WalletConnect : TxSentFrom("WalletConnect") object Sell : TxSentFrom("Sell") } + sealed interface TxData { + val blockchain: String + val token: String + val feeType: FeeType + } + + sealed class FeeType(val value: String) { + object Fixed : FeeType("Fixed") + object Min : FeeType("Min") + object Normal : FeeType("Normal") + object Max : FeeType("Max") + + companion object { + fun fromString(feeType: String): FeeType { + return when (feeType) { + Min.value -> Min + Normal.value -> Normal + Max.value -> Max + Fixed.value -> Fixed + else -> Fixed + } + } + } + } + sealed class WalletCreationType(val value: String) { object PrivateKey : WalletCreationType("Private key") object NewSeed : WalletCreationType("New seed") @@ -100,9 +142,13 @@ sealed class AnalyticsParam { } companion object Key { + const val BLOCKCHAIN = "blockchain" + const val TOKEN = "Token" const val SOURCE = "Source" const val BALANCE = "Balance" const val BATCH = "Batch" + const val FEE_TYPE = "Fee Type" + const val PERMISSION_TYPE = "Permission Type" const val PRODUCT_TYPE = "Product Type" const val FIRMWARE = "Firmware" const val CURRENCY = "Currency" diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt index 4f63bd4961..d9b029fcae 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Basic.kt @@ -51,7 +51,17 @@ sealed class Basic( class TransactionSent(sentFrom: AnalyticsParam.TxSentFrom) : Basic( event = "Transaction sent", - params = mapOf(AnalyticsParam.SOURCE to sentFrom.value), + params = buildMap { + this[AnalyticsParam.SOURCE] = sentFrom.value + if (sentFrom is AnalyticsParam.TxData) { + this[AnalyticsParam.BLOCKCHAIN] = sentFrom.blockchain + this[AnalyticsParam.TOKEN] = sentFrom.token + this[AnalyticsParam.FEE_TYPE] = sentFrom.feeType.value + } + if (sentFrom is AnalyticsParam.TxSentFrom.Approve) { + this[AnalyticsParam.PERMISSION_TYPE] = sentFrom.permissionType + } + }, ) class ScanError(error: Throwable) : Basic( diff --git a/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt b/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt index 46e02683ae..85dbfc7936 100644 --- a/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt +++ b/app/src/main/java/com/tangem/tap/common/analytics/events/Token.kt @@ -77,12 +77,6 @@ sealed class Token( class ButtonPaste : Send("Button - Paste") class ButtonQRCode : Send("Button - QR Code") class ButtonSwapCurrency : Send("Button - Swap Currency") - - class TransactionSent(type: CurrencyType, error: Throwable? = null) : Send( - event = "Transaction Sent", - params = mapOf("Token" to type.value), - error = error, - ) } sealed class Topup( diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt index 62a0161bb8..c54f4ff005 100644 --- a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/DisclaimerFragment.kt @@ -3,6 +3,7 @@ package com.tangem.tap.features.disclaimer.ui import android.os.Bundle import android.view.View import android.view.View.OVER_SCROLL_NEVER +import android.webkit.WebView import androidx.transition.TransitionInflater import by.kirich1409.viewbindingdelegate.viewBinding import com.tangem.core.ui.fragments.setStatusBarColor @@ -27,21 +28,6 @@ class DisclaimerFragment : BaseFragment(R.layout.fragment_disclaimer), StoreSubs private val binding: FragmentDisclaimerBinding by viewBinding(FragmentDisclaimerBinding::bind) private val webViewClient = DisclaimerWebViewClient() - override fun configureTransitions() { - val inflater = TransitionInflater.from(requireContext()) - when (store.state.disclaimerState.showedFromScreen) { - AppScreen.Home -> { - enterTransition = inflater.inflateTransition(android.R.transition.slide_bottom) - exitTransition = inflater.inflateTransition(android.R.transition.slide_top) - } - AppScreen.Details -> { - enterTransition = inflater.inflateTransition(android.R.transition.fade) - exitTransition = inflater.inflateTransition(android.R.transition.fade) - } - else -> {} - } - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) addBackPressHandler(handler = this) @@ -86,11 +72,29 @@ class DisclaimerFragment : BaseFragment(R.layout.fragment_disclaimer), StoreSubs super.onStop() } + override fun configureTransitions() { + val inflater = TransitionInflater.from(requireContext()) + when (store.state.disclaimerState.showedFromScreen) { + AppScreen.Home -> { + enterTransition = inflater.inflateTransition(android.R.transition.slide_bottom) + exitTransition = inflater.inflateTransition(android.R.transition.slide_top) + } + AppScreen.Details -> { + enterTransition = inflater.inflateTransition(android.R.transition.fade) + exitTransition = inflater.inflateTransition(android.R.transition.fade) + } + else -> {} + } + } + + override fun handleOnBackPressed() { + store.dispatch(DisclaimerAction.OnBackPressed) + } + override fun newState(state: DisclaimerState) = with(binding) { if (activity == null || view == null) return updateUiVisibility(state.disclaimer, state.progressState) - binding.webView.loadUrl(state.disclaimer.getUri().toString()) } private fun updateUiVisibility(disclaimer: Disclaimer, progressState: ProgressState?) = with(binding) { @@ -111,16 +115,19 @@ class DisclaimerFragment : BaseFragment(R.layout.fragment_disclaimer), StoreSubs } ProgressState.Error -> { root.beginDelayedTransition() - webView.hide() - groupAccept.hide() + webView.show() + groupAccept.show() groupLoading.hide() - groupError.show() + groupError.hide() + webView.loadLocalTermsOfServices() + } + else -> { + webView.loadUrl(disclaimer.getUri().toString()) } - else -> {} } } - override fun handleOnBackPressed() { - store.dispatch(DisclaimerAction.OnBackPressed) + private fun WebView.loadLocalTermsOfServices() { + loadData(localTermsOfServices, "text/html", "UTF-8") } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/ui/LocalTermOfServices.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/LocalTermOfServices.kt new file mode 100644 index 0000000000..6651d0fa75 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/disclaimer/ui/LocalTermOfServices.kt @@ -0,0 +1,66 @@ +package com.tangem.tap.features.disclaimer.ui + +internal val localTermsOfServices = """ + + + + + + + Legal Disclaimer + + +

Legal Disclaimer

+

    1. Tangem application (Software)

+

    The Software is intended for usage only with Tangem hardware wallets (Cards) via NFC interface. The + Software DOES NOT:

+

    a) Generate, store, transmit, or have access to private (secret) cryptographic keys to blockchain + wallets holding digital assets, including crypto-currency.

+

    b) Generate, store, transmit, or have access to secret keys, passwords, passphrases, recovery phrases + that can be used to restore or to copy private (secret) keys to blockchain wallets holding digital assets, including + crypto-currency.

+

    c) Provide exchange, trading, investment services on behalf of Tangem AG.

+

    2. Risks related to the use of Software

+

    Tangem will not be responsible for any losses, damages or claims arising from events falling within the + scope of the following five categories:

+

    a) Mistakes made by the user of any cryptocurrency-related software or service, e.g., forgotten + passwords, payments sent to wrong addresses, and accidental deletion of blockchain wallets on Cards.

+

    b) Problems of Software and/or any blockchain- or cryptocurrency- related software or service, e.g., + corrupted files, incorrectly constructed transactions, unsafe cryptographic libraries, malware.

+

    c) Technical failures in the hardware of the user, including Cards, of any cryptocurrency-related + software or service, e.g., data loss due to a faulty or damaged storage device.

+

    d) Security problems experienced by the user of any cryptocurrency-related software or service, e.g., + unauthorized access to users' wallets and/or accounts.

+

    e) Actions or inactions of third parties and/or events experienced by third parties, e.g., bankruptcy + of service providers, information security attacks on service providers, and fraud conducted by third parties.

+

    3. Trading and Investment risks

+

    There is considerable exposure to risk in any crypto-currency or other digital asset exchange + transaction. Any transaction involving currencies involves risks including, but not limited to, the potential for + changing economic conditions that may substantially affect the price or liquidity of a currency. Investments in + crypto-currency exchange speculation may also be susceptible to sharp rises and falls as the relevant market values + fluctuate. It is for this reason that when speculating in such markets it is advisable to use only risk capital.

+

    4. Electronic Trading Risks

+

    Before you engage in transactions using an electronic system, you should carefully review the rules and + regulations of the exchanges offering the system and/or listing the instruments you intend to trade. Online trading + has inherent risk due to system response and access times that may vary due to market conditions, system + performance, and other factors. You should understand these and additional risks before trading.

+

    5. Compliance with tax obligations        

+

    The users of the Software are solely responsible to determinate what, if any, taxes apply to their + crypto-currency transactions. The owners of, or contributors to, the Software are NOT responsible for determining + the taxes that apply to crypto-currency transactions.        

+

    6. No warranties        

+

    The Software is provided on an "as is" basis without any warranties of any kind regarding the + Software and/or any content, data, materials and/or services provided on the Software.       +  

+

    7. Limitation of liability

+

    Unless otherwise required by law, in no event shall the owners of, or contributors to, the Software be + liable for any damages of any kind, including, but not limited to, loss of use, loss of profits, or loss of data + arising out of or in any way connected with the use of the Software. In no way are the owners of, or contributors + to, the Software responsible for the actions, decisions, or other behavior taken or not taken by you in reliance + upon the Software.

+

    8. Last amendment

+

    This disclaimer was amended for the last time on October 1st, 2020.

+ +""".trimIndent() \ No newline at end of file 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 2d22340b26..c8938497e1 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 @@ -7,28 +7,19 @@ 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 -import com.tangem.blockchain.common.TransactionError -import com.tangem.blockchain.common.TransactionSender -import com.tangem.blockchain.common.WalletManager +import com.tangem.blockchain.common.* import com.tangem.blockchain.extensions.SimpleResult import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.guard import com.tangem.common.services.Result import com.tangem.core.analytics.Analytics -import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.common.TapWorkarounds.isStart2Coin import com.tangem.domain.common.extensions.withMainContext -import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE +import com.tangem.domain.models.scan.CardDTO +import com.tangem.tap.* import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.common.analytics.events.Basic -import com.tangem.tap.common.analytics.events.Token -import com.tangem.tap.common.extensions.dispatchDialogShow -import com.tangem.tap.common.extensions.dispatchErrorNotification -import com.tangem.tap.common.extensions.dispatchOnMain -import com.tangem.tap.common.extensions.safeUpdate -import com.tangem.tap.common.extensions.stripZeroPlainString +import com.tangem.tap.common.extensions.* import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction @@ -39,25 +30,11 @@ import com.tangem.tap.domain.configurable.warningMessage.WarningMessage import com.tangem.tap.domain.extensions.minimalAmount import com.tangem.tap.features.demo.DemoTransactionSender import com.tangem.tap.features.demo.isDemoCard -import com.tangem.tap.features.send.redux.AddressPayIdActionUi -import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction -import com.tangem.tap.features.send.redux.AmountAction -import com.tangem.tap.features.send.redux.AmountActionUi +import com.tangem.tap.features.send.redux.* import com.tangem.tap.features.send.redux.FeeAction.RequestFee -import com.tangem.tap.features.send.redux.PrepareSendScreen -import com.tangem.tap.features.send.redux.SendAction -import com.tangem.tap.features.send.redux.SendActionUi -import com.tangem.tap.features.send.redux.states.ButtonState -import com.tangem.tap.features.send.redux.states.ExternalTransactionData -import com.tangem.tap.features.send.redux.states.MainCurrencyType -import com.tangem.tap.features.send.redux.states.TransactionExtrasState +import com.tangem.tap.features.send.redux.states.* import com.tangem.tap.features.wallet.models.Currency import com.tangem.tap.features.wallet.redux.WalletAction -import com.tangem.tap.scope -import com.tangem.tap.store -import com.tangem.tap.tangemSdk -import com.tangem.tap.userWalletsListManager -import com.tangem.tap.walletCurrenciesManager import com.tangem.wallet.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -65,7 +42,7 @@ import kotlinx.coroutines.launch import org.rekotlin.Action import org.rekotlin.Middleware import timber.log.Timber -import java.util.* +import java.util.EnumSet /** [REDACTED_AUTHOR] @@ -134,9 +111,16 @@ private fun verifyAndSendTransaction( }, sendAllCallback = { sendTransaction( - action, walletManager, amountToSend, feeAmount, destinationAddress, - sendState.transactionExtrasState, card, sendState.externalTransactionData, - dispatch, + action = action, + walletManager = walletManager, + amountToSend = amountToSend, + feeAmount = feeAmount, + feeType = sendState.feeState.selectedFeeType, + destinationAddress = destinationAddress, + transactionExtras = sendState.transactionExtrasState, + card = card, + externalTransactionData = sendState.externalTransactionData, + dispatch = dispatch, ) }, reduceAmount, @@ -145,8 +129,16 @@ private fun verifyAndSendTransaction( } else -> { sendTransaction( - action, walletManager, amountToSend, feeAmount, destinationAddress, - sendState.transactionExtrasState, card, sendState.externalTransactionData, dispatch, + action = action, + walletManager = walletManager, + amountToSend = amountToSend, + feeAmount = feeAmount, + feeType = sendState.feeState.selectedFeeType, + destinationAddress = destinationAddress, + transactionExtras = sendState.transactionExtrasState, + card = card, + externalTransactionData = sendState.externalTransactionData, + dispatch = dispatch, ) } } @@ -158,6 +150,7 @@ private fun sendTransaction( walletManager: WalletManager, amountToSend: Amount, feeAmount: Amount, + feeType: FeeType, destinationAddress: String, transactionExtras: TransactionExtrasState, card: CardDTO, @@ -243,17 +236,23 @@ private fun sendTransaction( dispatch(SendAction.ChangeSendButtonState(ButtonState.ENABLED)) tangemSdk.config.linkedTerminal = linkedTerminalState - val currencyType = AnalyticsParam.CurrencyType.Amount(amountToSend) when (sendResult) { is SimpleResult.Success -> { - Analytics.send(Token.Send.TransactionSent(currencyType)) dispatch(SendAction.SendSuccess) if (externalTransactionData != null) { Analytics.send(Basic.TransactionSent(AnalyticsParam.TxSentFrom.Sell)) dispatch(WalletAction.TradeCryptoAction.FinishSelling(externalTransactionData.transactionId)) } else { - Analytics.send(Basic.TransactionSent(AnalyticsParam.TxSentFrom.Send)) + Analytics.send( + Basic.TransactionSent( + sentFrom = AnalyticsParam.TxSentFrom.Send( + blockchain = walletManager.wallet.blockchain.fullName, + token = amountToSend.currencySymbol, + feeType = feeType.convertToAnalyticsFeeType(), + ), + ), + ) dispatch(NavigationAction.PopBackTo()) } scope.launch(Dispatchers.IO) { @@ -271,8 +270,6 @@ private fun sendTransaction( ) val error = sendResult.error as? BlockchainSdkError ?: return@withMainContext - Analytics.send(Token.Send.TransactionSent(currencyType, error)) - when (error) { is BlockchainSdkError.WrappedTangemError -> { val tangemSdkError = error.tangemError as? TangemSdkError ?: return@withMainContext diff --git a/app/src/main/java/com/tangem/tap/features/send/redux/states/FeeState.kt b/app/src/main/java/com/tangem/tap/features/send/redux/states/FeeState.kt index e9c9472bb4..65067fc0f8 100644 --- a/app/src/main/java/com/tangem/tap/features/send/redux/states/FeeState.kt +++ b/app/src/main/java/com/tangem/tap/features/send/redux/states/FeeState.kt @@ -1,6 +1,7 @@ package com.tangem.tap.features.send.redux.states import com.tangem.blockchain.common.Amount +import com.tangem.tap.common.analytics.events.AnalyticsParam import com.tangem.tap.features.wallet.redux.ProgressState import java.math.BigDecimal @@ -8,7 +9,16 @@ import java.math.BigDecimal [REDACTED_AUTHOR] */ enum class FeeType { - SINGLE, LOW, NORMAL, PRIORITY + SINGLE, LOW, NORMAL, PRIORITY; +} + +fun FeeType.convertToAnalyticsFeeType(): AnalyticsParam.FeeType { + return when (this) { + FeeType.SINGLE -> AnalyticsParam.FeeType.Fixed + FeeType.LOW -> AnalyticsParam.FeeType.Min + FeeType.NORMAL -> AnalyticsParam.FeeType.Normal + FeeType.PRIORITY -> AnalyticsParam.FeeType.Max + } } data class FeeState( diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/BriefNetworksList.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/BriefNetworksList.kt index 9ecb139cae..104ebf9d8c 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/BriefNetworksList.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/BriefNetworksList.kt @@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.key import androidx.compose.ui.Alignment @@ -21,6 +22,9 @@ import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.features.tokens.impl.presentation.states.NetworkItemState import kotlinx.collections.immutable.ImmutableCollection +/** This const configures how many items show to user and hide more than*/ +private const val MAX_VISIBLE_BRIEF_ICONS = 9 + /** [REDACTED_AUTHOR] */ @@ -37,10 +41,25 @@ internal fun BriefNetworksList( exit = fadeOut(), ) { Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4)) { - networks.forEach { network -> - key(network.name + network.protocolName) { - BriefNetworkItem(model = network) + val iterator = networks.iterator() + var index = 0 + while (iterator.hasNext()) { + val network = iterator.next() + if (index < MAX_VISIBLE_BRIEF_ICONS) { + key(network.name + network.protocolName) { + BriefNetworkItem(model = network) + } + } else { + if (networks.size < MAX_VISIBLE_BRIEF_ICONS + 1) { + key(network.name + network.protocolName) { + BriefNetworkItem(model = network) + } + } else { + HasMoreItem(moreCount = networks.size - index) + break + } } + index++ } } } @@ -50,8 +69,8 @@ internal fun BriefNetworksList( [REDACTED_AUTHOR] */ @Composable -internal fun BriefNetworkItem(model: NetworkItemState) { - Box(modifier = Modifier.size(size = TangemTheme.dimens.size20)) { +internal fun BriefNetworkItem(model: NetworkItemState, modifier: Modifier = Modifier) { + Box(modifier = modifier.size(size = TangemTheme.dimens.size20)) { Image( painter = painterResource(id = model.iconResId.value), contentDescription = null, @@ -76,4 +95,21 @@ internal fun BriefNetworkItem(model: NetworkItemState) { } } } +} + +@Composable +internal fun HasMoreItem(moreCount: Int) { + Box( + modifier = Modifier + .size(size = TangemTheme.dimens.size20) + .clip(CircleShape) + .background(TangemTheme.colors.control.unchecked), + ) { + Text( + modifier = Modifier.align(Alignment.Center), + text = "+$moreCount", + style = TangemTheme.typography.overline, + color = TangemTheme.colors.text.tertiary, + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt index 8cc33eca6c..9955bf6d6d 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt @@ -110,9 +110,10 @@ internal class WelcomeMiddleware { } private fun handleInitialIntent(intent: Intent?) { - val isBackgroundScanWasHandled = intentHandler.handleBackgroundScan(intent, hasSavedUserWallets = true) + val isBackgroundScanNotHandled = !intentHandler.handleBackgroundScan(intent, hasSavedUserWallets = true) + val hasNotIncompletedBackup = !backupService.hasIncompletedBackup - if (!isBackgroundScanWasHandled) { + if (isBackgroundScanNotHandled && hasNotIncompletedBackup) { store.dispatchOnMain(WelcomeAction.ProceedWithBiometrics) } } diff --git a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt index dabd3c53ec..216203fce9 100644 --- a/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt +++ b/app/src/main/java/com/tangem/tap/proxy/TransactionManagerImpl.kt @@ -34,14 +34,11 @@ class TransactionManagerImpl( ) : TransactionManager { override suspend fun sendApproveTransaction( - networkId: String, - feeAmount: BigDecimal, - gasLimit: Int, - destinationAddress: String, - dataToSign: String, + txData: ApproveTxData, derivationPath: String?, + analyticsData: AnalyticsData, ): SendTxResult { - val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } + val blockchain = requireNotNull(Blockchain.fromNetworkId(txData.networkId)) { "blockchain not found" } val walletManager = getActualWalletManager(blockchain, derivationPath) walletManager.update() val amount = Amount(value = BigDecimal.ZERO, blockchain = blockchain) @@ -49,40 +46,55 @@ class TransactionManagerImpl( walletManager = walletManager, amount = amount, blockchain = blockchain, - feeAmount = feeAmount, - gasLimit = gasLimit, - destinationAddress = destinationAddress, - dataToSign = dataToSign, + feeAmount = txData.feeAmount, + gasLimit = txData.gasLimit, + destinationAddress = txData.destinationAddress, + dataToSign = txData.dataToSign, + successEvent = AnalyticsParam.TxSentFrom.Approve( + blockchain = blockchain.fullName, + token = analyticsData.tokenSymbol, + feeType = AnalyticsParam.FeeType.fromString(analyticsData.feeType), + permissionType = analyticsData.permissionType ?: "", + ), ) } override suspend fun sendTransaction( - networkId: String, - amountToSend: BigDecimal, - feeAmount: BigDecimal, - gasLimit: Int, - destinationAddress: String, - dataToSign: String, + txData: SwapTxData, isSwap: Boolean, - currencyToSend: Currency, derivationPath: String?, + analyticsData: AnalyticsData, ): SendTxResult { - val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } + val blockchain = requireNotNull(Blockchain.fromNetworkId(txData.networkId)) { "blockchain not found" } val walletManager = getActualWalletManager(blockchain, derivationPath) walletManager.update() val amount = if (isSwap) { - createAmountForSwap(amountToSend, currencyToSend, blockchain) + createAmountForSwap(txData.amountToSend, txData.currencyToSend, blockchain) } else { - createAmount(amountToSend, currencyToSend, blockchain) + createAmount(txData.amountToSend, txData.currencyToSend, blockchain) + } + val successEvent = if (isSwap) { + AnalyticsParam.TxSentFrom.Swap( + blockchain = blockchain.fullName, + token = analyticsData.tokenSymbol, + feeType = AnalyticsParam.FeeType.fromString(analyticsData.feeType), + ) + } else { + AnalyticsParam.TxSentFrom.Send( + blockchain = blockchain.fullName, + token = analyticsData.tokenSymbol, + feeType = AnalyticsParam.FeeType.fromString(analyticsData.feeType), + ) } return sendTransactionInternal( walletManager = walletManager, amount = amount, blockchain = blockchain, - feeAmount = feeAmount, - gasLimit = gasLimit, - destinationAddress = destinationAddress, - dataToSign = dataToSign, + feeAmount = txData.feeAmount, + gasLimit = txData.gasLimit, + destinationAddress = txData.destinationAddress, + dataToSign = txData.dataToSign, + successEvent = successEvent, ) } @@ -95,6 +107,7 @@ class TransactionManagerImpl( gasLimit: Int, destinationAddress: String, dataToSign: String, + successEvent: AnalyticsParam.TxSentFrom, ): SendTxResult { val txData = walletManager.createTransaction( amount = amount, @@ -110,7 +123,10 @@ class TransactionManagerImpl( FirebaseCrashlytics.getInstance().recordException(ex) return SendTxResult.UnknownError(ex) } - return handleSendResult(sendResult) + return handleSendResult( + result = sendResult, + successEvent = successEvent, + ) } override fun getExplorerTransactionLink(networkId: String, txAddress: String): String { @@ -319,10 +335,10 @@ class TransactionManagerImpl( } } - private fun handleSendResult(result: SimpleResult): SendTxResult { + private fun handleSendResult(result: SimpleResult, successEvent: AnalyticsParam.TxSentFrom): SendTxResult { when (result) { is SimpleResult.Success -> { - analytics.send(Basic.TransactionSent(AnalyticsParam.TxSentFrom.Swap)) + analytics.send(Basic.TransactionSent(sentFrom = successEvent)) return SendTxResult.Success } is SimpleResult.Failure -> { diff --git a/app/src/main/res/layout/fragment_shop.xml b/app/src/main/res/layout/fragment_shop.xml index c3dadcdb4b..523b68c535 100644 --- a/app/src/main/res/layout/fragment_shop.xml +++ b/app/src/main/res/layout/fragment_shop.xml @@ -132,7 +132,7 @@ android:paddingStart="3dp" android:paddingTop="3dp" android:paddingEnd="3dp" - app:layout_constraintBottom_toTopOf="@id/v_shipping_discount_background" + app:layout_constraintBottom_toTopOf="@id/ll_total" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:selectionRequired="true" @@ -156,162 +156,125 @@ app:chipMinTouchTargetSize="0dp" /> - - - - - - - - - - - - - - - - - - + android:orientation="vertical" + app:layout_constraintBottom_toTopOf="@id/ll_buttons"> - + - + - + - + + + + + + + + + + + + + + + + + = ReactiveNetwork - .observeInternetConnectivity() + .observeInternetConnectivity( + InternetObservingSettings.builder() + .interval(PING_INTERVAL) + .build(), + ) .toFlowable(BackpressureStrategy.LATEST) .asFlow() - .stateIn(scope, SharingStarted.Eagerly, initialValue = false) + .stateIn(scope, SharingStarted.Lazily, initialValue = initialNetworkResult) override val isOnline: Boolean get() = isOnlineFlow.value diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 0fe972b284..2baace2c4d 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -333,11 +333,10 @@ Транзакция успешно подписана и отправлена в блокчейн. Баланс будет обновлен через некоторое время Неверный адрес Купить сейчас - Бесплатно У меня есть промо-код… Tangem Wallet Другие способы оплаты - Доставка + Из-за большого количества заказов, которые мы получаем, cроки доставки могут быть увеличены. Итого Сеть Solana взимает арендную плату в размере %1$s каждые 2 дня. Аккаунты, которые не могут позволить себе арендную плату, удаляются из сети. Пополните свой счет более чем на %2$s, чтобы не платить арендную плату. Держите свои криптосбережения в безопасности. Приватные ключи надежно хранятся на карте. diff --git a/core/res/src/main/res/values-zh-rTW/strings.xml b/core/res/src/main/res/values-zh-rTW/strings.xml index d53dd02d89..08f4e9e68b 100644 --- a/core/res/src/main/res/values-zh-rTW/strings.xml +++ b/core/res/src/main/res/values-zh-rTW/strings.xml @@ -323,11 +323,9 @@ 交易已成功簽署並發送至區塊鏈節點。錢包餘額稍後更新 無效地址 現在購買 - 免費 輸入折扣碼 Tangem Wallet 其他付款方式 - 運送 總計 Solana 網絡每 2 天收取 %1$s 的費用。無法付此費用的帳戶將從網絡中清除。向您的帳戶存入超過 %2$s 即可免費使用 安全地存儲您的加密貨幣,同時將私鑰保存在您的卡中 diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 3ce5104d33..ea3c2c3c93 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -329,11 +329,10 @@ Transaction has been successfully signed and sent to the blockchain node. Wallet balance will be updated in a while Invalid address Buy now - Free I have a promo code… Tangem Wallet Other payment methods - Shipping + Due to the high volume of orders we are receiving, Shipping and Local Delivery orders may be delayed. Total Solana network charges a rent of %1$s every 2 days. Accounts that can\'t afford the rent are purged from the network. Deposit your account with more than %2$s to use it for free. Store your crypto assets secure while keeping private keys contained in your card 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 835ebdf273..f3f193b180 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 @@ -4,16 +4,13 @@ import com.tangem.feature.swap.domain.cache.SwapDataCache import com.tangem.feature.swap.domain.converters.CryptoCurrencyConverter import com.tangem.feature.swap.domain.models.DataError import com.tangem.feature.swap.domain.models.SwapAmount +import com.tangem.feature.swap.domain.models.domain.* import com.tangem.feature.swap.domain.models.domain.Currency -import com.tangem.feature.swap.domain.models.domain.PermissionOptions -import com.tangem.feature.swap.domain.models.domain.PreparedSwapConfigState -import com.tangem.feature.swap.domain.models.domain.SwapApproveType import com.tangem.feature.swap.domain.models.toStringWithRightOffset import com.tangem.feature.swap.domain.models.ui.* import com.tangem.lib.crypto.TransactionManager import com.tangem.lib.crypto.UserWalletManager -import com.tangem.lib.crypto.models.ProxyFees -import com.tangem.lib.crypto.models.ProxyFiatCurrency +import com.tangem.lib.crypto.models.* import com.tangem.lib.crypto.models.transactions.SendTxResult import com.tangem.utils.toFiatString import java.math.BigDecimal @@ -115,12 +112,19 @@ internal class SwapInteractorImpl @Inject constructor( permissionOptions.approveData.approveModel.data } val result = transactionManager.sendApproveTransaction( - networkId = networkId, - feeAmount = permissionOptions.txFee.feeValue, - gasLimit = permissionOptions.txFee.gasLimit, - destinationAddress = permissionOptions.approveData.approveModel.toAddress, - dataToSign = dataToSign, + txData = ApproveTxData( + networkId = networkId, + feeAmount = permissionOptions.txFee.feeValue, + gasLimit = permissionOptions.txFee.gasLimit, + destinationAddress = permissionOptions.approveData.approveModel.toAddress, + dataToSign = dataToSign, + ), derivationPath = derivationPath, + analyticsData = AnalyticsData( + feeType = permissionOptions.txFee.feeType.getNameForAnalytics(), + tokenSymbol = permissionOptions.fromToken.symbol, + permissionType = permissionOptions.approveType.getNameForAnalytics(), + ), ) return when (result) { is SendTxResult.Success -> { @@ -188,15 +192,21 @@ internal class SwapInteractorImpl @Inject constructor( ): TxState { val amount = requireNotNull(toBigDecimalOrNull(amountToSwap)) { "wrong amount format, use only digits" } val result = transactionManager.sendTransaction( - networkId = networkId, - amountToSend = amount, - currencyToSend = cryptoCurrencyConverter.convert(currencyToSend), - feeAmount = fee.feeValue, - gasLimit = fee.gasLimit, - destinationAddress = swapStateData.swapModel.transaction.toWalletAddress, - dataToSign = swapStateData.swapModel.transaction.data, + txData = SwapTxData( + networkId = networkId, + amountToSend = amount, + currencyToSend = cryptoCurrencyConverter.convert(currencyToSend), + feeAmount = fee.feeValue, + gasLimit = fee.gasLimit, + destinationAddress = swapStateData.swapModel.transaction.toWalletAddress, + dataToSign = swapStateData.swapModel.transaction.data, + ), isSwap = true, derivationPath = derivationPath, + analyticsData = AnalyticsData( + feeType = fee.feeType.getNameForAnalytics(), + tokenSymbol = currencyToSend.symbol, + ), ) return when (result) { is SendTxResult.Success -> { @@ -587,12 +597,14 @@ internal class SwapInteractorImpl @Inject constructor( gasLimit = normalFeeGas, feeFiatFormatted = normalFiatFee, feeCryptoFormatted = normalCryptoFee, + feeType = FeeType.NORMAL, ), priorityFee = TxFee( feeValue = priorityFeeValue, gasLimit = priorityFeeGas, feeFiatFormatted = priorityFiatFee, feeCryptoFormatted = priorityCryptoFee, + feeType = FeeType.PRIORITY, ), ) } @@ -670,8 +682,8 @@ internal class SwapInteractorImpl @Inject constructor( private const val DEFAULT_SLIPPAGE = 2 private const val ZERO_BALANCE = "0" private const val DEFAULT_BLOCKCHAIN_INCH_ADDRESS = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" - private const val INCREASE_FEE_TO_CHECK_ENOUGH_PERCENT = 1.4 - private const val INCREASE_GAS_LIMIT_BY = 125 // 25% + private const val INCREASE_FEE_TO_CHECK_ENOUGH_PERCENT = 1.0 // if need to increase fee when check isEnough + private const val INCREASE_GAS_LIMIT_BY = 112 // 12% private const val USDT_SYMBOL = "USDT" private const val USDC_SYMBOL = "USDC" private const val INFINITY_SYMBOL = "∞" diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/SwapApproveType.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/SwapApproveType.kt index 8690e83395..21ded70713 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/SwapApproveType.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/domain/SwapApproveType.kt @@ -2,4 +2,11 @@ package com.tangem.feature.swap.domain.models.domain enum class SwapApproveType { LIMITED, UNLIMITED +} + +fun SwapApproveType.getNameForAnalytics(): String { + return when (this) { + SwapApproveType.LIMITED -> "Transaction" + SwapApproveType.UNLIMITED -> "Unlimited" + } } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt index 8e4258d548..d2b760740e 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapState.kt @@ -77,4 +77,16 @@ data class TxFee( val gasLimit: Int, val feeFiatFormatted: String, val feeCryptoFormatted: String, -) \ No newline at end of file + val feeType: FeeType, +) + +enum class FeeType { + NORMAL, PRIORITY +} + +fun FeeType.getNameForAnalytics(): String { + return when (this) { + FeeType.NORMAL -> "Normal" + FeeType.PRIORITY -> "Max" + } +} \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt index 27e4b3c750..f5a39df29e 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt @@ -25,6 +25,7 @@ import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.getActiveIconRes import com.tangem.core.ui.extensions.getActiveIconResByCoinId import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.swap.domain.models.ui.FeeType import com.tangem.feature.swap.domain.models.ui.TxFee import com.tangem.feature.swap.models.* import com.tangem.feature.swap.presentation.R @@ -394,6 +395,7 @@ val stateSelectable = SelectableItemsState( gasLimit = 0, feeFiatFormatted = "", feeCryptoFormatted = "", + feeType = FeeType.NORMAL, ), ), items = listOf( @@ -407,6 +409,7 @@ val stateSelectable = SelectableItemsState( gasLimit = 0, feeFiatFormatted = "", feeCryptoFormatted = "", + feeType = FeeType.NORMAL, ), ), Item( @@ -419,6 +422,7 @@ val stateSelectable = SelectableItemsState( gasLimit = 0, feeFiatFormatted = "", feeCryptoFormatted = "", + feeType = FeeType.NORMAL, ), ), ).toImmutableList(), diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index d92341d185..06b3dd6a6b 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -75,9 +75,9 @@ reactiveNetwork = "3.0.8" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "develop-237" +tangemBlockchainSdk = "develop-240" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "develop-249" +tangemCardSdk = "develop-266" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds # endregion Tangem @@ -179,7 +179,7 @@ appsflyer = { module = "com.appsflyer:af-android-sdk", version.ref = "appsflyer" armadillo = { module = "at.favre.lib:armadillo", version.ref = "armadillo" } coil = { module = "io.coil-kt:coil", version.ref = "coil" } kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutine" } -kotlin-coroutines-reactive = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-reactive", version.ref = "coroutine" } +kotlin-coroutines-rx2 = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-rx2", version.ref = "coroutine" } kotlin-immutable-collections = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "kotlin-immutable-collections" } desugar = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugarJdkLibs" } googlePlay-core = { module = "com.google.android.play:core", version.ref = "googlePlayCore" } diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt index 8c7e7412f0..5a915b109e 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/TransactionManager.kt @@ -1,8 +1,6 @@ package com.tangem.lib.crypto -import com.tangem.lib.crypto.models.Currency -import com.tangem.lib.crypto.models.ProxyFees -import com.tangem.lib.crypto.models.ProxyNetworkInfo +import com.tangem.lib.crypto.models.* import com.tangem.lib.crypto.models.transactions.SendTxResult import java.math.BigDecimal @@ -11,26 +9,25 @@ interface TransactionManager { @Suppress("LongParameterList") @Throws(IllegalStateException::class) suspend fun sendApproveTransaction( - networkId: String, - feeAmount: BigDecimal, - gasLimit: Int, - destinationAddress: String, - dataToSign: String, + txData: ApproveTxData, derivationPath: String?, + analyticsData: AnalyticsData, ): SendTxResult - @Suppress("LongParameterList") + /** + * Send transaction + * + * @param txData data to build a tx + * @param derivationPath for select right walletManager + * @param analyticsData data for send analytics event + * @return result of transaction + */ @Throws(IllegalStateException::class) suspend fun sendTransaction( - networkId: String, - amountToSend: BigDecimal, - feeAmount: BigDecimal, - gasLimit: Int, - destinationAddress: String, - dataToSign: String, + txData: SwapTxData, isSwap: Boolean, - currencyToSend: Currency, derivationPath: String?, + analyticsData: AnalyticsData, ): SendTxResult /** diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/AnalyticsData.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/AnalyticsData.kt new file mode 100644 index 0000000000..71a0845c8d --- /dev/null +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/AnalyticsData.kt @@ -0,0 +1,14 @@ +package com.tangem.lib.crypto.models + +/** + * Analytics data for send events in analytics engine + * + * @property feeType type of fee (min,max,normal) + * @property tokenSymbol symbol + * @property permissionType optional parameter used for type tx approve + */ +data class AnalyticsData( + val feeType: String, + val tokenSymbol: String, + val permissionType: String? = null, +) \ No newline at end of file diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/ApproveTxData.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/ApproveTxData.kt new file mode 100644 index 0000000000..f2bfab34e3 --- /dev/null +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/ApproveTxData.kt @@ -0,0 +1,20 @@ +package com.tangem.lib.crypto.models + +import java.math.BigDecimal + +/** + * Tx data for create and make approve transaction + * + * @property networkId id of network + * @property feeAmount amount of fee + * @property gasLimit gasLimit for given tx + * @property destinationAddress address to send tx + * @property dataToSign data to sing with signer + */ +data class ApproveTxData( + val networkId: String, + val feeAmount: BigDecimal, + val gasLimit: Int, + val destinationAddress: String, + val dataToSign: String, +) \ No newline at end of file diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/SwapTxData.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/SwapTxData.kt new file mode 100644 index 0000000000..723169c990 --- /dev/null +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/SwapTxData.kt @@ -0,0 +1,24 @@ +package com.tangem.lib.crypto.models + +import java.math.BigDecimal + +/** + * Tx data for create and make transaction + * + * @property networkId id of network + * @property feeAmount amount of fee + * @property gasLimit gasLimit for given tx + * @property destinationAddress address to send tx + * @property dataToSign data to sing with signer + * @property amountToSend amount of tx + * @property currencyToSend currency for tx + */ +data class SwapTxData( + val networkId: String, + val feeAmount: BigDecimal, + val gasLimit: Int, + val destinationAddress: String, + val dataToSign: String, + val amountToSend: BigDecimal, + val currencyToSend: Currency, +) \ No newline at end of file