diff --git a/app/build.gradle b/app/build.gradle index abf8cda691..d51ad6b9a6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -93,7 +93,7 @@ dependencies { implementation 'com.google.android.play:core-ktx:1.8.1' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' - implementation 'com.tangem:blockchain:develop-93' + implementation 'com.tangem:blockchain:AND-1939_tron_fix_release-97' // implementation 'com.tangem:blockchain:0.0.1' implementation 'com.tangem.tangem-sdk-kotlin:core:develop-154' implementation 'com.tangem.tangem-sdk-kotlin:android:develop-154' diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index 7f058ec409..bf673f7f88 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit 7f058ec409b4eaaf8688ecd4bf944ef8d58d3564 +Subproject commit bf673f7f88ed561cd8827447c4886f80f30b7e23 diff --git a/app/src/main/java/com/tangem/tap/common/DialogManager.kt b/app/src/main/java/com/tangem/tap/common/DialogManager.kt index 8af2818c0a..9af6c88b29 100644 --- a/app/src/main/java/com/tangem/tap/common/DialogManager.kt +++ b/app/src/main/java/com/tangem/tap/common/DialogManager.kt @@ -22,6 +22,7 @@ import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.UnfinishedB import com.tangem.tap.features.wallet.redux.WalletDialog import com.tangem.tap.features.wallet.ui.dialogs.AmountToSendBottomSheetDialog import com.tangem.tap.features.wallet.ui.dialogs.ChooseTradeActionBottomSheetDialog +import com.tangem.tap.features.wallet.ui.dialogs.RussianCardholdersWarningBottomSheetDialog import com.tangem.tap.features.wallet.ui.dialogs.ScanFailsDialog import com.tangem.tap.features.wallet.ui.dialogs.SignedHashesWarningDialog import com.tangem.tap.features.wallet.ui.dialogs.SimpleOkDialog @@ -115,6 +116,8 @@ class DialogManager : StoreSubscriber { AmountToSendBottomSheetDialog(context, state.dialog) is WalletDialog.SignedHashesMultiWalletDialog -> SignedHashesWarningDialog.create(context) + is WalletDialog.RussianCardholdersWarningDialog -> + RussianCardholdersWarningBottomSheetDialog(context) else -> null } dialog?.show() diff --git a/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt b/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt index d671b60a29..40acdd1b7d 100644 --- a/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt +++ b/app/src/main/java/com/tangem/tap/common/extensions/WalletManager.kt @@ -49,11 +49,11 @@ suspend fun WalletManager.safeUpdate(): Result = try { fun WalletManager?.getToUpUrl(): String? { val globalState = store.state.globalState - val currencyExchangeManager = globalState.currencyExchangeManager ?: return null + val exchangeManager = globalState.exchangeManager ?: return null val wallet = this?.wallet ?: return null val defaultAddress = wallet.address - return currencyExchangeManager.getUrl( + return exchangeManager.getUrl( action = CurrencyExchangeManager.Action.Buy, blockchain = wallet.blockchain, cryptoCurrencyName = wallet.blockchain.currency, diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt index b871b7853f..a82d5ba94b 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt @@ -15,7 +15,6 @@ import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager import com.tangem.tap.features.details.redux.SecurityOption import com.tangem.tap.features.feedback.EmailData import com.tangem.tap.features.feedback.FeedbackManager -import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager import org.rekotlin.Action sealed class GlobalAction : Action { @@ -77,7 +76,17 @@ sealed class GlobalAction : Action { data class SendFeedback(val emailData: EmailData) : GlobalAction() data class UpdateFeedbackInfo(val walletManagers: List) : GlobalAction() - object InitCurrencyExchangeManager : GlobalAction() { - data class Success(val exchangeManager: CurrencyExchangeManager) : GlobalAction() + object ExchangeManager : GlobalAction() { + object Init : GlobalAction() { + data class Success( + val exchangeManager: com.tangem.tap.network.exchangeServices.CurrencyExchangeManager, + ) : GlobalAction() + } + + object Update : GlobalAction() + } + + object FetchUserCountry : GlobalAction() { + data class Success(val countryCode: String) : GlobalAction() } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt index 1e104b5dd4..344a11cc29 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMidlleware.kt @@ -2,20 +2,31 @@ package com.tangem.tap.common.redux.global import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError +import com.tangem.common.extensions.guard import com.tangem.common.extensions.ifNotNull +import com.tangem.common.services.Result import com.tangem.domain.common.extensions.withMainContext -import com.tangem.tap.* +import com.tangem.tap.common.extensions.dispatchDebugErrorNotification import com.tangem.tap.common.extensions.dispatchDialogShow import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.common.redux.AppState +import com.tangem.tap.currenciesRepository import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager import com.tangem.tap.features.send.redux.SendAction import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager +import com.tangem.tap.network.exchangeServices.mercuryo.MercuryoApi +import com.tangem.tap.network.exchangeServices.mercuryo.MercuryoService import com.tangem.tap.network.exchangeServices.moonpay.MoonPayService -import com.tangem.tap.network.exchangeServices.onramper.OnramperService +import com.tangem.tap.preferencesStorage +import com.tangem.tap.scope +import com.tangem.tap.store +import com.tangem.tap.tangemSdkManager +import java.util.Locale import kotlinx.coroutines.launch +import org.rekotlin.Action +import org.rekotlin.DispatchFunction import org.rekotlin.Middleware class GlobalMiddleware { @@ -24,94 +35,131 @@ class GlobalMiddleware { } } -private val globalMiddlewareHandler: Middleware = { _, appState -> +private val globalMiddlewareHandler: Middleware = { dispatch, appState -> { nextDispatch -> { action -> - when (action) { - is GlobalAction.ScanFailsCounter.ChooseBehavior -> { - when (action.result) { - is CompletionResult.Success -> store.dispatch(GlobalAction.ScanFailsCounter.Reset) + handleAction(action, appState, dispatch) + nextDispatch(action) + } + } +} + +private fun handleAction(action: Action, appState: () -> AppState?, dispatch: DispatchFunction) { + when (action) { + is GlobalAction.ScanFailsCounter.ChooseBehavior -> { + when (action.result) { + is CompletionResult.Success -> store.dispatch(GlobalAction.ScanFailsCounter.Reset) + is CompletionResult.Failure -> { + if (action.result.error is TangemSdkError.UserCancelled) { + store.dispatch(GlobalAction.ScanFailsCounter.Increment) + if (store.state.globalState.scanCardFailsCounter >= 2) { + store.dispatchDialogShow(AppDialog.ScanFailsDialog) + } + } else { + store.dispatch(GlobalAction.ScanFailsCounter.Reset) + } + } + } + } + is GlobalAction.RestoreAppCurrency -> { + store.dispatch(GlobalAction.RestoreAppCurrency.Success( + preferencesStorage.fiatCurrenciesPrefStorage.getAppCurrency() + )) + } + is GlobalAction.HideWarningMessage -> { + store.state.globalState.warningManager?.let { + if (it.hideWarning(action.warning)) { + if (WarningMessagesManager.isAlreadySignedHashesWarning(action.warning)) { + //TODO: No appropriate warningMessage identification. Make it better later + store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId) + } + + store.dispatch(WalletAction.Warnings.Update) + store.dispatch(SendAction.Warnings.Update) + } + } + } + is GlobalAction.SendFeedback -> { + store.state.globalState.feedbackManager?.send(action.emailData) + } + is GlobalAction.UpdateWalletSignedHashes -> { + store.dispatch(WalletAction.Warnings.CheckRemainingSignatures(action.remainingSignatures)) + } + is GlobalAction.UpdateFeedbackInfo -> { + store.state.globalState.feedbackManager?.infoHolder + ?.setWalletsInfo(action.walletManagers) + } + is GlobalAction.ExchangeManager.Init -> { + val config = appState()?.globalState?.configManager?.config + ifNotNull( + config?.mercuryoWidgetId, + config?.mercuryoSecret, + config?.moonPayApiKey, + config?.moonPayApiSecretKey, + ) { mercuryoWidgetId, mercuryoSecret, moonPayKey, moonPaySecretKey -> + scope.launch { + val buyService = MercuryoService( + apiVersion = MercuryoApi.API_VERSION, + mercuryoWidgetId = mercuryoWidgetId, + secret = mercuryoSecret, + ) + val sellService = MoonPayService(moonPayKey, moonPaySecretKey) + val exchangeManager = CurrencyExchangeManager(buyService, sellService) + store.dispatchOnMain(GlobalAction.ExchangeManager.Init.Success(exchangeManager)) + store.dispatchOnMain(GlobalAction.ExchangeManager.Update) + } + } + } + is GlobalAction.ExchangeManager.Init.Success -> {} + is GlobalAction.ExchangeManager.Update -> { + val exchangeManager = appState()?.globalState?.exchangeManager.guard { + store.dispatchDebugErrorNotification("exchangeManager is not initialized") + return + } + scope.launch { exchangeManager.update() } + } + is GlobalAction.ScanCard -> { + scope.launch { + val result = tangemSdkManager.scanProduct( + store.state.globalState.analyticsHandlers, + currenciesRepository, + action.additionalBlockchainsToDerive, + action.messageResId + ) + withMainContext { + store.dispatch(GlobalAction.ScanFailsCounter.ChooseBehavior(result)) + when (result) { + is CompletionResult.Success -> { + tangemSdkManager.changeDisplayedCardIdNumbersCount(result.data) + action.onSuccess?.invoke(result.data) + } is CompletionResult.Failure -> { - if (action.result.error is TangemSdkError.UserCancelled) { - store.dispatch(GlobalAction.ScanFailsCounter.Increment) - if (store.state.globalState.scanCardFailsCounter >= 2) { - store.dispatchDialogShow(AppDialog.ScanFailsDialog) - } - } else { - store.dispatch(GlobalAction.ScanFailsCounter.Reset) - } - } - } - } - is GlobalAction.RestoreAppCurrency -> { - store.dispatch(GlobalAction.RestoreAppCurrency.Success( - preferencesStorage.fiatCurrenciesPrefStorage.getAppCurrency() - )) - } - is GlobalAction.HideWarningMessage -> { - store.state.globalState.warningManager?.let { - if (it.hideWarning(action.warning)) { - if (WarningMessagesManager.isAlreadySignedHashesWarning(action.warning)) { - //TODO: No appropriate warningMessage identification. Make it better later - store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId) - } - - store.dispatch(WalletAction.Warnings.Update) - store.dispatch(SendAction.Warnings.Update) - } - } - } - - is GlobalAction.SendFeedback -> { - store.state.globalState.feedbackManager?.send(action.emailData) - } - is GlobalAction.UpdateWalletSignedHashes -> { - store.dispatch(WalletAction.Warnings.CheckRemainingSignatures(action.remainingSignatures)) - } - is GlobalAction.UpdateFeedbackInfo -> { - store.state.globalState.feedbackManager?.infoHolder - ?.setWalletsInfo(action.walletManagers) - } - is GlobalAction.InitCurrencyExchangeManager -> { - val config = appState()?.globalState?.configManager?.config - ifNotNull( - config?.onramperApiKey, - config?.moonPayApiKey, - config?.moonPayApiSecretKey, - ) { onramperKey, moonPayKey, moonPaySecretKey -> - scope.launch { - val onramper = OnramperService(onramperKey) - val moonPay = MoonPayService(moonPayKey, moonPaySecretKey) - val exchangeManager = CurrencyExchangeManager(onramper, moonPay) - exchangeManager.getStatus() - store.dispatchOnMain(GlobalAction.InitCurrencyExchangeManager.Success(exchangeManager)) - } - } - } - is GlobalAction.ScanCard -> { - scope.launch { - val result = tangemSdkManager.scanProduct( - store.state.globalState.analyticsHandlers, - currenciesRepository, - action.additionalBlockchainsToDerive, - action.messageResId - ) - withMainContext { - store.dispatch(GlobalAction.ScanFailsCounter.ChooseBehavior(result)) - when (result) { - is CompletionResult.Success -> { - tangemSdkManager.changeDisplayedCardIdNumbersCount(result.data) - action.onSuccess?.invoke(result.data) - } - is CompletionResult.Failure -> { - action.onFailure?.invoke(result.error) - } - } + action.onFailure?.invoke(result.error) } } } } - nextDispatch(action) + } + is GlobalAction.FetchUserCountry -> { + scope.launch { + val techService = store.state.domainNetworks.tangemTechService + when (val result = techService.userCountry()) { + is Result.Success -> { + store.dispatchOnMain( + GlobalAction.FetchUserCountry.Success( + countryCode = result.data.code.lowercase() + ) + ) + } + is Result.Failure -> { + store.dispatchOnMain( + GlobalAction.FetchUserCountry.Success( + countryCode = Locale.getDefault().country.lowercase() + ) + ) + } + } + } } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt index 67e54a6042..449d47ae10 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt @@ -70,12 +70,14 @@ fun globalReducer(action: Action, state: AppState): GlobalState { is GlobalAction.HideDialog -> { globalState.copy(dialog = null) } - is GlobalAction.InitCurrencyExchangeManager.Success -> { - globalState.copy(currencyExchangeManager = action.exchangeManager) + is GlobalAction.ExchangeManager.Init.Success -> { + globalState.copy(exchangeManager = action.exchangeManager) } is GlobalAction.SetIfCardVerifiedOnline -> globalState.copy(cardVerifiedOnline = action.verified) - + is GlobalAction.FetchUserCountry.Success -> globalState.copy( + userCountryCode = action.countryCode + ) else -> globalState } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt index 75504f3ef2..8588256411 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalState.kt @@ -25,9 +25,10 @@ data class GlobalState( val appCurrency: FiatCurrency = FiatCurrency.Default, val scanCardFailsCounter: Int = 0, val dialog: StateDialog? = null, - val currencyExchangeManager: CurrencyExchangeManager? = null, + val exchangeManager: CurrencyExchangeManager? = null, val resources: AndroidResources = AndroidResources(), val analyticsHandlers: AnalyticsHandler? = null, + val userCountryCode: String? = null, ) : StateType diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt index cb587f277b..bd99957d6e 100644 --- a/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/ConfigManager.kt @@ -11,8 +11,9 @@ import com.tangem.tap.domain.configurable.Loader data class Config( val coinMarketCapKey: String = "f6622117-c043-47a0-8975-9d673ce484de", val moonPayApiKey: String = "pk_test_kc90oYTANy7UQdBavDKGfL4K9l6VEPE", - val onramperApiKey: String = "pk_test_Ix2aCF3ej_5tcDKkBR7MChIvf5Nb0oPORPQ3Oal5G8I0", val moonPayApiSecretKey: String = "sk_test_V8w4M19LbDjjYOt170s0tGuvXAgyEb1C", + val mercuryoWidgetId: String = "", + val mercuryoSecret: String = "", val appsFlyerDevKey: String = "", val blockchainSdkConfig: BlockchainSdkConfig = BlockchainSdkConfig(), val isSendingToPayIdEnabled: Boolean = true, @@ -82,13 +83,15 @@ class ConfigManager( config = config.copy( coinMarketCapKey = values.coinMarketCapKey, moonPayApiKey = values.moonPayApiKey, - onramperApiKey = values.onramperApiKey, moonPayApiSecretKey = values.moonPayApiSecretKey, + mercuryoWidgetId = values.mercuryoWidgetId, + mercuryoSecret = values.mercuryoSecret, blockchainSdkConfig = BlockchainSdkConfig( blockchairApiKey = values.blockchairApiKey, blockchairAuthorizationToken = values.blockchairAuthorizationToken, blockcypherTokens = values.blockcypherTokens, - infuraProjectId = values.infuraProjectId + infuraProjectId = values.infuraProjectId, + tronGridApiKey = values.tronGridApiKey ), appsFlyerDevKey = values.appsFlyerDevKey, shopify = values.shopifyShop, @@ -96,8 +99,9 @@ class ConfigManager( defaultConfig = defaultConfig.copy( coinMarketCapKey = values.coinMarketCapKey, moonPayApiKey = values.moonPayApiKey, - onramperApiKey = values.onramperApiKey, moonPayApiSecretKey = values.moonPayApiSecretKey, + mercuryoWidgetId = values.mercuryoWidgetId, + mercuryoSecret = values.mercuryoSecret, blockchainSdkConfig = BlockchainSdkConfig( blockchairApiKey = values.blockchairApiKey, blockchairAuthorizationToken = values.blockchairAuthorizationToken, diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt b/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt index 41d1ea2022..542a006a8e 100644 --- a/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt +++ b/app/src/main/java/com/tangem/tap/domain/configurable/config/JsonModels.kt @@ -15,15 +15,17 @@ class FeatureModel( class ConfigValueModel( val coinMarketCapKey: String, + val mercuryoWidgetId: String, + val mercuryoSecret: String, val moonPayApiKey: String, - val onramperApiKey: String, val moonPayApiSecretKey: String, val blockchairApiKey: String?, val blockchairAuthorizationToken: String?, val blockcypherTokens: Set?, val infuraProjectId: String?, val appsFlyerDevKey: String, - val shopifyShop: ShopifyShop? + val shopifyShop: ShopifyShop?, + val tronGridApiKey: String, ) class ConfigModel(val features: FeatureModel?, val configValues: ConfigValueModel?) { diff --git a/app/src/main/java/com/tangem/tap/domain/extensions/CurrencyExchangeManager.kt b/app/src/main/java/com/tangem/tap/domain/extensions/CurrencyExchangeManager.kt deleted file mode 100644 index 23be9f4aaf..0000000000 --- a/app/src/main/java/com/tangem/tap/domain/extensions/CurrencyExchangeManager.kt +++ /dev/null @@ -1,57 +0,0 @@ -package com.tangem.tap.domain.extensions - -import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchain.common.Blockchain.Arbitrum -import com.tangem.tap.features.wallet.redux.Currency -import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager -import com.tangem.tap.network.exchangeServices.CurrencyExchangeStatus -import com.tangem.tap.store - -/** -[REDACTED_AUTHOR] - */ -fun CurrencyExchangeManager.buyIsAllowed(currency: Currency): Boolean { - return this.status?.buyIsAllowed(currency) ?: false -} - -fun CurrencyExchangeManager.sellIsAllowed(currency: Currency): Boolean { - return this.status?.sellIsAllowed(currency) ?: false -} - -fun CurrencyExchangeStatus.buyIsAllowed(currency: Currency): Boolean { - if (store.state.globalState.configManager?.config?.isTopUpEnabled == false) return false - if (currency.blockchain == Arbitrum) return false - if (!isBuyAllowed) return false - - //TODO: temporary, for the 3.32 release, unlock all buy button - return true - - return when (currency) { - is Currency.Blockchain -> { - val blockchain = currency.blockchain - when { - blockchain.isTestnet() -> blockchain.getTestnetTopUpUrl() != null - blockchain == Blockchain.Unknown -> false - else -> availableToBuy.contains(currency.currencySymbol) - } - } - is Currency.Token -> false - } -} - -fun CurrencyExchangeStatus.sellIsAllowed(currency: Currency): Boolean { - if (store.state.globalState.configManager?.config?.isTopUpEnabled == false) return false - if (!isSellAllowed) return false - - return when (currency) { - is Currency.Blockchain -> { - val blockchain = currency.blockchain - when { - blockchain.isTestnet() -> false - blockchain == Blockchain.Unknown || currency.blockchain == Blockchain.BSC -> false - else -> availableToSell.contains(currency.currencySymbol) - } - } - is Currency.Token -> false - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt b/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt index 7fe717e1a5..2a38c4e657 100644 --- a/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/demo/DemoHelper.kt @@ -104,56 +104,25 @@ class DemoConfig { ?: Amount(BigDecimal.ZERO, blockchain).copy() private fun getReleaseIds(): List { - return (releaseDemoCardIds + - releaseDemoCardIds_19042022 + - testDemoCardIds).distinct() + return (releaseDemoCardIds + testDemoCardIds).distinct() } - private val releaseDemoCardIds = mutableListOf( -// Tangem Wallet: - "AC01000000041100", - "AC01000000042462", - "AC01000000041647", - "AC01000000041621", - "AC01000000041217", + private val releaseDemoCardIds = mutableListOf( + // === Not from the Google Sheet table === + "FB10000000000196", // Note BTC + "FB20000000000186", // Note ETH + "FB30000000000176", // Wallet "AC01000000041225", - "AC01000000041209", - "AC01000000041092", "AC01000000041472", - "AC01000000041662", - "AC01000000045754", - "AC01000000045960", -// Tangem Note BTC: - "AB01000000046530", - "AB01000000046720", - "AB01000000046746", "AB01000000046498", - "AB01000000046753", "AB01000000049608", - "AB01000000046761", "AB01000000049574", - "AB01000000046605", - "AB01000000046571", "AB01000000046704", - "AB01000000046647", -// Tangem Note Ethereum: "AB02000000051000", - "AB02000000050986", - "AB02000000051026", - "AB02000000051042", - "AB02000000051091", - "AB02000000051083", - "AB02000000050960", - "AB02000000051034", "AB02000000050911", - "AB02000000051133", - "AB02000000051158", - "AB02000000051059", - ) - // https://tangem.slack.com/archives/GMXC6PP71/p1650360610759269 - private val releaseDemoCardIds_19042022 = listOf( - // Wallet + // === Mvideo === + // Wallet "AC01000000045754", "AC01000000041662", "AC01000000041647", @@ -186,7 +155,58 @@ class DemoConfig { "AC01000000013497", "AC01000000013836", "AC01000000013505", -// Note BTC + "AC03000000046693", + "AC03000000046685", + "AC03000000046677", + "AC03000000046669", + "AC03000000046651", + "AC03000000046644", + "AC03000000046636", + "AC03000000046628", + "AC03000000046610", + "AC03000000046602", + "AC03000000046594", + "AC03000000046586", + "AC03000000046578", + "AC03000000046560", + "AC03000000046552", + "AC03000000046545", + "AC03000000046537", + "AC03000000046529", + "AC03000000046511", + "AC03000000046800", + "AC03000000046792", + "AC03000000046784", + "AC03000000046776", + "AC03000000046768", + "AC03000000046750", + "AC03000000046743", + "AC03000000046735", + "AC03000000046727", + "AC03000000046446", + "AC03000000046438", + "AC03000000046412", + "AC03000000046388", + "AC03000000046370", + "AC03000000046354", + "AC03000000046347", + "AC03000000046339", + "AC03000000046321", + "AC03000000046172", + "AC03000000046396", + "AC03000000046404", + "AC03000000046701", + "AC03000000046420", + "AC03000000046719", + "AC03000000046503", + "AC03000000046495", + "AC03000000046487", + "AC03000000046362", + "AC03000000046479", + "AC03000000046461", + "AC03000000046453", + + // Note BTC "AB01000000059608", "AB01000000046647", "AB01000000046571", @@ -219,7 +239,58 @@ class DemoConfig { "AB01000000015782", "AB01000000022598", "AB01000000022580", -// Note ETH + "AB01000000005688", + "AB07000000005696", + "AB07000000005902", + "AB07000000005910", + "AB07000000005928", + "AB07000000005936", + "AB07000000005944", + "AB07000000005993", + "AB07000000005985", + "AB07000000005977", + "AB07000000005969", + "AB07000000005951", + "AB07000000005605", + "AB07000000005803", + "AB07000000005811", + "AB07000000005829", + "AB07000000005837", + "AB07000000005845", + "AB07000000005852", + "AB07000000005860", + "AB07000000005878", + "AB07000000005886", + "AB07000000005894", + "AB07000000005704", + "AB07000000005712", + "AB07000000005720", + "AB07000000005738", + "AB07000000005746", + "AB07000000005514", + "AB07000000005522", + "AB07000000005563", + "AB07000000005571", + "AB07000000005589", + "AB07000000005597", + "AB07000000005613", + "AB07000000005621", + "AB07000000005639", + "AB07000000005647", + "AB07000000005654", + "AB07000000005662", + "AB07000000005670", + "AB07000000005530", + "AB07000000005548", + "AB07000000005555", + "AB07000000005753", + "AB07000000005761", + "AB07000000005779", + "AB07000000005787", + "AB07000000005795", + "AB07000000005506", + + // Note ETH "AB02000000051083", "AB02000000051059", "AB02000000051158", @@ -252,6 +323,123 @@ class DemoConfig { "AB02000000020252", "AB02000000018652", "AB02000000018561", + "AB08000000009481", + "AB08000000009473", + "AB08000000009705", + "AB08000000009897", + "AB08000000009689", + "AB08000000009671", + "AB08000000009465", + "AB08000000009457", + "AB08000000009440", + "AB08000000009432", + "AB08000000009424", + "AB08000000009416", + "AB08000000009408", + "AB08000000009390", + "AB08000000009374", + "AB08000000009382", + "AB08000000009267", + "AB08000000009275", + "AB08000000009283", + "AB08000000009291", + "AB08000000009309", + "AB08000000009317", + "AB08000000009325", + "AB08000000009333", + "AB08000000009341", + "AB08000000009358", + "AB08000000009366", + "AB08000000009077", + "AB08000000009143", + "AB08000000009168", + "AB08000000009184", + "AB08000000009192", + "AB08000000009200", + "AB08000000009226", + "AB08000000009218", + "AB08000000009234", + "AB08000000009242", + "AB08000000008574", + "AB08000000009069", + "AB08000000008525", + "AB08000000009051", + "AB08000000009135", + "AB08000000009150", + "AB08000000009176", + "AB08000000009085", + "AB08000000009093", + "AB08000000009101", + "AB08000000009119", + "AB08000000009127", + "AB08000000009259", + + // === Technopark === + // Wallet + "AC01000000044120", + "AC01000000044997", + "AC01000000044989", + "AC01000000043494", + "AC01000000043486", + "AC01000000044187", + "AC01000000043148", + "AC01000000044013", + "AC01000000043973", + "AC01000000044815", + "AC01000000044807", + "AC01000000043809", + "AC01000000043833", + "AC01000000043460", + "AC01000000043064", + "AC01000000044138", + "AC01000000044500", + "AC01000000044492", + "AC01000000044260", + "AC01000000044278", + + // Note BTC + "AB01000000049864", + "AB01000000053239", + "AB01000000053056", + "AB01000000054237", + "AB01000000054245", + "AB01000000054211", + "AB01000000054229", + "AB01000000053189", + "AB01000000054195", + "AB01000000050797", + "AB01000000053833", + "AB01000000052124", + "AB01000000051605", + "AB01000000052223", + "AB01000000052207", + "AB01000000052199", + "AB01000000047785", + "AB01000000047850", + "AB01000000047868", + "AB01000000048288", + + // Note ETH + "AB02000000049715", + "AB02000000049848", + "AB02000000049814", + "AB02000000049863", + "AB02000000049871", + "AB02000000049855", + "AB02000000049285", + "AB02000000049277", + "AB02000000049558", + "AB02000000049889", + "AB02000000049988", + "AB02000000049707", + "AB02000000049699", + "AB02000000049897", + "AB02000000049905", + "AB02000000049913", + "AB02000000049251", + "AB02000000049533", + "AB02000000049541", + "AB02000000049830", ) private val testDemoCardIds = listOf( @@ -281,7 +469,6 @@ class DemoTransactionSender( } } - override suspend fun send(transactionData: TransactionData, signer: TransactionSigner): SimpleResult { val dataToSign = randomString(32).toByteArray() val signerResponse = signer.sign(dataToSign, walletManager.wallet.cardId, walletManager.wallet.publicKey) diff --git a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt index 2b26faeeba..c453cdadc4 100644 --- a/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/home/HomeFragment.kt @@ -38,7 +38,9 @@ class HomeFragment : Fragment(R.layout.fragment_home), StoreSubscriber = { dispatch, state -> +private val homeMiddleware: Middleware = { _, _ -> { next -> { action -> when (action) { is HomeAction.Init -> { store.dispatch(GlobalAction.RestoreAppCurrency) - store.dispatch(GlobalAction.InitCurrencyExchangeManager) + store.dispatch(GlobalAction.ExchangeManager.Init) store.dispatch(HomeAction.SetTermsOfUseState(preferencesStorage.wasDisclaimerAccepted())) + store.dispatch(GlobalAction.FetchUserCountry) } is HomeAction.ShouldScanCardOnResume -> { if (action.shouldScanCard) { @@ -55,8 +57,9 @@ private val homeMiddleware: Middleware = { dispatch, state -> // store.dispatch(NavigationAction.NavigateTo(AppScreen.AddCustomTokens)) } is HomeAction.GoToShop -> { - when (action.regionProvider.getRegion()?.toLowerCase()) { - "ru" -> store.dispatchOpenUrl(BUY_WALLET_URL) + when (action.userCountryCode) { + RUSSIA_COUNTRY_CODE, BELARUS_COUNTRY_CODE -> + store.dispatchOpenUrl(BUY_WALLET_URL) else -> store.dispatch(NavigationAction.NavigateTo(AppScreen.Shop)) } store.state.globalState.analyticsHandlers?.triggerEvent( diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt index f0873595ff..a539cefefd 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt @@ -83,7 +83,7 @@ class OnboardingManager( data class OnboardingWalletBalance( val value: BigDecimal = BigDecimal.ZERO, - val currency: Currency.Blockchain = Currency.Blockchain(Blockchain.Unknown, null), + val currency: Currency = Currency.Blockchain(Blockchain.Unknown, null), val hasIncomingTransaction: Boolean = false, val state: ProgressState, val error: TapError? = null, diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteState.kt index 77b12445f7..a9ebb0e757 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteState.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/note/redux/OnboardingNoteState.kt @@ -2,7 +2,6 @@ package com.tangem.tap.features.onboarding.products.note.redux import com.tangem.blockchain.common.WalletManager import com.tangem.tap.domain.TapError -import com.tangem.tap.domain.extensions.buyIsAllowed import com.tangem.tap.features.onboarding.OnboardingWalletBalance import com.tangem.tap.store import org.rekotlin.StateType @@ -26,8 +25,8 @@ data class OnboardingNoteState( val progress: Int get() = steps.indexOf(currentStep) - val isBuyAllowed: Boolean by ReadOnlyProperty { thisRef, property -> - store.state.globalState.currencyExchangeManager?.buyIsAllowed(walletBalance.currency) ?: false + val isBuyAllowed: Boolean by ReadOnlyProperty { _, _ -> + store.state.globalState.exchangeManager?.availableForBuy(walletBalance.currency) ?: false } } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt index fc24dbd461..19cc8ad307 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/twins/redux/TwinCardsState.kt @@ -3,7 +3,6 @@ package com.tangem.tap.features.onboarding.products.twins.redux import com.tangem.blockchain.common.WalletManager import com.tangem.domain.common.TwinCardNumber import com.tangem.tap.domain.TapError -import com.tangem.tap.domain.extensions.buyIsAllowed import com.tangem.tap.domain.twins.TwinCardsManager import com.tangem.tap.features.onboarding.OnboardingWalletBalance import com.tangem.tap.store @@ -56,8 +55,8 @@ data class TwinCardsState( val showAlert: Boolean get() = currentStep == TwinCardsStep.CreateSecondWallet || currentStep == TwinCardsStep.CreateThirdWallet - val isBuyAllowed: Boolean by ReadOnlyProperty { thisRef, property -> - store.state.globalState.currencyExchangeManager?.buyIsAllowed(walletBalance.currency) ?: false + val isBuyAllowed: Boolean by ReadOnlyProperty { _, _ -> + store.state.globalState.exchangeManager?.availableForBuy(walletBalance.currency) ?: false } } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/models/PendingTransaction.kt b/app/src/main/java/com/tangem/tap/features/wallet/models/PendingTransaction.kt index b96f055f59..698a4c9eb9 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/models/PendingTransaction.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/models/PendingTransaction.kt @@ -10,8 +10,8 @@ data class PendingTransaction( val type: PendingTransactionType, ) { val address: String? = when (type) { - PendingTransactionType.Incoming -> transactionData.destinationAddress - PendingTransactionType.Outgoing -> transactionData.sourceAddress + PendingTransactionType.Incoming -> transactionData.sourceAddress + PendingTransactionType.Outgoing -> transactionData.destinationAddress PendingTransactionType.Unknown -> null } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/models/WalletWarning.kt b/app/src/main/java/com/tangem/tap/features/wallet/models/WalletWarning.kt index f54300f3ec..0b4a56fa11 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/models/WalletWarning.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/models/WalletWarning.kt @@ -4,7 +4,6 @@ sealed class WalletWarning( val showingPosition: Int, ) { object TransactionInProgress : WalletWarning(10) - object SolanaTokensUnsupported : WalletWarning(20) data class BalanceNotEnoughForFee(val blockchainFullName: String) : WalletWarning(30) data class Rent(val walletRent: WalletRent) : WalletWarning(40) } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt index 769d28a8cd..c90af2af53 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletAction.kt @@ -145,6 +145,7 @@ sealed class WalletAction : Action { object SignedHashesMultiWalletDialog : DialogAction() object ChooseTradeActionDialog : DialogAction() data class ChooseCurrency(val amounts: List?) : DialogAction() + object RussianCardholdersWarningDialog : DialogAction() object Hide : DialogAction() } @@ -155,8 +156,10 @@ sealed class WalletAction : Action { object EmptyWallet : WalletAction() sealed class TradeCryptoAction : WalletAction() { - object Buy : TradeCryptoAction() object Sell : TradeCryptoAction() + data class Buy( + val checkUserLocation: Boolean = true, + ) : TradeCryptoAction() data class FinishSelling(val transactionId: String) : TradeCryptoAction() data class SendCrypto( val currencyId: String, diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt index b4502b9267..749f84b926 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/WalletState.kt @@ -1,10 +1,14 @@ package com.tangem.tap.features.wallet.redux import android.graphics.Bitmap -import com.tangem.blockchain.common.* +import com.tangem.blockchain.common.Amount +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.DerivationStyle +import com.tangem.blockchain.common.Token +import com.tangem.blockchain.common.Wallet +import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.address.AddressType import com.tangem.common.extensions.isZero -import com.tangem.domain.common.extensions.canHandleToken import com.tangem.domain.common.extensions.toCoinId import com.tangem.domain.features.addCustomToken.CustomCurrency import com.tangem.tap.common.entities.Button @@ -14,21 +18,25 @@ import com.tangem.tap.common.redux.StateDialog import com.tangem.tap.common.redux.global.CryptoCurrencyName import com.tangem.tap.common.toggleWidget.WidgetState import com.tangem.tap.domain.configurable.warningMessage.WarningMessage -import com.tangem.tap.domain.extensions.buyIsAllowed -import com.tangem.tap.domain.extensions.sellIsAllowed import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState import com.tangem.tap.features.tokens.redux.TokenWithBlockchain -import com.tangem.tap.features.wallet.models.* +import com.tangem.tap.features.wallet.models.PendingTransaction +import com.tangem.tap.features.wallet.models.TotalBalance +import com.tangem.tap.features.wallet.models.WalletRent +import com.tangem.tap.features.wallet.models.WalletWarning +import com.tangem.tap.features.wallet.models.hasPendingTransactions +import com.tangem.tap.features.wallet.models.hasSendableAmounts +import com.tangem.tap.features.wallet.models.isSendableAmount import com.tangem.tap.features.wallet.redux.reducers.calculateTotalFiatAmount import com.tangem.tap.features.wallet.redux.reducers.findProgressState import com.tangem.tap.features.wallet.ui.BalanceStatus import com.tangem.tap.features.wallet.ui.BalanceWidgetData import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager import com.tangem.tap.store -import org.rekotlin.StateType import java.math.BigDecimal import kotlin.properties.ReadOnlyProperty +import org.rekotlin.StateType data class WalletState( val state: ProgressState = ProgressState.Done, @@ -62,7 +70,7 @@ data class WalletState( val shouldShowDetails: Boolean = primaryWallet?.currencyData?.status != BalanceStatus.EmptyCard && - primaryWallet?.currencyData?.status != BalanceStatus.UnknownBlockchain + primaryWallet?.currencyData?.status != BalanceStatus.UnknownBlockchain val blockchains: List get() = wallets.mapNotNull { it.walletManager?.wallet?.blockchain } @@ -319,6 +327,7 @@ sealed interface WalletDialog : StateDialog { val currenciesList: List, val currentAppCurrency: FiatCurrency, ) : WalletDialog + object RussianCardholdersWarningDialog : WalletDialog } enum class ProgressState : WidgetState { Loading, Refreshing, Done, Error } @@ -363,18 +372,21 @@ data class Artwork( } data class TradeCryptoState( - val sellingAllowed: Boolean = false, - val buyingAllowed: Boolean = false, + val isAvailableToSell: () -> Boolean = { false }, + val isAvailableToBuy: () -> Boolean = { false }, ) { companion object { fun from( exchangeManager: CurrencyExchangeManager?, walletData: WalletData ): TradeCryptoState { - val status = exchangeManager ?: return walletData.tradeCryptoState + val exchanger = exchangeManager ?: return walletData.tradeCryptoState val currency = walletData.currency - return TradeCryptoState(status.sellIsAllowed(currency), status.buyIsAllowed(currency)) + return TradeCryptoState( + isAvailableToSell = { exchanger.availableForSell(currency) }, + isAvailableToBuy = { exchanger.availableForBuy(currency) }, + ) } } } @@ -404,19 +416,10 @@ data class WalletData( } fun assembleWarnings(): List { - val blockchain = currency.blockchain val walletWarnings = mutableListOf() if (currencyData.status == BalanceStatus.SameCurrencyTransactionInProgress) { walletWarnings.add(WalletWarning.TransactionInProgress) } - if (currency.isBlockchain()) { - if (blockchain == Blockchain.Solana || blockchain == Blockchain.SolanaTestnet) { - val card = store.state.globalState.scanResponse?.card - if (card?.canHandleToken(blockchain) == false) { - walletWarnings.add(WalletWarning.SolanaTokensUnsupported) - } - } - } if (walletRent != null) { walletWarnings.add(WalletWarning.Rent(walletRent)) } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt index 3e4210fe42..ebbaae8538 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt @@ -8,12 +8,13 @@ import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.navigation.AppScreen import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.features.demo.DemoHelper +import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE import com.tangem.tap.features.send.redux.PrepareSendScreen import com.tangem.tap.features.send.redux.SendAction import com.tangem.tap.features.wallet.redux.Currency import com.tangem.tap.features.wallet.redux.WalletAction import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager -import com.tangem.tap.network.exchangeServices.buyErc20Tokens +import com.tangem.tap.network.exchangeServices.buyErc20TestnetTokens import com.tangem.tap.scope import com.tangem.tap.store import kotlinx.coroutines.launch @@ -24,51 +25,70 @@ class TradeCryptoMiddleware { if (DemoHelper.tryHandle(state, action)) return when (action) { - is WalletAction.TradeCryptoAction.Buy -> startExchange(action) - is WalletAction.TradeCryptoAction.Sell -> startExchange(action) + is WalletAction.TradeCryptoAction.Buy -> proceedBuyAction(state, action) + is WalletAction.TradeCryptoAction.Sell -> proceedSellAction() is WalletAction.TradeCryptoAction.SendCrypto -> preconfigureAndOpenSendScreen(action) is WalletAction.TradeCryptoAction.FinishSelling -> openReceiptUrl(action.transactionId) } } - private fun startExchange(action: WalletAction.TradeCryptoAction) { - val selectedWalletData = store.state.walletState.getSelectedWalletData() - val exchangeManager = store.state.globalState.currencyExchangeManager ?: return - val addresses = selectedWalletData?.walletAddresses ?: return - if (addresses.list.isEmpty()) return - val appCurrency = store.state.globalState.appCurrency - - val defaultAddress = addresses.list[0].address - val currency = selectedWalletData.currency - val currencySymbol = selectedWalletData.currency.currencySymbol - - val exchangeAction = if (action is WalletAction.TradeCryptoAction.Buy) { - CurrencyExchangeManager.Action.Buy - } else { - CurrencyExchangeManager.Action.Sell + private fun proceedBuyAction( + state: () -> AppState?, + action: WalletAction.TradeCryptoAction.Buy, + ) { + if (action.checkUserLocation && state()?.globalState?.userCountryCode == RUSSIA_COUNTRY_CODE) { + store.dispatchOnMain( + WalletAction.DialogAction.RussianCardholdersWarningDialog + ) + return } - if (exchangeAction == CurrencyExchangeManager.Action.Buy && - currency is Currency.Token && currency.blockchain.isTestnet() - ) { + val selectedWalletData = store.state.walletState.getSelectedWalletData() ?: return + val exchangeManager = store.state.globalState.exchangeManager ?: return + val appCurrency = store.state.globalState.appCurrency + + val addresses = selectedWalletData.walletAddresses?.list.orEmpty() + if (addresses.isEmpty()) return + + val currency = selectedWalletData.currency + + if (currency is Currency.Token && currency.blockchain.isTestnet()) { val walletManager = store.state.walletState.getWalletManager(currency) if (walletManager !is EthereumWalletManager) { - store.dispatchDebugErrorNotification("Testnet tokens available only for the ETH") + store.dispatchDebugErrorNotification("Testnet tokens available only for the Ethereum") return } - scope.launch { exchangeManager.buyErc20Tokens(walletManager, currency.token) } + scope.launch { exchangeManager.buyErc20TestnetTokens(walletManager, currency.token) } return } exchangeManager.getUrl( - action = exchangeAction, + action = CurrencyExchangeManager.Action.Buy, blockchain = currency.blockchain, - cryptoCurrencyName = currencySymbol, + cryptoCurrencyName = currency.currencySymbol, fiatCurrencyName = appCurrency.code, - walletAddress = defaultAddress + walletAddress = addresses[0].address )?.let { store.dispatchOnMain(NavigationAction.OpenUrl(it)) } + } + private fun proceedSellAction() { + val selectedWalletData = store.state.walletState.getSelectedWalletData() ?: return + val exchangeManager = store.state.globalState.exchangeManager ?: return + val appCurrency = store.state.globalState.appCurrency + + val addresses = selectedWalletData.walletAddresses?.list.orEmpty() + if (addresses.isEmpty()) return + + val currency = selectedWalletData.currency + + exchangeManager.getUrl( + action = CurrencyExchangeManager.Action.Sell, + blockchain = currency.blockchain, + cryptoCurrencyName = currency.currencySymbol, + fiatCurrencyName = appCurrency.code, + walletAddress = addresses[0].address + )?.let { store.dispatchOnMain(NavigationAction.OpenUrl(it)) } } private fun preconfigureAndOpenSendScreen(action: WalletAction.TradeCryptoAction.SendCrypto) { @@ -89,7 +109,7 @@ class TradeCryptoMiddleware { } private fun openReceiptUrl(transactionId: String) { - val exchangeManager = store.state.globalState.currencyExchangeManager ?: return + val exchangeManager = store.state.globalState.exchangeManager ?: return store.dispatchOnMain(NavigationAction.PopBackTo()) exchangeManager.getSellCryptoReceiptUrl(CurrencyExchangeManager.Action.Sell, transactionId)?.let { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt index e495c55c94..e760c446f2 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WalletDialogsMiddleware.kt @@ -27,10 +27,13 @@ class WalletDialogsMiddleware { is WalletAction.DialogAction.ChooseCurrency -> { store.dispatchDialogShow( WalletDialog.SelectAmountToSendDialog( - amounts = action.amounts + amounts = action.amounts ) ) } + is WalletAction.DialogAction.RussianCardholdersWarningDialog -> { + store.dispatchDialogShow(WalletDialog.RussianCardholdersWarningDialog) + } is WalletAction.DialogAction.Hide -> { store.dispatchDialogHide() } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt index 2e03a3f88d..50f4a1cec0 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/OnWalletLoadedReducer.kt @@ -37,7 +37,7 @@ class OnWalletLoadedReducer { val walletData = walletState.getWalletData(blockchainNetwork) ?: return walletState val fiatCurrency = store.state.globalState.appCurrency - val exchangeManager = store.state.globalState.currencyExchangeManager + val exchangeManager = store.state.globalState.exchangeManager val coinAmountValue = wallet.amounts[AmountType.Coin]?.value val formattedAmount = coinAmountValue?.toFormattedCurrencyString( @@ -117,7 +117,7 @@ class OnWalletLoadedReducer { if (wallet.blockchain != walletState.primaryBlockchain) return walletState val fiatCurrencyName = store.state.globalState.appCurrency.code - val exchangeManager = store.state.globalState.currencyExchangeManager + val exchangeManager = store.state.globalState.exchangeManager val token = wallet.getFirstToken() val tokenData = if (token != null) { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt index 04fec99524..3e39e7ed42 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/reducers/WalletReducer.kt @@ -6,34 +6,19 @@ import com.tangem.blockchain.common.Wallet import com.tangem.common.extensions.mapNotNullValues import com.tangem.domain.common.TwinCardNumber import com.tangem.tap.common.entities.FiatCurrency -import com.tangem.tap.common.extensions.toFiatRateString -import com.tangem.tap.common.extensions.toFiatString -import com.tangem.tap.common.extensions.toFiatValue -import com.tangem.tap.common.extensions.toFormattedCurrencyString -import com.tangem.tap.common.extensions.toFormattedFiatValue +import com.tangem.tap.common.extensions.* import com.tangem.tap.common.redux.AppState import com.tangem.tap.domain.TapError import com.tangem.tap.domain.extensions.getArtworkUrl import com.tangem.tap.domain.getFirstToken import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.wallet.models.WalletRent -import com.tangem.tap.features.wallet.redux.AddressData -import com.tangem.tap.features.wallet.redux.Artwork -import com.tangem.tap.features.wallet.redux.Currency -import com.tangem.tap.features.wallet.redux.ErrorType -import com.tangem.tap.features.wallet.redux.ProgressState -import com.tangem.tap.features.wallet.redux.TradeCryptoState -import com.tangem.tap.features.wallet.redux.WalletAction -import com.tangem.tap.features.wallet.redux.WalletAddresses -import com.tangem.tap.features.wallet.redux.WalletData -import com.tangem.tap.features.wallet.redux.WalletMainButton -import com.tangem.tap.features.wallet.redux.WalletState -import com.tangem.tap.features.wallet.redux.WalletStore +import com.tangem.tap.features.wallet.redux.* import com.tangem.tap.features.wallet.ui.BalanceStatus import com.tangem.tap.features.wallet.ui.BalanceWidgetData import com.tangem.tap.store -import java.math.BigDecimal import org.rekotlin.Action +import java.math.BigDecimal class WalletReducer { companion object { @@ -49,7 +34,7 @@ private fun internalReduce(action: Action, state: AppState): WalletState { if (action !is WalletAction) return state.walletState - val exchangeManager = store.state.globalState.currencyExchangeManager + val exchangeManager = store.state.globalState.exchangeManager var newState = state.walletState when (action) { diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt index 997c042f15..1cf3a038b2 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletDetailsFragment.kt @@ -1,11 +1,7 @@ package com.tangem.tap.features.wallet.ui import android.os.Bundle -import android.view.Menu -import android.view.MenuInflater -import android.view.MenuItem -import android.view.View -import android.view.ViewGroup +import android.view.* import android.widget.TextView import androidx.activity.OnBackPressedCallback import androidx.annotation.ColorRes @@ -18,25 +14,13 @@ import by.kirich1409.viewbindingdelegate.viewBinding import com.tangem.tangem_sdk_new.extensions.dpToPx import com.tangem.tap.common.SnackbarHandler import com.tangem.tap.common.TestActions -import com.tangem.tap.common.extensions.appendIfNotNull -import com.tangem.tap.common.extensions.beginDelayedTransition -import com.tangem.tap.common.extensions.fitChipsByGroupWidth -import com.tangem.tap.common.extensions.getColor -import com.tangem.tap.common.extensions.getString -import com.tangem.tap.common.extensions.hide -import com.tangem.tap.common.extensions.show -import com.tangem.tap.common.extensions.toQrCode +import com.tangem.tap.common.extensions.* import com.tangem.tap.common.recyclerView.SpaceItemDecoration import com.tangem.tap.common.redux.navigation.NavigationAction import com.tangem.tap.domain.tokens.models.BlockchainNetwork import com.tangem.tap.features.onboarding.getQRReceiveMessage import com.tangem.tap.features.wallet.models.PendingTransaction -import com.tangem.tap.features.wallet.redux.Currency -import com.tangem.tap.features.wallet.redux.ErrorType -import com.tangem.tap.features.wallet.redux.ProgressState -import com.tangem.tap.features.wallet.redux.WalletAction -import com.tangem.tap.features.wallet.redux.WalletData -import com.tangem.tap.features.wallet.redux.WalletState +import com.tangem.tap.features.wallet.redux.* import com.tangem.tap.features.wallet.redux.WalletState.Companion.UNKNOWN_AMOUNT_SIGN import com.tangem.tap.features.wallet.ui.adapters.PendingTransactionsAdapter import com.tangem.tap.features.wallet.ui.adapters.WalletDetailWarningMessagesAdapter @@ -107,7 +91,7 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), private fun setupButtons() = with(binding) { rowButtons.onBuyClick = { - store.dispatch(WalletAction.TradeCryptoAction.Buy) + store.dispatch(WalletAction.TradeCryptoAction.Buy()) } rowButtons.onSellClick = { store.dispatch(WalletAction.TradeCryptoAction.Sell) @@ -200,8 +184,8 @@ class WalletDetailsFragment : Fragment(R.layout.fragment_wallet_details), } rowButtons.updateButtonsVisibility( - buyAllowed = selectedWallet.tradeCryptoState.buyingAllowed, - sellAllowed = selectedWallet.tradeCryptoState.sellingAllowed, + buyAllowed = selectedWallet.tradeCryptoState.isAvailableToBuy(), + sellAllowed = selectedWallet.tradeCryptoState.isAvailableToSell(), sendAllowed = selectedWallet.mainButton.enabled, ) } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletWarningConverter.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletWarningConverter.kt index 6137f344e3..1e9e127e59 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletWarningConverter.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletWarningConverter.kt @@ -21,9 +21,6 @@ class WalletWarningConverter( message.blockchainFullName, message.blockchainFullName ) } - WalletWarning.SolanaTokensUnsupported -> { - context.getString(R.string.warning_token_send_unsupported_message) - } WalletWarning.TransactionInProgress -> { context.getString(R.string.wallet_pending_transaction_warning) } diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ChooseTradeActionBottomSheetDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ChooseTradeActionBottomSheetDialog.kt index 1c65315d3a..3fc5905990 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ChooseTradeActionBottomSheetDialog.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/ChooseTradeActionBottomSheetDialog.kt @@ -27,12 +27,12 @@ class ChooseTradeActionBottomSheetDialog(context: Context) : BottomSheetDialog(c } binding!!.dialogBtnBuy.setOnClickListener { - store.dispatch(WalletAction.TradeCryptoAction.Buy) dismiss() + store.dispatch(WalletAction.TradeCryptoAction.Buy()) } binding!!.dialogBtnSell.setOnClickListener { - store.dispatch(WalletAction.TradeCryptoAction.Sell) dismiss() + store.dispatch(WalletAction.TradeCryptoAction.Sell) } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/RussianCardholdersWarningBottomSheetDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/RussianCardholdersWarningBottomSheetDialog.kt new file mode 100644 index 0000000000..a41dce9228 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/RussianCardholdersWarningBottomSheetDialog.kt @@ -0,0 +1,44 @@ +package com.tangem.tap.features.wallet.ui.dialogs + +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.tangem.tap.common.extensions.dispatchDialogHide +import com.tangem.tap.common.redux.navigation.NavigationAction +import com.tangem.tap.features.wallet.redux.WalletAction +import com.tangem.tap.store +import com.tangem.wallet.databinding.DialogRussiansCardholdersWarningBinding + +class RussianCardholdersWarningBottomSheetDialog(context: Context) : BottomSheetDialog(context) { + + private var binding: DialogRussiansCardholdersWarningBinding? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = DialogRussiansCardholdersWarningBinding + .inflate(LayoutInflater.from(context)) + .also { setContentView(it.root) } + } + + override fun show() { + super.show() + setOnDismissListener { + binding = null + store.dispatchDialogHide() + } + + binding?.btnYes?.setOnClickListener { + store.dispatch(WalletAction.TradeCryptoAction.Buy(checkUserLocation = false)) + dismiss() + } + binding?.btnNo?.setOnClickListener { + store.dispatch(NavigationAction.OpenUrl(INSTRUCTION_URL)) + dismiss() + } + } + + companion object { + private const val INSTRUCTION_URL = "https://tangem.com/howtobuy.html" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconRequest.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconRequest.kt index 99051bc72d..0561426bad 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconRequest.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/images/CurrencyIconRequest.kt @@ -1,12 +1,9 @@ package com.tangem.tap.features.wallet.ui.images -import android.graphics.PorterDuff -import android.graphics.PorterDuffColorFilter import android.widget.ImageView import android.widget.TextView import androidx.constraintlayout.utils.widget.ImageFilterView import coil.imageLoader -import coil.load import coil.request.ImageRequest import coil.transform.RoundedCornersTransformation import com.tangem.blockchain.common.Blockchain @@ -29,67 +26,117 @@ fun loadCurrencyIcon( token: Token?, blockchain: Blockchain, ) { - when { - token == null -> currencyImageView.loadIcon( - iconUrl = getIconUrl(blockchain.toNetworkId()), - placeholderRes = blockchain.getRoundIconRes(), + CurrencyIconLoader( + currencyImageView = currencyImageView, + currencyTextView = currencyTextView, + token = token, + blockchain = blockchain + ) + .load() +} + +private class CurrencyIconLoader( + private val currencyImageView: ImageFilterView, + private val currencyTextView: TextView, + private val token: Token?, + private val blockchain: Blockchain, +) { + fun load() { + when { + token == null && blockchain.isTestnet() -> loadTestnetBlockchainIcon() + token == null -> loadBlockchainIcon() + blockchain.isTestnet() -> loadTestnetTokenIcon() + else -> loadTokenIcon() + } + } + + private fun loadBlockchainIcon() { + loadBlockchainIconBase( onStart = { - if (blockchain.isTestnet()) { - currencyImageView.saturation = 0f - } else { - currencyImageView.colorFilter = null - } + currencyImageView.colorFilter = null } ) - token.symbol == QCX -> currencyImageView.load(R.drawable.ic_qcx) - token.symbol == VOYR -> currencyImageView.load(R.drawable.ic_voyr) - else -> currencyImageView.loadIcon( - iconUrl = getTokenIconUrl(token, blockchain), + } + + private fun loadTestnetBlockchainIcon() { + loadBlockchainIconBase( + onStart = { + currencyImageView.saturation = 0f + } + ) + } + + private fun loadTokenIcon() { + loadTokenIconBase( + onStart = { + currencyImageView.setColorFilter(it.getColor()) + }, + onSuccess = { + currencyImageView.colorFilter = null + } + ) + } + + private fun loadTestnetTokenIcon() { + loadTokenIconBase( + onStart = { + currencyImageView.saturation = 0f + } + ) + } + + private inline fun loadBlockchainIconBase( + crossinline onStart: (Blockchain) -> Unit = {}, + crossinline onSuccess: (Blockchain) -> Unit = {}, + crossinline onError: (Blockchain) -> Unit = {}, + ) { + currencyImageView.loadIcon( + data = getIconUrl(blockchain.toNetworkId()), + placeholderRes = blockchain.getRoundIconRes(), + onStart = { onStart(blockchain) }, + onSuccess = { onSuccess(blockchain) }, + onError = { onError(blockchain) }, + ) + } + + private inline fun loadTokenIconBase( + crossinline onStart: (Token) -> Unit = {}, + crossinline onSuccess: (Token) -> Unit = {}, + crossinline onError: (Token) -> Unit = {}, + ) { + if (token == null) return + + currencyImageView.loadIcon( + data = getTokenIcon(token, blockchain), placeholderRes = R.drawable.shape_circle, onStart = { currencyTextView.text = token.symbol.take(1) currencyTextView.setTextColor(token.getTextColor()) - - if (blockchain.isTestnet()) { - currencyImageView.saturation = 0f - } - }, - onError = { - currencyImageView.colorFilter = PorterDuffColorFilter( - /* color = */ - token.getColor(), - /* mode = */ - PorterDuff.Mode.SRC_ATOP, - ) + onStart(token) }, onSuccess = { - if (!blockchain.isTestnet()) { - currencyImageView.colorFilter = null - } - } + currencyTextView.text = null + onSuccess(token) + }, + onError = { onError(token) }, ) } } private inline fun ImageView.loadIcon( - iconUrl: String?, + data: Any?, placeholderRes: Int, crossinline onStart: () -> Unit = {}, crossinline onSuccess: () -> Unit = {}, crossinline onError: () -> Unit = {}, ) { ImageRequest.Builder(context) - .data(iconUrl) + .data(data) .placeholder(placeholderRes) .error(placeholderRes) .fallback(placeholderRes) .transformations( - RoundedCornersTransformation( - topLeft = 32f, - topRight = 32f, - bottomLeft = 32f, - bottomRight = 32f - ) + RoundedCornersTransformation(radius = 8f) ) .listener( onStart = { onStart() }, @@ -101,9 +148,15 @@ private inline fun ImageView.loadIcon( .also(context.imageLoader::enqueue) } -private fun getTokenIconUrl(token: Token, blockchain: Blockchain): String? { - return token.id?.let(::getIconUrl) - ?: token.getCustomIconUrl() - ?: IconsUtil.getTokenIconUri(blockchain, token) - ?.toString() +private fun getTokenIcon(token: Token, blockchain: Blockchain): Any? { + return when (token.symbol) { + QCX -> R.drawable.ic_qcx + VOYR -> R.drawable.ic_voyr + else -> { + token.id?.let(::getIconUrl) + ?: token.getCustomIconUrl() + ?: IconsUtil.getTokenIconUri(blockchain, token) + ?.toString() + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/images/RoundedCornersCropTransformation.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/images/RoundedCornersCropTransformation.kt new file mode 100644 index 0000000000..7a9b0a3015 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/images/RoundedCornersCropTransformation.kt @@ -0,0 +1,58 @@ +package com.tangem.tap.features.wallet.ui.images + +import android.graphics.Bitmap +import android.graphics.BitmapShader +import android.graphics.Matrix +import android.graphics.Paint +import android.graphics.RectF +import android.graphics.Shader +import androidx.annotation.Px +import androidx.core.graphics.applyCanvas +import coil.size.Size +import coil.size.pxOrElse +import coil.transform.Transformation + +class RoundedCornersCropTransformation( + @Px val radiusPx: Float, +) : Transformation { + override val cacheKey: String = "${javaClass.name}-$radiusPx" + + override suspend fun transform(input: Bitmap, size: Size): Bitmap { + val outputWidth = size.width.pxOrElse { Int.MAX_VALUE } + val outputHeight = size.height.pxOrElse { Int.MAX_VALUE } + val outputSize = minOf(outputWidth, outputHeight) + val output = Bitmap.createBitmap(outputSize, outputSize, Bitmap.Config.ARGB_8888) + + val rect = RectF( + /* left = */ 0f, + /* top = */ 0f, + /* right = */ outputWidth.toFloat(), + /* bottom = */ outputHeight.toFloat() + ) + val matrix = Matrix().apply { + setTranslate( + /* dx = */ (outputWidth - input.width) * .5f, + /* dy = */ (outputHeight - input.height) * .5f + ) + } + val bitmapShader = BitmapShader( + /* bitmap = */ input, + /* tileX = */ Shader.TileMode.DECAL, + /* tileY = */ Shader.TileMode.DECAL + ).apply { + setLocalMatrix(matrix) + } + val paint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.FILTER_BITMAP_FLAG).apply { + shader = bitmapShader + } + + return output.applyCanvas { + drawRoundRect( + /* rect = */ rect, + /* rx = */ radiusPx, + /* ry = */ radiusPx, + /* paint = */ paint + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt index b7e0a307a5..7c431c33b7 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/wallet/SingleWalletView.kt @@ -11,12 +11,7 @@ import com.tangem.tap.common.extensions.hide import com.tangem.tap.common.extensions.show import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState import com.tangem.tap.features.wallet.models.PendingTransaction -import com.tangem.tap.features.wallet.redux.Currency -import com.tangem.tap.features.wallet.redux.TradeCryptoState -import com.tangem.tap.features.wallet.redux.WalletAction -import com.tangem.tap.features.wallet.redux.WalletData -import com.tangem.tap.features.wallet.redux.WalletMainButton -import com.tangem.tap.features.wallet.redux.WalletState +import com.tangem.tap.features.wallet.redux.* import com.tangem.tap.features.wallet.ui.BalanceWidget import com.tangem.tap.features.wallet.ui.MultipleAddressUiHelper import com.tangem.tap.features.wallet.ui.WalletFragment @@ -122,9 +117,8 @@ class SingleWalletView : WalletView { setupButtonsType(state, binding) - val btnConfirm = if (state.tradeCryptoState.sellingAllowed || - state.tradeCryptoState.buyingAllowed - ) { + val tradeState = state.tradeCryptoState + val btnConfirm = if (tradeState.isAvailableToSell() || tradeState.isAvailableToBuy()) { lButtonsShort.btnConfirm } else { lButtonsLong.btnConfirmLong @@ -152,10 +146,10 @@ class SingleWalletView : WalletView { } private fun setupTradeButton(binding: FragmentWalletBinding, tradeCryptoState: TradeCryptoState) { - val allowedToBuy = tradeCryptoState.buyingAllowed - val allowedToSell = tradeCryptoState.sellingAllowed + val allowedToBuy = tradeCryptoState.isAvailableToBuy() + val allowedToSell = tradeCryptoState.isAvailableToSell() val action = when { - allowedToBuy && !allowedToSell -> WalletAction.TradeCryptoAction.Buy + allowedToBuy && !allowedToSell -> WalletAction.TradeCryptoAction.Buy() !allowedToBuy && allowedToSell -> WalletAction.TradeCryptoAction.Sell allowedToBuy && allowedToSell -> WalletAction.DialogAction.ChooseTradeActionDialog else -> null @@ -180,9 +174,7 @@ class SingleWalletView : WalletView { } private fun setupButtonsType(state: WalletData, binding: FragmentWalletBinding) = with(binding) { - if (state.tradeCryptoState.sellingAllowed || - state.tradeCryptoState.buyingAllowed - ) { + if (state.tradeCryptoState.isAvailableToSell() || state.tradeCryptoState.isAvailableToBuy()) { lButtonsLong.root.hide() lButtonsShort.root.show() } else { diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt index 7a89edc4b0..745ee01053 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/CurrencyExchangeManager.kt @@ -11,6 +11,7 @@ import com.tangem.tap.common.extensions.safeUpdate import com.tangem.tap.common.redux.global.CryptoCurrencyName import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.domain.TangemSigner +import com.tangem.tap.features.wallet.redux.Currency import com.tangem.tap.store import com.tangem.tap.tangemSdk import java.math.BigDecimal @@ -18,57 +19,20 @@ import java.math.BigDecimal /** [REDACTED_AUTHOR] */ -interface ExchangeService { - suspend fun isBuyAllowed(): Boolean - suspend fun availableToBuy(): List - suspend fun isSellAllowed(): Boolean - suspend fun availableToSell(): List -} - -interface ExchangeUrlBuilder { - fun getUrl( - action: CurrencyExchangeManager.Action, - blockchain: Blockchain, - cryptoCurrencyName: CryptoCurrencyName, - fiatCurrencyName: String, - walletAddress: String, - ): String? - - fun getSellCryptoReceiptUrl(action: CurrencyExchangeManager.Action, transactionId: String): String? - - companion object { - const val SCHEME = "https" - const val URL_SELL = "sell.moonpay.com" - const val SUCCESS_URL = "tangem://success.tangem.com" - } -} - class CurrencyExchangeManager( - private val onramperService: ExchangeService, - private val moonPayService: ExchangeService, + private val buyService: ExchangeService, + private val sellService: ExchangeService, ) : ExchangeService, ExchangeUrlBuilder { - var status: CurrencyExchangeStatus? = null - private set - - suspend fun getStatus(): CurrencyExchangeStatus { - val isBuyAllowed = isBuyAllowed() - val isSellAllowed = isSellAllowed() - val availableToBuy = availableToBuy() - val availableToSell = availableToSell() - status = CurrencyExchangeStatus( - isBuyAllowed, - isSellAllowed, - availableToBuy, - availableToSell, - ) - return status!! + override suspend fun update() { + buyService.update() + sellService.update() } - override suspend fun isBuyAllowed(): Boolean = onramperService.isBuyAllowed() - override suspend fun availableToBuy(): List = onramperService.availableToBuy() - override suspend fun isSellAllowed(): Boolean = moonPayService.isSellAllowed() - override suspend fun availableToSell(): List = moonPayService.availableToSell() + override fun isBuyAllowed(): Boolean = buyService.isBuyAllowed() + override fun isSellAllowed(): Boolean = sellService.isSellAllowed() + override fun availableForBuy(currency: Currency): Boolean = buyService.availableForBuy(currency) + override fun availableForSell(currency: Currency): Boolean = sellService.availableForSell(currency) override fun getUrl( action: Action, @@ -96,22 +60,15 @@ class CurrencyExchangeManager( private fun getExchangeUrlBuilder(action: Action): ExchangeUrlBuilder { return when (action) { - Action.Buy -> onramperService - Action.Sell -> moonPayService + Action.Buy -> buyService + Action.Sell -> sellService } as ExchangeUrlBuilder } enum class Action { Buy, Sell } } -data class CurrencyExchangeStatus( - val isBuyAllowed: Boolean, - val isSellAllowed: Boolean, - val availableToBuy: List, - val availableToSell: List, -) - -suspend fun CurrencyExchangeManager.buyErc20Tokens(walletManager: EthereumWalletManager, token: Token) { +suspend fun CurrencyExchangeManager.buyErc20TestnetTokens(walletManager: EthereumWalletManager, token: Token) { walletManager.safeUpdate() val amountToSend = Amount(walletManager.wallet.blockchain) diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt new file mode 100644 index 0000000000..e08e007029 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/ExchangeService.kt @@ -0,0 +1,29 @@ +package com.tangem.tap.network.exchangeServices + +import com.tangem.blockchain.common.Blockchain +import com.tangem.tap.features.wallet.redux.Currency + +interface ExchangeService { + suspend fun update() + fun isBuyAllowed(): Boolean + fun isSellAllowed(): Boolean + fun availableForBuy(currency: Currency):Boolean + fun availableForSell(currency: Currency):Boolean +} + +interface ExchangeUrlBuilder { + fun getUrl( + action: CurrencyExchangeManager.Action, + blockchain: Blockchain, + cryptoCurrencyName: String, + fiatCurrencyName: String, + walletAddress: String, + ): String? + + fun getSellCryptoReceiptUrl(action: CurrencyExchangeManager.Action, transactionId: String): String? + + companion object { + const val SCHEME = "https" + const val SUCCESS_URL = "tangem://success.tangem.com" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoApi.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoApi.kt new file mode 100644 index 0000000000..45494e0114 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoApi.kt @@ -0,0 +1,57 @@ +package com.tangem.tap.network.exchangeServices.mercuryo + +import com.squareup.moshi.Json +import retrofit2.http.GET +import retrofit2.http.Path + +/** +[REDACTED_AUTHOR] + */ + + + +private val CurrenciesUrl = "https://api.mercuryo.io/v1.6/lib/currencies" + + + +interface MercuryoApi { + + @GET("{apiVersion}/lib/currencies") + suspend fun currencies( + @Path("apiVersion") apiVersion: String, + ): MercuryoCurrenciesResponse + + companion object { + const val BASE_URL = "https://api.mercuryo.io/" + const val API_VERSION = "v1.6" + } +} + +data class MercuryoCurrenciesResponse( + val status: Int, + val data: Data, +) { + data class Data( + val fiat: List, + val crypto: List, + val config: Config + ) + + data class Config( + val base: Map, + @Json(name = "has_withdrawal_fee") + val hasWithdrawalFee: Map, + @Json(name = "display_options") + val displayOptions: Map, + val icons: Map, + ) + + data class DisplayOption( + @Json(name = "fullname") + val fullName: String, + @Json(name = "total_digits") + val totalDigits: Int, + @Json(name = "display_digits") + val displayDigits: Int, + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt new file mode 100644 index 0000000000..2d446490ec --- /dev/null +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt @@ -0,0 +1,135 @@ +package com.tangem.tap.network.exchangeServices.mercuryo + +import android.net.Uri +import com.tangem.blockchain.common.Blockchain +import com.tangem.common.extensions.calculateSha512 +import com.tangem.common.extensions.toHexString +import com.tangem.common.services.Result +import com.tangem.common.services.performRequest +import com.tangem.network.common.createRetrofitInstance +import com.tangem.tap.common.redux.global.CryptoCurrencyName +import com.tangem.tap.features.wallet.redux.Currency +import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager +import com.tangem.tap.network.exchangeServices.ExchangeService +import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder + +/** +[REDACTED_AUTHOR] + */ +class MercuryoService( + private val apiVersion: String, + private val mercuryoWidgetId: String, + private val secret: String, +) : ExchangeService, ExchangeUrlBuilder { + + private val api: MercuryoApi = createRetrofitInstance(MercuryoApi.BASE_URL) + .create(MercuryoApi::class.java) + + private val blockchainsAvailableToBuy = mutableListOf() + private val tokensAvailableToBy = mutableMapOf>() + + override suspend fun update() { + when (val result = performRequest { api.currencies(apiVersion) }) { + is Result.Success -> { + val response = result.data + if (response.status == 200) { + // all currencies which can be bought + val currenciesAvailableToBy = response.data.crypto + // tokens which can be bought only from specific blockchain network + val supportedTokensWithNetwork = response.data.config.base + + currenciesAvailableToBy.forEach { currencyName -> + val blockchain = blockchainFromCurrencyName(currencyName) + if (blockchain == null) { + // suppose its a token + supportedTokensWithNetwork[currencyName]?.let { + blockchainFromCurrencyName(it) + }?.let { blockchainNetwork -> + val supportedInBlockchainsNetwork = tokensAvailableToBy[currencyName] + ?: mutableListOf() + supportedInBlockchainsNetwork.add(blockchainNetwork) + tokensAvailableToBy[currencyName] = supportedInBlockchainsNetwork + } + } else { + blockchainsAvailableToBuy.add(blockchain) + } + } + } + } + is Result.Failure -> { + blockchainsAvailableToBuy.clear() + tokensAvailableToBy.clear() + } + } + } + + override fun isBuyAllowed(): Boolean = true + + override fun isSellAllowed(): Boolean = false + + override fun availableForBuy(currency: Currency): Boolean { + if (!isBuyAllowed()) return false + + // blockchains which cant be defined by mercuryo service + val unsupportedBlockchains = listOf(Blockchain.Unknown, Blockchain.Binance, Blockchain.Arbitrum) + val blockchain = currency.blockchain + + return when (currency) { + is Currency.Blockchain -> { + when { + blockchain.isTestnet() -> blockchain.getTestnetTopUpUrl() != null + unsupportedBlockchains.contains(blockchain) -> false + else -> { + blockchainsAvailableToBuy.contains(currency.blockchain) + } + } + } + is Currency.Token -> { + val supportedInBlockchains = tokensAvailableToBy[currency.currencySymbol] ?: return false + supportedInBlockchains.contains(currency.blockchain) + } + } + } + + override fun availableForSell(currency: Currency): Boolean = false + + override fun getUrl( + action: CurrencyExchangeManager.Action, + blockchain: Blockchain, + cryptoCurrencyName: CryptoCurrencyName, + fiatCurrencyName: String, + walletAddress: String + ): String { + if (action == CurrencyExchangeManager.Action.Sell) throw UnsupportedOperationException() + + val builder = Uri.Builder() + .scheme(ExchangeUrlBuilder.SCHEME) + .authority("exchange.mercuryo.io") + .appendQueryParameter("widget_id", mercuryoWidgetId) + .appendQueryParameter("type", action.name.lowercase()) + .appendQueryParameter("currency", cryptoCurrencyName) + .appendQueryParameter("address", walletAddress) + .appendQueryParameter("signature", signature(walletAddress)) + .appendQueryParameter("fix_currency", "true") + .appendQueryParameter("return_url", ExchangeUrlBuilder.SUCCESS_URL) + + val url = builder.build().toString() + return url + } + + private fun signature(address: String): String { + return (address + secret).calculateSha512().toHexString().lowercase() + } + + + override fun getSellCryptoReceiptUrl( + action: CurrencyExchangeManager.Action, + transactionId: String + ): String? = null + + private fun blockchainFromCurrencyName(currencyName: String): Blockchain? = when (currencyName) { + "BNB" -> Blockchain.BSC + "ETH" -> Blockchain.Ethereum + else -> Blockchain.values().find { it.currency.lowercase() == currencyName.lowercase() } + } +} diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt index 54fd9c558e..161cfc568a 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/moonpay/MoonPayService.kt @@ -5,15 +5,15 @@ import android.util.Base64 import com.tangem.blockchain.common.Blockchain import com.tangem.common.services.Result import com.tangem.common.services.performRequest +import com.tangem.domain.common.extensions.withIOContext import com.tangem.network.common.createRetrofitInstance import com.tangem.tap.common.extensions.urlEncode import com.tangem.tap.common.redux.global.CryptoCurrencyName +import com.tangem.tap.features.wallet.redux.Currency import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager import com.tangem.tap.network.exchangeServices.ExchangeService import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder.Companion.SCHEME -import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder.Companion.URL_SELL -import kotlinx.coroutines.coroutineScope import javax.crypto.Mac import javax.crypto.spec.SecretKeySpec @@ -29,14 +29,14 @@ class MoonPayService( private var status: MoonPayStatus? = null - private suspend fun updateStatus() { - try { - coroutineScope { + override suspend fun update() { + withIOContext { + performRequest { val userStatusResult = performRequest { api.getUserStatus(apiKey) } - if (userStatusResult is Result.Failure) return@coroutineScope userStatusResult + if (userStatusResult is Result.Failure) return@performRequest val currenciesResult = performRequest { api.getCurrencies(apiKey) } - if (currenciesResult is Result.Failure) return@coroutineScope currenciesResult + if (currenciesResult is Result.Failure) return@performRequest val userStatus = (userStatusResult as Result.Success).data val currencies = (currenciesResult as Result.Success).data @@ -65,29 +65,31 @@ class MoonPayService( status = MoonPayStatus(currenciesToSell, userStatus, currencies) } - } catch (error: Error) { - status = null - Result.Failure(error) } } - override suspend fun isBuyAllowed(): Boolean = false + override fun isBuyAllowed(): Boolean = false - override suspend fun availableToBuy(): List = listOf() - - override suspend fun isSellAllowed(): Boolean { - refreshStatus() + override fun isSellAllowed(): Boolean { return status?.responseUserStatus?.isSellAllowed ?: false } - override suspend fun availableToSell(): List { - refreshStatus() - return status?.availableToSell ?: emptyList() - } + override fun availableForBuy(currency: Currency): Boolean = false - private suspend fun refreshStatus() { - if (status == null) { - updateStatus() + override fun availableForSell(currency: Currency): Boolean { + val availableForSell = status?.availableForSell ?: return false + if (!isSellAllowed()) return false + + return when (currency) { + is Currency.Blockchain -> { + val blockchain = currency.blockchain + when { + blockchain.isTestnet() -> false + blockchain == Blockchain.Unknown || currency.blockchain == Blockchain.BSC -> false + else -> availableForSell.contains(currency.currencySymbol) + } + } + is Currency.Token -> false } } @@ -133,10 +135,14 @@ class MoonPayService( val sha256encoded = sha256Hmac.doFinal(data.toByteArray()) return Base64.encodeToString(sha256encoded, Base64.NO_WRAP) } + + companion object { + const val URL_SELL = "sell.moonpay.com" + } } private data class MoonPayStatus( - val availableToSell: List, + val availableForSell: List, val responseUserStatus: MoonPayUserStatus, val responseCurrencies: List ) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/onramper/OnnramperApi.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/onramper/OnnramperApi.kt deleted file mode 100644 index 30a65ed706..0000000000 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/onramper/OnnramperApi.kt +++ /dev/null @@ -1,83 +0,0 @@ -package com.tangem.tap.network.exchangeServices.onramper - -import com.squareup.moshi.JsonClass -import retrofit2.http.GET -import retrofit2.http.Path - -interface OnramperApi { - @GET("gateways") - suspend fun gateways(): GatewaysResponse - - @GET("rate/{fromCurrency}/{toCurrency}/{paymentMethod}/{amount}") - suspend fun rate( - @Path("fromCurrency") fromCurrency: String, - @Path("toCurrency") toCurrency: String, - @Path("paymentMethod") paymentMethod: String, - @Path("amount") amount: Int, - ): RateResponse - - companion object { - val BASE_URL = "https://onramper.tech/" - } -} - -@JsonClass(generateAdapter = true) -data class GatewaysResponse( - val gateways: List -) - -@JsonClass(generateAdapter = true) -data class OnramperGateway( - val identifier: String, - val paymentMethods: List, - val fiatCurrencies: List, - val cryptoCurrencies: List -) - -@JsonClass(generateAdapter = true) -data class OnramperCurrency( - val id: String, - val code: String, - val precision: Int -) - -@JsonClass(generateAdapter = true) -data class RateResponse( - val identifier: String, - val duration: OnramperDuration, - val available: Boolean, - val error: OnramperError? = null, - val rate: Double? = null, - val fees: Double? = null, - val requiredKYC: List? = null, - val receivedCrypto: Double? = null, - val nextStep: OnramperNextStep? = null, -) - -@JsonClass(generateAdapter = true) -data class OnramperNextStep( - val type: String, - val url: String, - val message: String, - val extraData: List -) - -@JsonClass(generateAdapter = true) -data class OnramperExtraData( - val type: String, - val name: String, - val humanName: String, -) - -@JsonClass(generateAdapter = true) -data class OnramperDuration( - val seconds: Long, - val message: String -) - -@JsonClass(generateAdapter = true) -data class OnramperError( - val type: String, - val message: String, - val limit: Double -) \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/onramper/OnramperService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/onramper/OnramperService.kt deleted file mode 100644 index 94bdae395e..0000000000 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/onramper/OnramperService.kt +++ /dev/null @@ -1,116 +0,0 @@ -package com.tangem.tap.network.exchangeServices.onramper - -import android.net.Uri -import com.tangem.blockchain.common.Blockchain -import com.tangem.common.services.Result -import com.tangem.common.services.performRequest -import com.tangem.network.common.AddHeaderInterceptor -import com.tangem.network.common.createRetrofitInstance -import com.tangem.tap.common.extensions.urlEncode -import com.tangem.tap.common.redux.global.CryptoCurrencyName -import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager -import com.tangem.tap.network.exchangeServices.ExchangeService -import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder -import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder.Companion.SCHEME -import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder.Companion.SUCCESS_URL -import kotlinx.coroutines.coroutineScope -import java.util.* - -/** -[REDACTED_AUTHOR] - */ -class OnramperService( - val apiKey: String -) : ExchangeService, ExchangeUrlBuilder { - - private val api: OnramperApi by lazy { - createRetrofitInstance( - baseUrl = OnramperApi.BASE_URL, - interceptors = listOf( - AddHeaderInterceptor(mapOf("Authorization" to "Basic $apiKey")), - ) - ).create(OnramperApi::class.java) - } - - private var status: OnramperStatus? = null - - private suspend fun updateStatus() { - try { - coroutineScope { - val result = performRequest { api.gateways() } - if (result is Result.Failure) return@coroutineScope result - - val response = (result as Result.Success).data - val currenciesToBuy = extractCurrenciesToBuy(response).sorted() - val status = OnramperStatus(currenciesToBuy, response) - this@OnramperService.status = status - } - } catch (error: Error) { - status = null - Result.Failure(error) - } - } - - private fun extractCurrenciesToBuy(response: GatewaysResponse): List { - return response.gateways.map { gateway -> - gateway.cryptoCurrencies.map { currency -> currency.code } - }.flatten().toMutableSet().toList() - } - - override suspend fun isBuyAllowed(): Boolean { - refreshStatus() - return status != null - } - - override suspend fun availableToBuy(): List { - refreshStatus() - return status?.availableToBuy ?: emptyList() - } - - private suspend fun refreshStatus() { - if (status == null) { - updateStatus() - } - } - - override suspend fun isSellAllowed(): Boolean = false - - override suspend fun availableToSell(): List = listOf() - - override fun getUrl( - action: CurrencyExchangeManager.Action, - blockchain: Blockchain, - cryptoCurrencyName: CryptoCurrencyName, - fiatCurrency: String, - walletAddress: String, - ): String? { - var languageCode = Locale.getDefault().language - if (languageCode.isEmpty()) languageCode = "en" - - val builder = Uri.Builder() - .scheme(SCHEME) - .authority("widget.onramper.com") - .appendQueryParameter("apiKey", this.apiKey.urlEncode()) - .appendQueryParameter("defaultCrypto", cryptoCurrencyName) - .appendQueryParameter("wallets", "${blockchain.currency}:$walletAddress".urlEncode()) - .appendQueryParameter("redirectURL", SUCCESS_URL) - .appendQueryParameter("defaultFiat", fiatCurrency) - .appendQueryParameter("language", languageCode) - - status?.apply { - val gateways = responseGateways.gateways.joinToString(",") { it.identifier }.urlEncode() - builder.appendQueryParameter("onlyGateways", gateways) - - } - - val url = builder.build().toString() - return url - } - - override fun getSellCryptoReceiptUrl(action: CurrencyExchangeManager.Action, transactionId: String): String? = null -} - -private data class OnramperStatus( - val availableToBuy: List, - val responseGateways: GatewaysResponse -) \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_cross_rounded_24.xml b/app/src/main/res/drawable/ic_cross_rounded_24.xml new file mode 100644 index 0000000000..ad1cab5a84 --- /dev/null +++ b/app/src/main/res/drawable/ic_cross_rounded_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/img_flag_ru_24.xml b/app/src/main/res/drawable/img_flag_ru_24.xml new file mode 100644 index 0000000000..deff0dc4bb --- /dev/null +++ b/app/src/main/res/drawable/img_flag_ru_24.xml @@ -0,0 +1,18 @@ + + + + + + + + diff --git a/app/src/main/res/layout/dialog_russians_cardholders_warning.xml b/app/src/main/res/layout/dialog_russians_cardholders_warning.xml new file mode 100644 index 0000000000..8f2196c2d5 --- /dev/null +++ b/app/src/main/res/layout/dialog_russians_cardholders_warning.xml @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 97c8b2d0af..3e7c059c3a 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -1,6 +1,5 @@ - Nein Änderungen speichern Warnung Erneut versuchen @@ -196,4 +195,4 @@ RECHTLICHER HAFTUNGSAUSSCHLUSS Um %s XTZ reduzieren Nein, alles senden - \ No newline at end of file + diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 9e6777a914..069163f054 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -1,7 +1,6 @@ - Non Sauvegarder les modifications Alerte Réessayer @@ -188,4 +187,4 @@ Avertissement Réduire de% s XTZ Non, envoyer toute la somme - \ No newline at end of file + diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index a7011d4bcc..7ad1cddc70 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -1,7 +1,6 @@ - No Mantieni le modifiche Avviso Riprova @@ -195,4 +194,4 @@ Questa nota legale è stata modificata l\'ultima volta 01.10.2020. Riduci di %s XTZ No, invia l\'intero importo - \ No newline at end of file + diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 3712df8712..3df2976282 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -1,7 +1,6 @@ Tangem - Нет Сохранить изменения Предупреждение Повторить попытку @@ -262,8 +261,8 @@ Ваша криптокарта активирована и готова к использованию Продолжить Баланс - Отправляйте только %s (%s) на этот адрес. Отправка любой другой валюты приведет к ее безвозвратной потере. - Отправляйте только %s (%s) из сети %s на этот адрес. Отправка любой другой валюты приведет к ее безвозвратной потере. + Отправляйте только %s (%s) на этот адрес. Иначе это может привести к утрате средств. + Отправляйте только %s (%s) из сети %s на этот адрес. Иначе это может привести к утрате средств. Если процесс повторного создания кошелька каким-либо образом прервется, вам придется начать все сначала. Процесс связывания карт частично завершен. Вы не можете выйти из него сейчас. Внутренняя ошибка: не удается создать менеджер кошельков @@ -382,4 +381,5 @@ Привет, команда поддержки, Пожалуйста, расскажите нам больше о Вашей проблеме. Каждая деталь может быть полезной. Информация ниже не является обязательной. Вы можете стереть её, если хотите. + Нет diff --git a/app/src/main/res/values-ru/strings_final.xml b/app/src/main/res/values-ru/strings_final.xml index 201a1684ae..cd706ff8af 100644 --- a/app/src/main/res/values-ru/strings_final.xml +++ b/app/src/main/res/values-ru/strings_final.xml @@ -50,4 +50,21 @@ В сумме учтены не все монеты Управление токенами Нет цены + + Скрыть токен + Скрыть %s + Скрыть + Вы скрываете токен с главного экрана, но в любой момент сможете добавить его обратно. + Невозможно скрыть %s + Токен %s является основной валютой в сети %s и не может быть скрыт, до тех пор пока у вас в списке есть другие токены этой сети. + + Сеть %s не найдена. Пожалуйста, добавьте её и попробуйте заново. + Сеть %s + + Бэкап кошелька не был произведен + Чтобы защитить свои активы, мы советуем вам выполнить эту процедуру + + Карты банков РФ в данный момент не принимаются + У вас есть карта банка другой страны или платежной системы UnionPay? + Да diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 520b7bc183..5a05ac5fae 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2,7 +2,6 @@ Tangem - No Save changes Warning Retry diff --git a/app/src/main/res/values/strings_final.xml b/app/src/main/res/values/strings_final.xml index d59d31ff0e..3a16ec6ea0 100644 --- a/app/src/main/res/values/strings_final.xml +++ b/app/src/main/res/values/strings_final.xml @@ -50,4 +50,23 @@ The amount does not include some of your funds Manage tokens No rate + + Hide token + Hide %s + Hide + You are about to hide this token from the main screen. You can add it back anytime. + Unable to hide %s + The %s token is the main currency on the %s network and cannot be hidden as long as you have other tokens on this network in the list. + + %s network not found. Please, add it first and try again. + %s network + + Your wallet has not been backed up + To protect your assets, we advise you to carry out this procedure + Remove token + + Russian bank cards are not accepted at the moment + Do you have a bank card of another country or a UnionPay card? + Yes + No diff --git a/app/src/main/res/values/strings_untranslated.xml b/app/src/main/res/values/strings_untranslated.xml index 687dd82c75..1f498055db 100644 --- a/app/src/main/res/values/strings_untranslated.xml +++ b/app/src/main/res/values/strings_untranslated.xml @@ -206,8 +206,8 @@ Your crypto card is activated and ready to be used Continue Balance - Send only %s (%s) to this address. Sending any other currency will result in its irreversible loss. - Send only %s (%s) from %s network to this address. Sending any other currency will result in its irreversible loss. + Send only %s (%s) to this address. Using other tokens and networks may result in loss of funds. + Send only %s (%s) from %s network to this address. Using other tokens and networks may result in loss of funds. If the process of re-creating the wallet gets interrupted in any way, you\'ll have to start over. The twinning process is partly complete. You can\'t exit it now. diff --git a/domain/build.gradle b/domain/build.gradle index 5a54411b94..c2e5c807db 100644 --- a/domain/build.gradle +++ b/domain/build.gradle @@ -59,7 +59,7 @@ dependencies { implementation implementation(project(path: ':network')) implementation implementation(project(path: ':common')) - implementation 'com.tangem:blockchain:develop-93' + implementation 'com.tangem:blockchain:AND-1939_tron_fix_release-97' // implementation 'com.tangem:blockchain:0.0.1' implementation 'com.tangem.tangem-sdk-kotlin:core:develop-154' implementation 'com.tangem.tangem-sdk-kotlin:android:develop-154' diff --git a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt index 9a4b0be414..1c09b4116a 100644 --- a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt +++ b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt @@ -2,7 +2,6 @@ package com.tangem.domain.features.addCustomToken.redux import android.webkit.ValueCallback import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchain.common.DerivationStyle import com.tangem.common.extensions.guard import com.tangem.common.services.Result import com.tangem.domain.AddCustomTokenError @@ -571,17 +570,11 @@ private class AddCustomTokenReducer( supportedTokenNetworkIds = supportedTokenNetworkIds ) - var derivationPathState = state.screenState.derivationPath - derivationPathState = when (card.derivationStyle) { - DerivationStyle.LEGACY -> derivationPathState.copy(isVisible = true) - null, DerivationStyle.NEW -> derivationPathState.copy(isVisible = false) - } val form = Form(AddCustomTokenState.createFormFields(card, CustomTokenType.Blockchain)) state.copy( cardDerivationStyle = card.derivationStyle, form = form, tangemTechServiceManager = tangemTechServiceManager, - screenState = state.screenState.copy(derivationPath = derivationPathState) ) } is OnDestroy -> { diff --git a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenState.kt b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenState.kt index ed18fe69f5..97c652da19 100644 --- a/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenState.kt +++ b/domain/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenState.kt @@ -50,7 +50,7 @@ data class AddCustomTokenState( fun blockchainToName(blockchain: Blockchain, isDerivationPath: Boolean = false): String? { return when { - isDerivationPath -> blockchain.derivationPath(cardDerivationStyle)?.rawPath + isDerivationPath -> blockchain.derivationPath(DerivationStyle.LEGACY)?.rawPath else -> { when (blockchain) { Blockchain.Unknown -> null @@ -150,10 +150,21 @@ data class AddCustomTokenState( mainNetwork: Blockchain, derivationNetwork: Blockchain, derivationStyle: DerivationStyle? - ): com.tangem.common.hdWallet.DerivationPath? = when (derivationNetwork) { - Blockchain.Unknown -> mainNetwork - else -> derivationNetwork - }.derivationPath(derivationStyle) + ): com.tangem.common.hdWallet.DerivationPath? { + // If we allow user to select derivations, we need to provide different derivations + // (Legacy style derivations). + // But the mainNetwork derivation depends on whether a user has a card + // with legacy derivations or new style derivations. + val derivationStyleToUse = if (derivationNetwork == Blockchain.Unknown) { + derivationStyle + } else { + DerivationStyle.LEGACY + } + return when (derivationNetwork) { + Blockchain.Unknown -> mainNetwork + else -> derivationNetwork + }.derivationPath(derivationStyleToUse) + } internal fun createFormFields(card: Card, type: CustomTokenType): List> { return listOf( diff --git a/network/src/main/java/com/tangem/network/api/tangemTech/Responses.kt b/network/src/main/java/com/tangem/network/api/tangemTech/Responses.kt index c21fb92726..9a4b2cd8b1 100644 --- a/network/src/main/java/com/tangem/network/api/tangemTech/Responses.kt +++ b/network/src/main/java/com/tangem/network/api/tangemTech/Responses.kt @@ -43,4 +43,8 @@ data class CurrenciesResponse(val currencies: List) { val unit: String, // $, €, ₽ val type: String, ) : TangemTechResponse -} \ No newline at end of file +} + +data class GeoResponse( + val code: String +) : TangemTechResponse \ No newline at end of file diff --git a/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechApi.kt b/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechApi.kt index 0081f4ee24..80558732dd 100644 --- a/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechApi.kt +++ b/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechApi.kt @@ -27,4 +27,6 @@ interface TangemTechApi { @GET("currencies") suspend fun currencies(): CurrenciesResponse + @GET("geo") + suspend fun geo(): GeoResponse } \ No newline at end of file diff --git a/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechService.kt b/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechService.kt index 5790bdac3a..3bf3bd232e 100644 --- a/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechService.kt +++ b/network/src/main/java/com/tangem/network/api/tangemTech/TangemTechService.kt @@ -47,6 +47,10 @@ class TangemTechService { } } + suspend fun userCountry(): Result = withContext(Dispatchers.IO) { + performRequest { api.geo() } + } + suspend fun currencies(): Result = withContext(Dispatchers.IO) { performRequest { api.currencies() } }