diff --git a/Marathonfile b/Marathonfile new file mode 100644 index 0000000000..91d15ba5fa --- /dev/null +++ b/Marathonfile @@ -0,0 +1,41 @@ +name: "Tangem Android UI Tests" +outputDir: "build/reports/marathon" + +testOutputTimeoutMillis: 180000 # 3 minutes +testBatchTimeoutMillis: 600000 # 10 minutes + +vendorConfiguration: + type: "Android" + applicationApk: "app/build/outputs/apk/google/mocked/app-google-mocked.apk" + testApplicationApk: "app/build/outputs/apk/androidTest/google/mocked/app-google-mocked-androidTest.apk" + autoGrantPermission: true + applicationPmClear: true + testApplicationPmClear: true + # wiremockBaseUrl redirects API calls from wiremock.tests-d.com to local WireMock + # This is used on CI where each emulator has its own WireMock instance via adb reverse + instrumentationArgs: + wiremockBaseUrl: "http://localhost:8080" + allureConfiguration: + enabled: false # we collect allure via adb pull, not through marathon + +poolingStrategy: + type: "omni" + +batchingStrategy: + type: "isolate" + +sortingStrategy: + type: "no-sorting" + +retryStrategy: + type: "fixed-quota" + totalAllowedRetryQuota: 30 + retryPerTestQuota: 1 + +flakinessStrategy: + type: "ignore" + +analyticsConfiguration: + type: "disabled" + +debug: false diff --git a/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt b/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt index 96c353f84c..009af28e09 100644 --- a/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt +++ b/app/src/androidTest/kotlin/com/tangem/common/BaseTestCase.kt @@ -6,6 +6,7 @@ import androidx.compose.ui.test.junit4.createEmptyComposeRule import androidx.compose.ui.test.printToLog import androidx.test.core.app.ActivityScenario import androidx.test.espresso.intent.Intents +import androidx.test.platform.app.InstrumentationRegistry import androidx.test.rule.GrantPermissionRule import com.kaspersky.components.alluresupport.interceptors.step.ScreenshotStepInterceptor import com.kaspersky.components.alluresupport.withForcedAllureSupport @@ -19,6 +20,7 @@ import com.tangem.common.rules.ApiEnvironmentRule import com.tangem.datasource.api.common.config.managers.ApiConfigsManager import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.PreferencesKeys +import com.tangem.datasource.utils.WireMockRedirectInterceptor import com.tangem.domain.promo.PromoRepository import com.tangem.domain.promo.models.PromoId import com.tangem.tap.MainActivity @@ -86,6 +88,9 @@ abstract class BaseTestCase : TestCase( additionalAfterSection: () -> Unit = {}, ) = before { Allure.label(ALLURE_LABEL_NAME, ALLURE_LABEL_VALUE) + // Setup WireMock redirect for CI with local WireMock instances + val wiremockUrl = InstrumentationRegistry.getArguments().getString(WIREMOCK_BASE_URL_ARG) + WireMockRedirectInterceptor.overriddenBaseUrl = wiremockUrl hiltRule.inject() runBlocking { appPreferencesStore.editData { mutablePreferences -> @@ -138,7 +143,6 @@ abstract class BaseTestCase : TestCase( private fun applicationInjectionRule(): ApplicationInjectionExecutionRule { return ApplicationInjectionExecutionRule( toggleStates = mapOf( - "NEW_TOKEN_RECEIVE_ENABLED" to true, "SWAP_REDESIGN_ENABLED" to false, "NEW_ONRAMP_MAIN_ENABLED" to true, "HOT_WALLET_ENABLED" to true, @@ -147,4 +151,8 @@ abstract class BaseTestCase : TestCase( ) ) } + + private companion object { + const val WIREMOCK_BASE_URL_ARG = "wiremockBaseUrl" + } } \ No newline at end of file diff --git a/app/src/androidTest/kotlin/com/tangem/common/utils/WireMockUtils.kt b/app/src/androidTest/kotlin/com/tangem/common/utils/WireMockUtils.kt index fe4c30d9a6..1095a7aa96 100644 --- a/app/src/androidTest/kotlin/com/tangem/common/utils/WireMockUtils.kt +++ b/app/src/androidTest/kotlin/com/tangem/common/utils/WireMockUtils.kt @@ -1,22 +1,31 @@ package com.tangem.common.utils +import com.tangem.datasource.utils.WireMockRedirectInterceptor import okhttp3.* import okhttp3.MediaType.Companion.toMediaType import okhttp3.RequestBody.Companion.toRequestBody import timber.log.Timber import java.io.IOException +private const val DEFAULT_WIREMOCK_URL = "[REDACTED_ENV_URL]" + +/** + * Returns the WireMock base URL to use. + */ +private fun getWireMockBaseUrl(): String = + WireMockRedirectInterceptor.overriddenBaseUrl ?: DEFAULT_WIREMOCK_URL + /** * Method uses to set WireMock scenario state * @param scenarioName Name of the scenario to modify * @param state The target state to set (must be one of the scenario's possibleStates) - * @param baseUrl WireMock base URL + * @param baseUrl WireMock base URL (defaults to local override if set, otherwise remote) * @return true if state was set successfully, false otherwise */ fun setWireMockScenarioState( scenarioName: String, state: String, - baseUrl: String = "[REDACTED_ENV_URL]" + baseUrl: String = getWireMockBaseUrl() ): Boolean { Timber.i("=== WireMock Scenario Set ===") Timber.i("Setting scenario '$scenarioName' to state: $state") @@ -46,8 +55,9 @@ fun setWireMockScenarioState( /** * Method checks accessibility of WireMock + * @param baseUrl WireMock base URL (defaults to local override if set, otherwise remote) */ -fun checkWireMockStatus(baseUrl: String = "[REDACTED_ENV_URL]"): Boolean { +fun checkWireMockStatus(baseUrl: String = getWireMockBaseUrl()): Boolean { val client = OkHttpClient() val request = Request.Builder() .url("$baseUrl/__admin/scenarios") @@ -69,8 +79,9 @@ fun checkWireMockStatus(baseUrl: String = "[REDACTED_ENV_URL]"): Boolean { /** * Method to reset all WireMock scenarios + * @param baseUrl WireMock base URL (defaults to local override if set, otherwise remote) */ -fun resetWireMockScenarios(baseUrl: String = "[REDACTED_ENV_URL]"): Boolean { +fun resetWireMockScenarios(baseUrl: String = getWireMockBaseUrl()): Boolean { Timber.i("=== WireMock Scenarios Reset ===") Timber.i("Base URL: $baseUrl") @@ -105,13 +116,13 @@ fun resetWireMockScenarios(baseUrl: String = "[REDACTED_ENV_URL]"): Boolean { * Method to reset a specific WireMock scenario to its initial state * @param scenarioName Name of the scenario to reset * @param initialState The target state to reset the scenario to (must be one of the scenario's possibleStates) - * @param baseUrl WireMock base URL + * @param baseUrl WireMock base URL (defaults to local override if set, otherwise remote) * @return true if reset was successful, false otherwise */ fun resetWireMockScenarioState( scenarioName: String, initialState: String = "Started", - baseUrl: String = "[REDACTED_ENV_URL]" + baseUrl: String = getWireMockBaseUrl() ): Boolean { Timber.i("=== WireMock Scenario Reset ===") Timber.i("Resetting scenario '$scenarioName' to initial state: $initialState") diff --git a/app/src/main/java/com/tangem/tap/TangemApplication.kt b/app/src/main/java/com/tangem/tap/TangemApplication.kt index 4e83789e83..6b59b1b8b2 100644 --- a/app/src/main/java/com/tangem/tap/TangemApplication.kt +++ b/app/src/main/java/com/tangem/tap/TangemApplication.kt @@ -42,6 +42,7 @@ import com.tangem.datasource.local.logs.AppLogsStore import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.token.UserTokensResponseStore import com.tangem.datasource.utils.NetworkLogsSaveInterceptor +import com.tangem.datasource.utils.WireMockRedirectInterceptor import com.tangem.domain.appcurrency.repository.AppCurrencyRepository import com.tangem.domain.apptheme.GetAppThemeModeUseCase import com.tangem.domain.apptheme.repository.AppThemeModeRepository @@ -334,15 +335,23 @@ open class TangemApplication : Application(), ImageLoaderFactory, Configuration. ExceptionHandler.append(blockchainExceptionHandler) if (LogConfig.network.isBlockchainSdkNetworkLogEnabled) { - BlockchainSdkRetrofitBuilder.interceptors = listOf( - createNetworkLoggingInterceptor(), - ChuckerInterceptor(this), - ) + BlockchainSdkRetrofitBuilder.interceptors = buildList { + if (BuildConfig.MOCK_DATA_SOURCE) { + add(WireMockRedirectInterceptor()) + } + add(createNetworkLoggingInterceptor()) + add(ChuckerInterceptor(this@TangemApplication)) + } TangemApiServiceSettings.addInterceptors( - createNetworkLoggingInterceptor(), - ChuckerInterceptor(this), - NetworkLogsSaveInterceptor(appLogsStore), + *buildList { + if (BuildConfig.MOCK_DATA_SOURCE) { + add(WireMockRedirectInterceptor()) + } + add(createNetworkLoggingInterceptor()) + add(ChuckerInterceptor(this@TangemApplication)) + add(NetworkLogsSaveInterceptor(appLogsStore)) + }.toTypedArray(), ) } diff --git a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt index ff1b6bb97a..55dab8c3b9 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt @@ -393,4 +393,18 @@ internal object TransactionDomainModule { currencyChecksRepository = currencyChecksRepository, ) } + + @Provides + @Singleton + fun provideSignCloreMessageUseCase( + walletManagersFacade: WalletManagersFacade, + cardSdkConfigRepository: CardSdkConfigRepository, + tangemHotWalletSignerFactory: TangemHotWalletSigner.Factory, + ): SignCloreMessageUseCase { + return SignCloreMessageUseCase( + walletManagersFacade = walletManagersFacade, + cardSdkConfigRepository = cardSdkConfigRepository, + getHotWalletSigner = tangemHotWalletSignerFactory::create, + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt index 52533057f0..53c7fa8abd 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt @@ -1,5 +1,6 @@ package com.tangem.tap.di.domain +import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.tokens.repository.CurrenciesRepository @@ -129,12 +130,14 @@ internal object WalletsDomainModule { userWalletsListRepository: UserWalletsListRepository, hotWalletFeatureToggles: HotWalletFeatureToggles, walletsRepository: WalletsRepository, + analyticsEventHandler: AnalyticsEventHandler, ): SaveWalletUseCase { return SaveWalletUseCase( userWalletsListManager = userWalletsListManager, userWalletsListRepository = userWalletsListRepository, walletsRepository = walletsRepository, useNewRepository = hotWalletFeatureToggles.isHotWalletEnabled, + analyticsEventHandler = analyticsEventHandler, ) } diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DefaultUserWalletsListRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DefaultUserWalletsListRepository.kt index 80dce033a7..414ea8a0b3 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DefaultUserWalletsListRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/DefaultUserWalletsListRepository.kt @@ -21,6 +21,7 @@ import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.isLocked import com.tangem.domain.wallets.R +import com.tangem.domain.wallets.analytics.WalletSettingsAnalyticEvents import com.tangem.domain.wallets.builder.UserWalletIdBuilder import com.tangem.domain.wallets.hot.HotWalletAccessCodeAttemptsRepository import com.tangem.domain.wallets.hot.HotWalletPasswordRequester @@ -390,6 +391,7 @@ internal class DefaultUserWalletsListRepository( if (newUserWallet.walletId == oldUserWallet.walletId && oldUserWallet is UserWallet.Hot && newUserWallet is UserWallet.Cold ) { + trackWalletUpgradeEvent() removeHotWalletsFromSDKAndRepos(walletIds = listOf(oldUserWallet.walletId)) // When upgrading from Hot to Cold, if biometric lock is available, set it if (hasBiometry()) { @@ -536,4 +538,8 @@ internal class DefaultUserWalletsListRepository( ), ) } + + private fun trackWalletUpgradeEvent() { + analyticsEventHandler.send(event = WalletSettingsAnalyticEvents.WalletUpgraded()) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt deleted file mode 100644 index f8350e77a9..0000000000 --- a/app/src/main/java/com/tangem/tap/proxy/UserWalletManagerImpl.kt +++ /dev/null @@ -1,79 +0,0 @@ -package com.tangem.tap.proxy - -import com.tangem.blockchain.common.AmountType -import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchain.common.WalletManager -import com.tangem.blockchainsdk.utils.fromNetworkId -import com.tangem.domain.walletmanager.WalletManagersFacade -import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase -import com.tangem.lib.crypto.UserWalletManager -import com.tangem.lib.crypto.models.ProxyAmount -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.coroutines.withContext -import timber.log.Timber -import java.math.BigDecimal - -class UserWalletManagerImpl( - private val walletManagersFacade: WalletManagersFacade, - private val getSelectedWalletUseCase: GetSelectedWalletUseCase, - private val dispatchers: CoroutineDispatcherProvider, -) : UserWalletManager { - - override fun getWalletId(): String { - val selectedUserWallet = requireNotNull( - getSelectedWalletUseCase.sync().getOrNull(), - ) { "selectedUserWallet shouldn't be null" } - return selectedUserWallet.walletId.stringValue - } - - override suspend fun hideAllTokens() { - // FIXME: Used only in Tester Actions - Timber.w("Not implemented") - } - - override suspend fun getWalletAddress(networkId: String, derivationPath: String?): String { - val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } - val walletManager = getActualWalletManager(blockchain, derivationPath) - return walletManager.wallet.address - } - - override suspend fun getLastTransactionHash(networkId: String, derivationPath: String?): String? { - val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } - val walletManager = getActualWalletManager(blockchain, derivationPath) - return walletManager.wallet.recentTransactions - .lastOrNull { it.hash?.isNotEmpty() == true } - ?.hash - } - - override suspend fun getNativeTokenBalance(networkId: String, derivationPath: String?): ProxyAmount? { - val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } - val walletManager = getActualWalletManager(blockchain, derivationPath) - return walletManager.wallet.amounts.firstNotNullOfOrNull { amountEntry -> - amountEntry.takeIf { amountEntry.key is AmountType.Coin } - }?.value?.let { amount -> - ProxyAmount( - amount.currencySymbol, - amount.value ?: BigDecimal.ZERO, - amount.decimals, - ) - } - } - - @Throws(IllegalArgumentException::class) - private suspend fun getActualWalletManager(blockchain: Blockchain, derivationPath: String?): WalletManager { - val selectedUserWallet = requireNotNull( - getSelectedWalletUseCase.sync().getOrNull(), - ) { "userWallet or userWalletsListManager is null" } - val walletManager = withContext(dispatchers.io) { - walletManagersFacade.getOrCreateWalletManager( - selectedUserWallet.walletId, - blockchain, - derivationPath, - ) - } - - return requireNotNull(walletManager) { - "No wallet manager found" - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt b/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt index 677d7e5ebd..5c943d917b 100644 --- a/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt +++ b/app/src/main/java/com/tangem/tap/proxy/di/ProxyModule.kt @@ -1,11 +1,6 @@ package com.tangem.tap.proxy.di -import com.tangem.domain.walletmanager.WalletManagersFacade -import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase -import com.tangem.lib.crypto.UserWalletManager import com.tangem.tap.proxy.AppStateHolder -import com.tangem.tap.proxy.UserWalletManagerImpl -import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -21,18 +16,4 @@ internal object ProxyModule { fun provideAppStateHolder(): AppStateHolder { return AppStateHolder() } - - @Provides - @Singleton - fun provideUserWalletManager( - walletManagersFacade: WalletManagersFacade, - getSelectedWalletUseCase: GetSelectedWalletUseCase, - dispatchers: CoroutineDispatcherProvider, - ): UserWalletManager { - return UserWalletManagerImpl( - walletManagersFacade = walletManagersFacade, - getSelectedWalletUseCase = getSelectedWalletUseCase, - dispatchers = dispatchers, - ) - } } \ No newline at end of file diff --git a/app/src/mocked/res/xml/network_security_config.xml b/app/src/mocked/res/xml/network_security_config.xml index 52c44ac992..a4929411f1 100644 --- a/app/src/mocked/res/xml/network_security_config.xml +++ b/app/src/mocked/res/xml/network_security_config.xml @@ -6,4 +6,9 @@ + + + 10.0.2.2 + localhost + diff --git a/common/ui/src/main/java/com/tangem/common/ui/account/AccountIcon.kt b/common/ui/src/main/java/com/tangem/common/ui/account/AccountIcon.kt index 188fcb1709..ba746abcc9 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/account/AccountIcon.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/account/AccountIcon.kt @@ -6,6 +6,7 @@ import com.tangem.common.ui.R import com.tangem.core.ui.components.account.AccountCharIcon import com.tangem.core.ui.components.account.AccountIconSize import com.tangem.core.ui.components.account.AccountResIcon +import com.tangem.core.ui.components.account.PaymentAccountIcon import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference import com.tangem.domain.models.account.CryptoPortfolioIcon @@ -16,7 +17,7 @@ import com.tangem.domain.models.account.CryptoPortfolioIcon.Color * - a single character (derived from the [name]) using [AccountCharIcon], or * - a predefined vector resource (from [icon]) using [AccountResIcon]. * - * The background color is taken from [CryptoPortfolioIconUM.color], + * The background color is taken from [AccountIconUM.CryptoPortfolio.color], * and the icon size, text style, and container shape are adapted based on the given [size]. * * @param name A [TextReference] used to resolve and display the first letter @@ -31,29 +32,43 @@ import com.tangem.domain.models.account.CryptoPortfolioIcon.Color @Composable fun AccountIcon( name: TextReference, - icon: CryptoPortfolioIconUM, + icon: AccountIconUM.CryptoPortfolio, size: AccountIconSize, modifier: Modifier = Modifier, ) { val letter = name.resolveReference().firstOrNull() + val iconColor = icon.color.getUiColor() when { icon.value == CryptoPortfolioIcon.Icon.Letter && letter == null -> { AccountResIcon( resId = R.drawable.ic_tangem_24, - color = icon.color.getUiColor(), + color = iconColor, size = size, modifier = modifier, ) } icon.value == CryptoPortfolioIcon.Icon.Letter -> AccountCharIcon( char = letter ?: 'N', - color = icon.color.getUiColor(), + color = iconColor, size = size, modifier = modifier, ) else -> AccountResIcon( resId = icon.value.getResId(), - color = icon.color.getUiColor(), + color = iconColor, + size = size, + modifier = modifier, + ) + } +} + +@Composable +fun AccountIcon(name: TextReference, icon: AccountIconUM, size: AccountIconSize, modifier: Modifier = Modifier) { + when (icon) { + is AccountIconUM.Payment -> PaymentAccountIcon(size = size, modifier = modifier) + is AccountIconUM.CryptoPortfolio -> AccountIcon( + name = name, + icon = icon, size = size, modifier = modifier, ) @@ -62,7 +77,7 @@ fun AccountIcon( object AccountIconPreviewData { - fun randomAccountIcon(letter: Boolean = false) = CryptoPortfolioIconUM( + fun randomAccountIcon(letter: Boolean = false) = AccountIconUM.CryptoPortfolio( value = if (letter) CryptoPortfolioIcon.Icon.Letter else CryptoPortfolioIcon.Icon.entries.random(), color = Color.entries.random(), ) diff --git a/common/ui/src/main/java/com/tangem/common/ui/account/AccountIconUM.kt b/common/ui/src/main/java/com/tangem/common/ui/account/AccountIconUM.kt new file mode 100644 index 0000000000..ca79e78402 --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/account/AccountIconUM.kt @@ -0,0 +1,10 @@ +package com.tangem.common.ui.account + +import com.tangem.domain.models.account.CryptoPortfolioIcon.Color +import com.tangem.domain.models.account.CryptoPortfolioIcon.Icon + +sealed class AccountIconUM { + data class CryptoPortfolio(val value: Icon, val color: Color) : AccountIconUM() + + data object Payment : AccountIconUM() +} \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/account/AccountLabel.kt b/common/ui/src/main/java/com/tangem/common/ui/account/AccountLabel.kt index 51f13b848f..4938e46e43 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/account/AccountLabel.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/account/AccountLabel.kt @@ -27,7 +27,7 @@ import com.tangem.core.ui.res.TangemTheme @Composable fun AccountLabel( name: TextReference, - icon: CryptoPortfolioIconUM, + icon: AccountIconUM, iconSize: AccountIconSize, modifier: Modifier = Modifier, nameStyle: TextStyle = TangemTheme.typography.subtitle2, diff --git a/common/ui/src/main/java/com/tangem/common/ui/account/AccountPortfolioItemUMConverter.kt b/common/ui/src/main/java/com/tangem/common/ui/account/AccountPortfolioItemUMConverter.kt index baed1af4d7..6bb24f24e3 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/account/AccountPortfolioItemUMConverter.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/account/AccountPortfolioItemUMConverter.kt @@ -48,13 +48,15 @@ class AccountPortfolioItemUMConverter( ) UserWalletItemUM.Information.Loaded(text) } + is Account.Payment -> TODO("[REDACTED_JIRA]") } private fun getImageState(account: Account.CryptoPortfolio) = when (account) { is Account.CryptoPortfolio -> UserWalletItemUM.ImageState.Account( name = account.accountName.toUM().value, - icon = account.icon.toUM(), + icon = CryptoPortfolioIconConverter.convert(account.icon), ) + is Account.Payment -> TODO("[REDACTED_JIRA]") } private fun getBalanceInfo(): UserWalletItemUM.Balance { diff --git a/common/ui/src/main/java/com/tangem/common/ui/account/AccountRow.kt b/common/ui/src/main/java/com/tangem/common/ui/account/AccountRow.kt index 0b51e38465..8e755eb8bc 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/account/AccountRow.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/account/AccountRow.kt @@ -41,7 +41,7 @@ import com.tangem.core.ui.res.TangemThemePreview fun AccountRow( title: TextReference, subtitle: TextReference, - icon: CryptoPortfolioIconUM, + icon: AccountIconUM.CryptoPortfolio, modifier: Modifier = Modifier, isReverse: Boolean = false, ) { diff --git a/common/ui/src/main/java/com/tangem/common/ui/account/AccountTitle.kt b/common/ui/src/main/java/com/tangem/common/ui/account/AccountTitle.kt index d21a35276b..a77848f899 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/account/AccountTitle.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/account/AccountTitle.kt @@ -1,7 +1,9 @@ package com.tangem.common.ui.account import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -9,11 +11,16 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.testTag import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.account.AccountIconSize import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.test.SendScreenTestTags +import com.tangem.domain.models.account.CryptoPortfolioIcon +import com.tangem.utils.StringsSigns /** * A composable function that displays an account label (icon + name) with an optional prefix. @@ -61,4 +68,28 @@ fun AccountTitle( ) } } +} + +@Preview +@Composable +private fun PreviewAccountTitle() { + TangemThemePreview { + Column { + AccountTitle( + accountTitleUM = AccountTitleUM.Account( + prefixText = stringReference(StringsSigns.DOT), + name = stringReference("Main Wallet"), + icon = AccountIconUM.CryptoPortfolio( + value = CryptoPortfolioIcon.Icon.Bookmark, + color = CryptoPortfolioIcon.Color.Pattypan, + ), + ), + modifier = Modifier.padding(4.dp), + ) + AccountTitle( + accountTitleUM = AccountTitleUM.Account.payment(prefixText = stringReference(StringsSigns.DOT)), + modifier = Modifier.padding(4.dp), + ) + } + } } \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/account/AccountTitleUM.kt b/common/ui/src/main/java/com/tangem/common/ui/account/AccountTitleUM.kt index 5b9537de75..f5cdef91d2 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/account/AccountTitleUM.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/account/AccountTitleUM.kt @@ -1,7 +1,9 @@ package com.tangem.common.ui.account import androidx.compose.runtime.Immutable +import com.tangem.common.ui.R import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resourceReference /** * A sealed interface representing the title of an account, which can be either a simple text @@ -19,6 +21,16 @@ sealed interface AccountTitleUM { data class Account( val prefixText: TextReference, val name: TextReference, - val icon: CryptoPortfolioIconUM, - ) : AccountTitleUM + val icon: AccountIconUM, + ) : AccountTitleUM { + companion object { + fun payment(prefixText: TextReference = TextReference.EMPTY): Account { + return Account( + prefixText = prefixText, + name = resourceReference(R.string.tangempay_payment_account), + icon = AccountIconUM.Payment, + ) + } + } + } } \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/account/CryptoPortfolioIconConverter.kt b/common/ui/src/main/java/com/tangem/common/ui/account/CryptoPortfolioIconConverter.kt new file mode 100644 index 0000000000..cfb11a33be --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/account/CryptoPortfolioIconConverter.kt @@ -0,0 +1,14 @@ +package com.tangem.common.ui.account + +import com.tangem.domain.models.account.CryptoPortfolioIcon +import com.tangem.utils.converter.TwoWayConverter + +object CryptoPortfolioIconConverter : TwoWayConverter { + override fun convert(value: CryptoPortfolioIcon): AccountIconUM.CryptoPortfolio { + return AccountIconUM.CryptoPortfolio(value = value.value, color = value.color) + } + + override fun convertBack(value: AccountIconUM.CryptoPortfolio): CryptoPortfolioIcon { + return CryptoPortfolioIcon.ofCustomAccount(value = value.value, color = value.color) + } +} \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/account/CryptoPortfolioIconExt.kt b/common/ui/src/main/java/com/tangem/common/ui/account/CryptoPortfolioIconExt.kt index d97401c0a3..14015c86b2 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/account/CryptoPortfolioIconExt.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/account/CryptoPortfolioIconExt.kt @@ -47,7 +47,4 @@ fun CryptoPortfolioIcon.Icon.getResId(): Int { CryptoPortfolioIcon.Icon.Package -> R.drawable.ic_package_24 CryptoPortfolioIcon.Icon.Gift -> R.drawable.ic_gift_24 } -} - -fun CryptoPortfolioIcon.toUM() = CryptoPortfolioIconUM(domainModel = this) -fun CryptoPortfolioIconUM.toDomain() = CryptoPortfolioIcon.ofCustomAccount(value = this.value, color = this.color) \ No newline at end of file +} \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/account/CryptoPortfolioIconUM.kt b/common/ui/src/main/java/com/tangem/common/ui/account/CryptoPortfolioIconUM.kt deleted file mode 100644 index cb8b2d22af..0000000000 --- a/common/ui/src/main/java/com/tangem/common/ui/account/CryptoPortfolioIconUM.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.tangem.common.ui.account - -import com.tangem.domain.models.account.CryptoPortfolioIcon -import com.tangem.domain.models.account.CryptoPortfolioIcon.Color -import com.tangem.domain.models.account.CryptoPortfolioIcon.Icon - -data class CryptoPortfolioIconUM( - val value: Icon, - val color: Color, -) { - constructor(domainModel: CryptoPortfolioIcon) : this( - value = domainModel.value, - color = domainModel.color, - ) -} \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/account/PortfolioSelectRow.kt b/common/ui/src/main/java/com/tangem/common/ui/account/PortfolioSelectRow.kt index caa4ec4ce0..e4575a197c 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/account/PortfolioSelectRow.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/account/PortfolioSelectRow.kt @@ -82,7 +82,7 @@ fun PortfolioSelectRow( @Immutable data class PortfolioSelectUM( - val icon: CryptoPortfolioIconUM?, + val icon: AccountIconUM.CryptoPortfolio?, val name: TextReference, val isAccountMode: Boolean, val isMultiChoice: Boolean, diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountAccountConverter.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountAccountConverter.kt index b859d383d1..ea09230bd7 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountAccountConverter.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/converters/AmountAccountConverter.kt @@ -1,6 +1,7 @@ package com.tangem.common.ui.amountScreen.converters import com.tangem.common.ui.account.AccountTitleUM +import com.tangem.common.ui.account.CryptoPortfolioIconConverter import com.tangem.common.ui.account.toUM import com.tangem.core.ui.extensions.TextReference import com.tangem.domain.models.account.Account @@ -15,7 +16,7 @@ class AmountAccountConverter( return if (value != null && isAccountsMode) { AccountTitleUM.Account( name = value.accountName.toUM().value, - icon = value.icon.toUM(), + icon = CryptoPortfolioIconConverter.convert(value.icon), prefixText = prefixText, ) } else { diff --git a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/preview/AmountStatePreviewData.kt b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/preview/AmountStatePreviewData.kt index d5eafdf94d..d5a6605ccd 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/amountScreen/preview/AmountStatePreviewData.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/amountScreen/preview/AmountStatePreviewData.kt @@ -5,7 +5,7 @@ import androidx.compose.foundation.text.KeyboardOptions import com.tangem.common.ui.R import com.tangem.common.ui.account.AccountNameUM import com.tangem.common.ui.account.AccountTitleUM -import com.tangem.common.ui.account.toUM +import com.tangem.common.ui.account.CryptoPortfolioIconConverter import com.tangem.common.ui.amountScreen.models.AmountFieldModel import com.tangem.common.ui.amountScreen.models.AmountState import com.tangem.core.ui.components.currency.icon.CurrencyIconState @@ -88,7 +88,7 @@ object AmountStatePreviewData { val amountStateV2Accounts = amountState.copy( accountTitleUM = AccountTitleUM.Account( name = AccountNameUM.DefaultMain.value, - icon = CryptoPortfolioIcon.ofDefaultCustomAccount().toUM(), + icon = CryptoPortfolioIconConverter.convert(CryptoPortfolioIcon.ofDefaultCustomAccount()), prefixText = resourceReference(R.string.common_from), ), availableBalanceCrypto = stringReference("2 130,81231238 USDT"), diff --git a/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/chooseaddress/ChooseAddressBottomSheet.kt b/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/chooseaddress/ChooseAddressBottomSheet.kt index 4054fddac7..2bb9af138c 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/chooseaddress/ChooseAddressBottomSheet.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/chooseaddress/ChooseAddressBottomSheet.kt @@ -1,15 +1,23 @@ package com.tangem.common.ui.bottomsheet.chooseaddress +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import com.tangem.common.ui.bottomsheet.receive.AddressModel import com.tangem.core.ui.R import com.tangem.core.ui.components.SimpleSettingsRow -import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import kotlinx.collections.immutable.persistentListOf @Composable fun ChooseAddressBottomSheet(config: TangemBottomSheetConfig) { @@ -31,4 +39,40 @@ private fun ChooseAddressBottomSheetContent(content: ChooseAddressBottomSheetCon ) } } -} \ No newline at end of file +} + +// region Preview +@Composable +@Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) +private fun ChooseAddressBottomSheet_Preview( + @PreviewParameter(ChooseAddressBottomSheetPreviewProvider::class) params: ChooseAddressBottomSheetConfig, +) { + TangemThemePreview { + ChooseAddressBottomSheetContent(params) + } +} + +private class ChooseAddressBottomSheetPreviewProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + ChooseAddressBottomSheetConfig( + addressModels = persistentListOf( + AddressModel( + displayName = stringReference("Main address"), + fullName = stringReference("Bitcoin"), + value = "0x1234...abcd", + type = AddressModel.Type.Default, + ), + AddressModel( + displayName = stringReference("Legacy address"), + fullName = stringReference("Bitcoin"), + value = "0x1234...abcd", + type = AddressModel.Type.Default, + ), + ), + onClick = {}, + ), + ) +} +// endregion \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/chooseaddress/ChooseAddressBottomSheetConfig.kt b/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/chooseaddress/ChooseAddressBottomSheetConfig.kt index 70a3642e18..a02e20a5c1 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/chooseaddress/ChooseAddressBottomSheetConfig.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/chooseaddress/ChooseAddressBottomSheetConfig.kt @@ -1,10 +1,9 @@ package com.tangem.common.ui.bottomsheet.chooseaddress import com.tangem.common.ui.bottomsheet.receive.AddressModel -import com.tangem.common.ui.bottomsheet.receive.TokenReceiveBottomSheetConfig import com.tangem.common.ui.bottomsheet.receive.mapToAddressModels import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent -import com.tangem.domain.models.network.Network +import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.network.NetworkAddress import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList @@ -14,13 +13,12 @@ class ChooseAddressBottomSheetConfig( val onClick: (AddressModel) -> Unit, ) : TangemBottomSheetConfigContent { constructor( - asset: TokenReceiveBottomSheetConfig.Asset, - network: Network, + currency: CryptoCurrency, networkAddress: NetworkAddress, onClick: (AddressModel) -> Unit, ) : this( addressModels = networkAddress.availableAddresses - .mapToAddressModels(asset, network) + .mapToAddressModels(cryptoCurrency = currency) .toImmutableList(), onClick = onClick, ) diff --git a/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/receive/AddressMappers.kt b/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/receive/AddressMappers.kt index f1c9f86b9d..2ad5ab5c2a 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/receive/AddressMappers.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/receive/AddressMappers.kt @@ -1,9 +1,7 @@ package com.tangem.common.ui.bottomsheet.receive -import com.tangem.core.ui.R import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.combinedReference -import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.network.Network @@ -20,20 +18,6 @@ fun Set.mapToAddressModels(cryptoCurrency: CryptoCurrenc cryptoCurrency.network, ) -fun Set.mapToAddressModels( - asset: TokenReceiveBottomSheetConfig.Asset, - network: Network, -): List = when (asset) { - is TokenReceiveBottomSheetConfig.Asset.Currency -> mapToAddressModels( - stringReference("${asset.name} (${asset.symbol})"), - network, - ) - is TokenReceiveBottomSheetConfig.Asset.NFT -> mapToAddressModels( - resourceReference(R.string.common_nft), - network, - ) -} - private fun Set.mapToAddressModels(name: TextReference, network: Network): List = this .sortedBy { it.type.ordinal } diff --git a/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/receive/TokenReceiveBottomSheet.kt b/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/receive/TokenReceiveBottomSheet.kt deleted file mode 100644 index 9093a606aa..0000000000 --- a/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/receive/TokenReceiveBottomSheet.kt +++ /dev/null @@ -1,330 +0,0 @@ -package com.tangem.common.ui.bottomsheet.receive - -import android.content.res.Configuration -import androidx.compose.animation.animateColorAsState -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.LazyRow -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.foundation.pager.HorizontalPager -import androidx.compose.foundation.pager.rememberPagerState -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.Text -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.hapticfeedback.HapticFeedbackType -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalHapticFeedback -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import androidx.compose.ui.unit.dp -import androidx.compose.ui.util.fastForEach -import com.tangem.core.res.getStringSafe -import com.tangem.core.ui.R -import com.tangem.core.ui.components.SecondaryButtonIconStart -import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig -import com.tangem.core.ui.components.notifications.Notification -import com.tangem.core.ui.components.notifications.NotificationConfig -import com.tangem.core.ui.components.rememberQrPainters -import com.tangem.core.ui.components.snackbar.CopiedTextSnackbarHost -import com.tangem.core.ui.extensions.* -import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.res.TangemThemePreview -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf -import kotlinx.coroutines.launch - -@Composable -fun TokenReceiveBottomSheet(config: TangemBottomSheetConfig) { - TangemBottomSheet(config) { content: TokenReceiveBottomSheetConfig -> - TokenReceiveBottomSheetContent(content = content) - } -} - -@Composable -private fun TokenReceiveBottomSheetContent(content: TokenReceiveBottomSheetConfig) { - var selectedAddress by remember { mutableStateOf(content.addresses.first()) } - - val snackbarHostState = remember(::SnackbarHostState) - - ContainerWithSnackbarHost(snackbarHostState = snackbarHostState) { - Column( - modifier = Modifier - .verticalScroll(state = rememberScrollState()) - .padding(top = 24.dp, bottom = 16.dp) - .padding(horizontal = 24.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(space = 24.dp), - ) { - QrCodeContent(content = content, onAddressChange = { selectedAddress = it }) - - Info( - showMemoDisclaimer = content.showMemoDisclaimer, - notifications = content.notifications, - ) - - Buttons( - snackbarHostState = snackbarHostState, - onShareClick = { content.onShareClick(selectedAddress.value) }, - onCopyClick = { content.onCopyClick(selectedAddress.value) }, - ) - } - } -} - -@Composable -private fun Info( - showMemoDisclaimer: Boolean, - notifications: ImmutableList, - modifier: Modifier = Modifier, -) { - Column( - modifier = modifier, - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(space = 16.dp), - ) { - if (showMemoDisclaimer) { - Text( - modifier = Modifier - .padding(horizontal = 18.dp) - .fillMaxWidth(), - text = stringResourceSafe(R.string.receive_bottom_sheet_no_memo_required_message), - style = TangemTheme.typography.caption1, - color = TangemTheme.colors.text.tertiary, - textAlign = TextAlign.Center, - ) - } - - notifications.fastForEach { notificationConfig -> - key(notificationConfig.hashCode()) { - Notification(config = notificationConfig) - } - } - } -} - -@Composable -private fun ContainerWithSnackbarHost(snackbarHostState: SnackbarHostState, content: @Composable () -> Unit) { - Box { - content() - - CopiedTextSnackbarHost( - hostState = snackbarHostState, - modifier = Modifier - .align(Alignment.BottomCenter) - .padding(bottom = 80.dp), - ) - } -} - -@Composable -private fun QrCodeContent(content: TokenReceiveBottomSheetConfig, onAddressChange: (AddressModel) -> Unit) { - val qrCodes = rememberQrPainters(content.addresses.map(AddressModel::value)) - - val pagerState = rememberPagerState( - initialPage = 0, - initialPageOffsetFraction = 0f, - pageCount = content.addresses::count, - ) - - LaunchedEffect(key1 = pagerState.currentPage) { - onAddressChange.invoke(content.addresses[pagerState.currentPage]) - } - - HorizontalPager(state = pagerState) { currentPage -> - QrCodePage( - content = content, - qrCodePainter = qrCodes[currentPage], - currentIndex = currentPage, - ) - } - - if (pagerState.pageCount > 1) { - val indicatorState = rememberLazyListState() - val selectedColor = TangemTheme.colors.icon.primary1 - val unselectedColor = TangemTheme.colors.icon.informative - - LazyRow( - modifier = Modifier.height(20.dp), - state = indicatorState, - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically, - ) { - repeat(pagerState.pageCount) { iteration -> - item(key = iteration) { - val color by animateColorAsState( - targetValue = if (pagerState.currentPage == iteration) selectedColor else unselectedColor, - label = "", - ) - - Box( - modifier = Modifier - .padding(horizontal = 4.dp, vertical = 6.dp) - .background(color = color, shape = CircleShape) - .size(7.dp), - ) - } - } - } - } -} - -@Composable -private fun QrCodePage(content: TokenReceiveBottomSheetConfig, qrCodePainter: Painter, currentIndex: Int) { - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(24.dp), - ) { - Text( - text = stringResourceSafe( - R.string.receive_bottom_sheet_warning_message_compact, - content.addresses[currentIndex].fullName.resolveReference(), - content.network, - ), - color = TangemTheme.colors.text.primary1, - textAlign = TextAlign.Center, - style = TangemTheme.typography.h3, - ) - - Image( - painter = qrCodePainter, - contentDescription = null, - contentScale = ContentScale.Fit, - modifier = Modifier.size(248.dp), - ) - - Text( - text = content.addresses[currentIndex].value, - color = TangemTheme.colors.text.primary1, - textAlign = TextAlign.Center, - style = TangemTheme.typography.subtitle1, - ) - } -} - -@Composable -private fun Buttons( - snackbarHostState: SnackbarHostState, - onShareClick: () -> Unit, - onCopyClick: () -> Unit, - modifier: Modifier = Modifier, -) { - val hapticFeedback = LocalHapticFeedback.current - val coroutineScope = rememberCoroutineScope() - val context = LocalContext.current - val resources = context.resources - - Row( - modifier = modifier, - horizontalArrangement = Arrangement.spacedBy(16.dp), - ) { - SecondaryButtonIconStart( - modifier = Modifier.weight(1f), - text = stringResourceSafe(id = R.string.common_copy), - iconResId = R.drawable.ic_copy_24, - onClick = { - onCopyClick() - - hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) - - coroutineScope.launch { - snackbarHostState.showSnackbar( - message = resources.getStringSafe(R.string.wallet_notification_address_copied), - ) - } - }, - ) - - SecondaryButtonIconStart( - modifier = Modifier.weight(1f), - text = stringResourceSafe(id = R.string.common_share), - iconResId = R.drawable.ic_share_24, - onClick = { - hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) - - onShareClick() - }, - ) - } -} - -// region Preview -@Composable -@Preview(showBackground = true, widthDp = 360) -@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) -private fun Preview_TokenReceiveBottomSheet( - @PreviewParameter(TokenReceiveBottomSheetConfigPreviewProvider::class) params: TokenReceiveBottomSheetConfig, -) { - TangemThemePreview { - val config = TangemBottomSheetConfig( - isShown = true, - content = params, - onDismissRequest = {}, - ) - - TokenReceiveBottomSheet(config) - } -} - -private class TokenReceiveBottomSheetConfigPreviewProvider : PreviewParameterProvider { - val address = AddressModel( - displayName = stringReference("Address 1"), - fullName = combinedReference( - stringReference("Address 1"), - stringReference(" Stellar"), - ), - value = "0xe5178c7d4d0e861ed2e9414e045b501226b0de8d", - type = AddressModel.Type.Default, - ) - private val baseConfig = TokenReceiveBottomSheetConfig( - asset = TokenReceiveBottomSheetConfig.Asset.Currency( - name = "Stellar", - symbol = "XLM", - ), - network = "Ethereum", - notifications = persistentListOf( - NotificationConfig( - title = stringReference("Send only XLM on the Ethereum network"), - subtitle = resourceReference(R.string.receive_bottom_sheet_warning_message_description), - iconResId = R.drawable.ic_alert_circle_24, - ), - ), - addresses = persistentListOf(address), - showMemoDisclaimer = false, - onCopyClick = {}, - onShareClick = {}, - ) - - override val values: Sequence - get() = sequenceOf( - baseConfig, - baseConfig.copy( - addresses = persistentListOf( - address, - address.copy(displayName = stringReference("Address 2")), - ), - ), - baseConfig.copy( - showMemoDisclaimer = true, - ), - baseConfig.copy( - showMemoDisclaimer = true, - addresses = persistentListOf( - address, - address.copy(displayName = stringReference("Address 2")), - ), - ), - ) -} -// endregion Preview \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/receive/TokenReceiveBottomSheetConfig.kt b/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/receive/TokenReceiveBottomSheetConfig.kt deleted file mode 100644 index 5d29be17ec..0000000000 --- a/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/receive/TokenReceiveBottomSheetConfig.kt +++ /dev/null @@ -1,72 +0,0 @@ -package com.tangem.common.ui.bottomsheet.receive - -import androidx.compose.runtime.Immutable -import com.tangem.core.ui.R -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent -import com.tangem.core.ui.components.notifications.NotificationConfig -import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.extensions.stringReference -import com.tangem.core.ui.extensions.wrappedList -import com.tangem.domain.models.network.Network -import com.tangem.domain.models.network.NetworkAddress -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toImmutableList - -data class TokenReceiveBottomSheetConfig( - val asset: Asset, - val network: String, - val addresses: ImmutableList, - val showMemoDisclaimer: Boolean, - val notifications: ImmutableList, - val onCopyClick: (String) -> Unit, - val onShareClick: (String) -> Unit, -) : TangemBottomSheetConfigContent { - - constructor( - asset: Asset, - network: Network, - networkAddress: NetworkAddress, - showMemoDisclaimer: Boolean, - customNotifications: ImmutableList = persistentListOf(), - onCopyClick: (String) -> Unit, - onShareClick: (String) -> Unit, - ) : this( - asset = asset, - network = network.name, - addresses = networkAddress.availableAddresses - .mapToAddressModels(asset, network) - .toImmutableList(), - showMemoDisclaimer = showMemoDisclaimer, - notifications = persistentListOf(defaultAssetNotificationConfig(asset, network)).addAll(0, customNotifications), - onCopyClick = onCopyClick, - onShareClick = onShareClick, - ) - - companion object { - private fun defaultAssetNotificationConfig(asset: Asset, network: Network): NotificationConfig = - NotificationConfig( - title = resourceReference( - R.string.receive_bottom_sheet_warning_title, - wrappedList(asset.displaySymbol, network.name), - ), - subtitle = resourceReference(R.string.receive_bottom_sheet_warning_message_description), - iconResId = R.drawable.ic_alert_circle_24, - iconTint = NotificationConfig.IconTint.Accent, - ) - } - - @Immutable - sealed class Asset { - abstract val displaySymbol: TextReference - - data class Currency(val name: String, val symbol: String) : Asset() { - override val displaySymbol = stringReference(symbol) - } - - data object NFT : Asset() { - override val displaySymbol = resourceReference(R.string.common_nft) - } - } -} \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/navigationButtons/NavigationButtonsBlock.kt b/common/ui/src/main/java/com/tangem/common/ui/navigationButtons/NavigationButtonsBlock.kt index dde935dbec..639f70a3ec 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/navigationButtons/NavigationButtonsBlock.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/navigationButtons/NavigationButtonsBlock.kt @@ -24,6 +24,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import com.tangem.common.ui.navigationButtons.preview.NavigationButtonsPreview +import com.tangem.core.ui.components.HoldToConfirmButton import com.tangem.core.ui.components.Keyboard import com.tangem.core.ui.components.SecondaryButtonIconStart import com.tangem.core.ui.components.buttons.common.TangemButton @@ -98,34 +99,48 @@ fun NavigationPrimaryButton(primaryButton: NavigationButton?, modifier: Modifier modifier = modifier.fillMaxWidth(), ) { button -> if (button != null && button.textReference != TextReference.EMPTY) { - val icon = if (button.iconRes != null && button.isIconVisible) { - TangemButtonIconPosition.End(iconResId = button.iconRes) - } else { - TangemButtonIconPosition.None - } - val color = if (button.isDimmed) { - TangemButtonsDefaults.secondaryButtonColors - .copy(contentColor = TangemTheme.colors.text.tertiary) - } else { - TangemButtonsDefaults.primaryButtonColors - } - TangemButton( - text = button.textReference.resolveReference(), - enabled = button.isEnabled, - onClick = { - GlobalMultipleClickPreventer.processEvent { - if (button.isHapticClick) { - hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) + if (button.isHoldToConfirm) { + HoldToConfirmButton( + text = button.textReference.resolveReference(), + enabled = button.isEnabled, + isLoading = button.shouldShowProgress, + onConfirm = { + GlobalMultipleClickPreventer.processEvent { + button.onClick() } - button.onClick() - } - }, - showProgress = button.shouldShowProgress, - colors = color, - textStyle = TangemTheme.typography.subtitle1, - icon = icon, - modifier = Modifier.fillMaxWidth(), - ) + }, + modifier = Modifier.fillMaxWidth(), + ) + } else { + val icon = if (button.iconRes != null && button.isIconVisible) { + TangemButtonIconPosition.End(iconResId = button.iconRes) + } else { + TangemButtonIconPosition.None + } + val color = if (button.isDimmed) { + TangemButtonsDefaults.secondaryButtonColors + .copy(contentColor = TangemTheme.colors.text.tertiary) + } else { + TangemButtonsDefaults.primaryButtonColors + } + TangemButton( + text = button.textReference.resolveReference(), + enabled = button.isEnabled, + onClick = { + GlobalMultipleClickPreventer.processEvent { + if (button.isHapticClick) { + hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) + } + button.onClick() + } + }, + showProgress = button.shouldShowProgress, + colors = color, + textStyle = TangemTheme.typography.subtitle1, + icon = icon, + modifier = Modifier.fillMaxWidth(), + ) + } } else { Spacer(modifier = Modifier.fillMaxWidth()) } diff --git a/common/ui/src/main/java/com/tangem/common/ui/navigationButtons/NavigationButtonsState.kt b/common/ui/src/main/java/com/tangem/common/ui/navigationButtons/NavigationButtonsState.kt index 6191b4de9c..8b1bebbcd4 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/navigationButtons/NavigationButtonsState.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/navigationButtons/NavigationButtonsState.kt @@ -34,5 +34,6 @@ data class NavigationButton( val isEnabled: Boolean = true, val isDimmed: Boolean = false, val isHapticClick: Boolean = false, + val isHoldToConfirm: Boolean = false, val onClick: () -> Unit, ) \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/userwallet/state/UserWalletItemUM.kt b/common/ui/src/main/java/com/tangem/common/ui/userwallet/state/UserWalletItemUM.kt index 3c59142510..75632fb64a 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/userwallet/state/UserWalletItemUM.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/userwallet/state/UserWalletItemUM.kt @@ -1,6 +1,6 @@ package com.tangem.common.ui.userwallet.state -import com.tangem.common.ui.account.CryptoPortfolioIconUM +import com.tangem.common.ui.account.AccountIconUM import com.tangem.core.ui.components.artwork.ArtworkUM import com.tangem.core.ui.extensions.TextReference import javax.annotation.concurrent.Immutable @@ -64,7 +64,7 @@ data class UserWalletItemUM( data class Account( val name: TextReference, - val icon: CryptoPortfolioIconUM, + val icon: AccountIconUM.CryptoPortfolio, ) : ImageState() data class Image( diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt index 837e466433..8fead6f8ae 100644 --- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt @@ -89,6 +89,7 @@ sealed class AnalyticsParam { data object ImportWallet : ScreensSources("Import Wallet") data object CreateWalletIntro : ScreensSources("Create Wallet Intro") data object AddNewWallet : ScreensSources("Add New Wallet") + data object AddNew : ScreensSources("Add New") data object CreateWallet : ScreensSources("Create Wallet") data object NewsList : ScreensSources("News List") data object NewsLink : ScreensSources("News Link") diff --git a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json index 5dc9541240..299c0abe09 100644 --- a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json @@ -47,10 +47,6 @@ "name": "TANGEM_PAY_ENABLED", "version": "5.31.0" }, - { - "name": "NEW_TOKEN_RECEIVE_ENABLED", - "version": "5.28.0" - }, { "name": "YIELD_SUPPLY_FEATURE_ENABLED", "version": "5.30.0" @@ -82,5 +78,9 @@ { "name": "SWAP_MARKET_LIST_ENABLED", "version": "undefined" + }, + { + "name": "EARN_BLOCK_ENABLED", + "version": "undefined" } ] diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/promotion/models/PromoBannerV2Response.kt b/core/datasource/src/main/java/com/tangem/datasource/api/promotion/models/PromoBannerV2Response.kt new file mode 100644 index 0000000000..23b62757cb --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/promotion/models/PromoBannerV2Response.kt @@ -0,0 +1,10 @@ +package com.tangem.datasource.api.promotion.models + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class PromoBannerV2Response( + @Json(name = "promotions") + val promotions: List, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt index a3b38cb1f0..f9bdbc2aa6 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt @@ -2,6 +2,7 @@ package com.tangem.datasource.api.tangemTech import com.tangem.datasource.api.common.response.ApiResponse import com.tangem.datasource.api.promotion.models.PromoBannerResponse +import com.tangem.datasource.api.promotion.models.PromoBannerV2Response import com.tangem.datasource.api.promotion.models.StoryContentResponse import com.tangem.datasource.api.tangemTech.models.* import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse @@ -188,6 +189,12 @@ interface TangemTechApi { @Query("programName") name: String, @Header("Cache-Control") cacheControl: String = "max-age=600", ): ApiResponse + + @GET("/v2/promotion") + suspend fun getPromoBannersV2( + @Query("walletId") walletId: String, + @Header("Cache-Control") cacheControl: String = "max-age=600", + ): ApiResponse // endregion /** diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/utils/RetrofitApiBuilder.kt b/core/datasource/src/main/java/com/tangem/datasource/di/utils/RetrofitApiBuilder.kt index 2c3b8604a0..f1f6854fce 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/utils/RetrofitApiBuilder.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/utils/RetrofitApiBuilder.kt @@ -18,6 +18,7 @@ import com.tangem.datasource.api.utils.WriteTimeout import com.tangem.datasource.di.NetworkMoshi import com.tangem.datasource.local.logs.AppLogsStore import com.tangem.datasource.utils.NetworkLogsSaveInterceptor +import com.tangem.datasource.utils.WireMockRedirectInterceptor import com.tangem.datasource.utils.addHeaders import dagger.hilt.android.qualifiers.ApplicationContext import okhttp3.Interceptor @@ -77,6 +78,7 @@ internal class RetrofitApiBuilder @Inject constructor( .client( OkHttpClient.Builder() .applyApiConfig(apiConfigId = apiConfigId, environmentConfig = environmentConfig) + .applyWireMockRedirect() .let { if (applyTimeoutAnnotations) it.applyTimeoutAnnotations() else it } @@ -181,6 +183,14 @@ internal class RetrofitApiBuilder @Inject constructor( ) } + private fun OkHttpClient.Builder.applyWireMockRedirect(): OkHttpClient.Builder { + return if (BuildConfig.MOCK_DATA_SOURCE) { + addInterceptor(interceptor = WireMockRedirectInterceptor()) + } else { + this + } + } + private fun OkHttpClient.Builder.addLoggers(apiConfigId: ApiConfig.ID, context: Context): OkHttpClient.Builder { if (apiConfigId in excludedApiForLogging) return this diff --git a/core/datasource/src/main/java/com/tangem/datasource/utils/WireMockRedirectInterceptor.kt b/core/datasource/src/main/java/com/tangem/datasource/utils/WireMockRedirectInterceptor.kt new file mode 100644 index 0000000000..b246a027cb --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/utils/WireMockRedirectInterceptor.kt @@ -0,0 +1,40 @@ +package com.tangem.datasource.utils + +import okhttp3.Interceptor +import okhttp3.Response +import timber.log.Timber + +/** + * OkHttp interceptor that redirects requests from wiremock.tests-d.com to a local WireMock instance. + */ +class WireMockRedirectInterceptor : Interceptor { + + override fun intercept(chain: Interceptor.Chain): Response { + val override = overriddenBaseUrl ?: return chain.proceed(chain.request()) + + val request = chain.request() + val url = request.url.toString() + + if (url.contains(WIREMOCK_REMOTE_URL)) { + val newUrl = url.replace(WIREMOCK_REMOTE_URL, override.trimEnd('/')) + Timber.d("WireMockRedirect: $url -> $newUrl") + val newRequest = request.newBuilder() + .url(newUrl) + .build() + return chain.proceed(newRequest) + } + + return chain.proceed(request) + } + + companion object { + private const val WIREMOCK_REMOTE_URL = "[REDACTED_ENV_URL]" + + /** + * Override base URL for WireMock requests. + * When null (default), requests go to wiremock.tests-d.com. + * When set (e.g., "http://localhost:8080"), requests are redirected to local WireMock instance. + */ + var overriddenBaseUrl: String? = null + } +} \ No newline at end of file diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 2a9bd1b3eb..4175f06e1c 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -899,6 +899,9 @@ Krypto mit SEPA kaufen Trage dich in die Warteliste ein und erhalte eine Zahlungskarte, die es so noch nie gab. Tangem Visa Card + Bedingungen + Zahlen Sie mindestens $100 ein, halten Sie den Betrag 30 Tage und erhalten Sie $10. + Yield-Mode-Kampagne Du musst einen einzigen Zugangscode einrichten, um alle deine Geräte zu schützen Schützen Du kannst später auf jeder Karte oder Ring einen individuellen Zugangscode einrichten diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml index 901dbaefc1..c3b8bb8a6a 100644 --- a/core/res/src/main/res/values-es/strings.xml +++ b/core/res/src/main/res/values-es/strings.xml @@ -299,6 +299,7 @@ Ir al token Entendido Ocultar + Mantener para %s hora Importe En progreso @@ -323,6 +324,7 @@ No Ninguna dirección No agregadas + No disponible Ahora no Ahora OK @@ -373,6 +375,7 @@ Intercambiar Tangem Tangem Wallet + Toque y mantenga presionado términos y condiciones Condiciones de uso A @@ -462,6 +465,11 @@ Enviar activos a otras redes resultará en una pérdida permanente. Red de %s Envíe fondos utilizando solo + Las mejores oportunidades + Todas las redes + Todos los tipos + Mayormente usado + Ganar Hola equipo de soporte, he encontrado un error con el código: %s Error de WalletConnect Ha usado una tarjeta de otra billetera. Toque la tarjeta asociada con esta billetera @@ -561,6 +569,8 @@ Comentarios en Tangem No se puede completar una transacción Error de descripción de moneda + Fondos insuficientes + Tarifa de transacción Ocurrió un error Ocurrió un error. Código: %s Requiere memo @@ -610,6 +620,7 @@ Finalizar la copia de seguridad primero Incompleto Otros métodos + Guarde su frase de recuperación en un lugar seguro y manténgala privada para proteger sus fondos y configure un código de acceso para mayor seguridad. Guarde su frase de recuperación en un lugar seguro y manténgala en privado para proteger sus fondos. Frase de recuperación Para proteger su billetera con un código de acceso, complete el proceso de copia de seguridad. @@ -896,6 +907,9 @@ Comprar criptomonedas con SEPA Únete a la lista de espera y consigue una tarjeta de pago como ninguna otra. Tangem Visa Card + Términos y condiciones + Deposite $100 o más, manténgalo durante 30 días y reciba $10. + ¡Únase a la campaña Yield Mode! Configure un código de acceso único para proteger todos sus dispositivos. Proteger Puede configurar un código de acceso individual en cada tarjeta más adelante @@ -995,6 +1009,7 @@ Restaurar código de acceso Tarjetas idénticas Código de acceso + Solo puede tener una billetera móvil. Actualícela a la billetera de hardware Tangem o añada una nueva billetera de hardware. Todas las ofertas Disponible con %s La compra de criptomonedas en Tangem se lleva a cabo utilizando proveedores externos. @@ -1266,6 +1281,7 @@ La cantidad de staking se redondeará a %1$s TRX debido a las reglas de la red. La cantidad de unstaking se redondeará a %1$s TRX debido a las reglas de la red. APR %1$s%% + Sus recompensas de staking comenzarán después de 5 épocas (~25 días) mientras su delegación esté registrada y contabilizada por la red. Reclamar lo que no está en staking Tarifa de staking de la cuenta Una cuenta de staking es una cuenta especial donde se almacenan los tokens SOL de staking. Se crea cuando delegas sus tokens a un validador para participar en la validación de transacciones y ganar recompensas. Se cobra una pequeña tarifa por crear la cuenta de staking, que se devuelve una vez que se completa el staking. @@ -1276,6 +1292,7 @@ APR APY Las recompensas se acumulan automáticamente en su saldo de staking diariamente. + Las recompensas se acumulan en su saldo de staking. Fondos ganados: %s Disponible Tasa de recompensa promedio ¿Cómo funciona el staking? @@ -1420,6 +1437,7 @@ Sencillo e intuitivo, permite cambiar tokens con solo unos cuantos toques Simplemente cómodo Intercambio a través del proveedor + Sus activos La cantidad incluye:\n• Tarifas del proveedor de servicios\n• Tarifas de red por enviar %s desde el intercambio a la dirección del usuario. El importe incluye:\n- comisión del proveedor de servicios\n- comisión de red por el envío de %1$s desde el exchange de vuelta a la dirección del usuario. \n\nEl deslizamiento (slippage) del proveedor es de hasta %2$s El importe incluye los honorarios del proveedor de servicios. @@ -1434,6 +1452,7 @@ Fondos insuficientes Dar autorización Intercambiar + Intercambiando... Usted recibe Elige token no disponible @@ -1452,6 +1471,9 @@ Mantén tu dinero seguro. Puedes desbloquear en cualquier momento. Tu tarjeta está congelada. Obtener ayuda + Razón: %s + %s · %s + MCC %s Otro No se puede usar en dispositivos rooteados Completado @@ -1528,10 +1550,15 @@ ¿Seguro que desea detener el proceso KYC? Puede retomarlo en cualquier momento. No pudimos verificar tu perfil. Si tienes alguna pregunta, contacta con el soporte. Lamentablemente, no pudimos verificar tu identidad + El proceso KYC ha fallado KYC en curso Ver estado KYC en progreso para Tangem Pay Los documentos suelen verificarse automáticamente en menos de 5 minutos. En casos excepcionales que requieran revisión manual, el proceso puede tardar hasta 48 horas. + KYC rechazado + Ocultar el bloque KYC + Lo sentimos, no pudimos verificarle + su perfil. Obtén tu tarjeta virtual Tangem Visa gratuita Usa USDC para pagos cotidianos Obtener tarjeta @@ -1818,7 +1845,17 @@ Entendido ¡Realmente genial! Actualizar + Iniciar migración + Copiar + La firma de mensajes no es compatible con esta red + No se puede firmar el mensaje. Por favor, inténtelo de nuevo. Según la documentación oficial de Clore, todas las monedas recibidas antes del 21 de diciembre serán migradas a Clore (token ERC-20); las monedas recibidas después de esa fecha no lo serán. Se está desarrollando una solución de transferencia — mantente atento. + Mensaje + Abrir el portal de reclamaciones + Para mantener el acceso a sus fondos, comience la migración de acuerdo con las pautas oficiales de Clore. + Migración de la red Clore + Firmar + Firma Migración de la red Clore Actualmente estás en el modo Demo Modo demo activo diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index f0253cda22..d88cdd4e57 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -43,6 +43,7 @@ Ajouter un compte Enregistrer Nom du compte + Un compte portant ce nom existe déjà. Veuillez choisir un autre nom. Nom de compte déjà utilisé Compte Nouveau compte @@ -298,6 +299,7 @@ Aller au jeton Compris Cacher + Maintenir contre %s heure Importez En cours @@ -322,6 +324,7 @@ Non Aucune adresse Non ajouté + Indisponible Pas maintenant Maintenant OK @@ -372,6 +375,7 @@ Échanger Tangem Tangem Wallet + Appuyez et maintenez enfoncé termes et conditions Conditions d\'utilisation À @@ -399,6 +403,8 @@ Mode Rendement Adresse du contrat copiée ! Réseaux disponibles + La dérivation de votre token correspond à la dérivation de %1$s. Votre token sera ajouté à ce compte. + La dérivation appartient à un autre compte. Jeton ajouté au compte %1$s Adresse du contrat L\'adresse du contrat n\'est pas valide @@ -459,6 +465,11 @@ L’envoi d’actifs sur d’autres réseaux entraînera une perte définitive. %s réseau Envoyez des fonds en utilisant uniquement + Meilleures opportunités + Tous les réseaux + Tous les types + Principalement utilisé + Gagner Bonjour équipe de support, j’ai rencontré une erreur avec le code : %s Erreur WalletConnect Vous avez utilisé une carte d\'un autre portefeuille. Appuyez sur la carte associée à ce portefeuille @@ -558,6 +569,8 @@ Commentaires sur Tangem Impossible d\'effectuer une transaction Erreur de description de la pièce + Fonds insuffisants + Frais de transaction Une erreur s\'est produite Une erreur s\'est produite. Code:%s Un mémo est requis @@ -607,6 +620,7 @@ Finalisez d\'abord la sauvegarde. Incomplet Autres méthodes + Conservez votre seed phrase dans un endroit sûr et gardez-la confidentielle afin de protéger vos fonds, puis configurez un code d\'accès pour plus de sécurité. Conservez votre seed phrase dans un endroit sûr et gardez-la confidentielle afin de protéger vos fonds. Seed phrase Pour sécuriser votre wallet avec un code d\'accès, effectuez la procédure de sauvegarde. @@ -644,7 +658,7 @@ Une erreur s\'est produite pendant l\'opération. Vos fonds restent en sécurité et entièrement accessibles pendant toute la durée du processus. Accès aux fonds - Après la mise à niveau, votre portefeuille mobile sera supprimé de l\'application et stocké sur votre hardware wallet; votre seedphrase restera en votre possession. + Après la mise à niveau, votre portefeuille mobile sera supprimé de l\'application et stocké sur votre hardware wallet; votre seed phrase restera en votre possession. Sécurité générale Les clés privées seront transférées de l\'application vers votre hardware wallet Tangem. Migration des clés @@ -893,6 +907,9 @@ Acheter des cryptomonnaies avec SEPA Rejoignez la liste d’attente et obtenez une carte de paiement pas comme les autres. Tangem Visa Card + Conditions générales + Déposez 100 $ ou plus, conservez le dépôt pendant 30 jours et recevez 10 $. + Rejoignez la campagne Yield Mode ! Vous devez définir un seul code d\'accès pour protéger tous vos appareils. Protéger Vous pourrez définir un code d\'accès individuel sur chaque carte plus tard @@ -992,6 +1009,7 @@ Restauration du code d\'accès Cartes identiques Code d\'accès + Vous ne pouvez avoir qu\'un seul portefeuille mobile. Passez au portefeuille matériel Tangem ou ajoutez un nouveau portefeuille matériel. Toutes les offres Disponible avec %s L\'achat de crypto-monnaies sur Tangem est assuré par des fournisseurs tiers selon leurs conditions. @@ -1036,6 +1054,7 @@ Disponible auprès de Disponible jusqu\'à Vous obtenez + Le service est fourni par un prestataire externe. Tangem n\'est pas responsable. Vous pouvez vérifier l\'état de la transaction à partir de la page du jeton Jusqu\'à Via @@ -1248,18 +1267,21 @@ Cela supprimera le portefeuille de l\'application. Le portefeuille lui-même peut être ajouté à nouveau. Facile à utiliser Gardez vos cryptomonnaies en sécurité et hors ligne. Aussi fin qu\'une carte de crédit, plus sûr qu\'un coffre-fort bancaire. - Pas de seedphrase + Pas de seed phrase Le meilleur hardware wallet Tangem Hardware Wallet Nom Faites travailler votre jeton pour vous + Les frais de réseau sont une petite somme à payer pour traiter et confirmer ta transaction sur la blockchain. Pour commencer à staker, votre compte TON doit être activé avec une transaction de 1 TON. Les fonds restent sur votre compte, car cette étape sert uniquement à l\'activer pour le staking. + Activation du compte Les frais de réseau ont changé. Veuillez vérifier le nouveau montant avant de continuer. Frais de réseau mis à jour Le montant à staker doit être au moins %s Le montant du staking sera arrondi à %1$s TRX en raison des règles du réseau. Le montant d\'annulation du staking sera arrondi à %1$s TRX en raison des règles du réseau. TAEG %1$s%% + Vos récompenses de staking commenceront après 5 époques (~25 jours), une fois que votre délégation aura été enregistrée et comptabilisée par le réseau. Réclamation déstakée Frais de staking du compte Un compte de staking est un compte spécial où sont stockés les jetons SOL stakés. Il est créé lorsque vous déléguez vos jetons à un validateur pour participer à la validation des transactions et gagner des récompenses. Des frais minimes sont facturés pour la création du compte de staking, qui sont restitués une fois le staking terminé. @@ -1270,6 +1292,7 @@ APR APY Les récompenses s\'accumulent automatiquement sur votre solde de staking quotidiennement. + Les récompenses sont ajoutées à votre solde de staking. Fonds gagnés : %s Disponible Taux de récompense moyen Qu\'est-ce que le Staking ? @@ -1325,6 +1348,7 @@ L\'option de restaker vous permet de déplacer vos fonds d\'un validateur à un autre sans avoir besoin de les déstaker. Vous êtes sur le point de staker l\'intégralité de votre solde. Nous vous recommandons de laisser un petit montant pour couvrir les frais de réseau pour unstaking ou la réclamation des récompenses. Pour commencer le staking, vous devez d’abord activer votre compte TON + Activation du compte Pour commencer à staker TON, effectuez d\'abord une transaction sortante de n\'importe quel montant — cela activera votre portefeuille. Jusqu\'à 0,2 TON peuvent être requis en plus des frais de réseau pour terminer la transaction. Tout montant non utilisé sera remboursé. 0,2 TON requis pour effectuer cette opération, en plus des frais de réseau. Veuillez recharger votre solde. @@ -1413,6 +1437,7 @@ Simple et intuitif, vous permettant d\'échanger des jetons en quelques clics Plus Simple Que Jamais Échange via le fournisseur + Vos actifs Le montant comprend :\n• les frais du fournisseur de services\n• les frais de réseau pour l\'envoi de %s depuis l\'échange vers l\'adresse de l\'utilisateur. Le montant comprend :\n• les frais du fournisseur de services\n• les frais de réseau pour l\'envoi de %1$s depuis l\'échange vers l\'adresse de l\'utilisateur. \n\nLe slippage du fournisseur peut atteindre %2$s Le montant comprend les frais du fournisseur de services. @@ -1427,6 +1452,7 @@ Fonds insuffisants Donner l\'autorisation Échanger + Échange... Vous recevez Choisir le jeton non disponible @@ -1445,6 +1471,9 @@ Geler Votre carte est gelée. Obtenir de l\'aide + Raison: %s + %s · %s + MCC %s Autre Impossible à utiliser sur les appareils rootés Terminé @@ -1521,10 +1550,15 @@ Êtes-vous sûr de vouloir interrompre le processus KYC ? Vous pouvez y revenir à tout moment. Nous n\'avons pas pu vérifier votre profil. Pour toute question, veuillez contacter le support. Malheureusement, nous n\'avons pas pu vérifier votre identité + KYC a échoué KYC en cours Voir le statut KYC en cours pour Tangem Pay Les documents sont généralement vérifiés automatiquement en moins de 5 minutes. Dans de rares cas nécessitant une vérification manuelle, le processus peut prendre jusqu’à 48 heures. + KYC rejeté + Masquer le bloc KYC + Désolé, nous n\'avons pas pu vérifier. + votre profil. Obtenez votre carte virtuelle Tangem Visa gratuite Utilisez USDC pour les paiements quotidiens Obtenir la carte @@ -1579,6 +1613,7 @@ Jeton dans le %%image%% %1$s réseau Le jeton %1$s (%2$s) est la principale devise du réseau %3$s et ne peut pas être masqué tant que vous avez d\'autres jetons de ce réseau dans la liste Impossible de masquer %s + Afficher le code QR Échangez ce jeton contre un autre moyennant des frais de service de %1$s du %2$s au %3$s février. Échangez avec Changelly, %s frais Échangez maintenant @@ -1710,6 +1745,12 @@ Scannez votre carte pour déverrouiller l\'accès Déverrouillage nécessaire Choisissez comment ajouter votre portefeuille + Scannez votre carte ou votre bague Tangem pour la restaurer ou l\'importer depuis un autre portefeuille. + Créer un Hardware Wallet + Vous souhaitez acheter un portefeuille Tangem ? + Importer la seed phrase + Restaurez votre portefeuille sur votre téléphone ou importez-le depuis une autre application — pratique, mais moins sécurisé qu\'une carte Tangem. + Que choisir ? La blockchain n\'est pas accessible. Réessayez plus tard Scanner la carte ou la bague Ce portefeuille a déjà été activé auparavant.\nSi cela n\'a pas été fait par vous, veuillez contacter le support.\nTangem ne vend jamais de portefeuilles avec le code d\'accès pré-généré. @@ -1766,9 +1807,9 @@ Ajouter un portefeuille existant Dispositifs physiques qui stockent votre clé privée hors ligne en toute sécurité. Scanner un Tangem Wallet - Importez un portefeuille existant à l\'aide de votre seedphrase. + Importez un portefeuille existant à l\'aide de votre seed phrase. Importer un wallet - Entrez la seedphrase + Entrez la seed phrase Vous avez importé votre portefeuille avec succès. Importer un wallet Importation terminée @@ -1984,7 +2025,7 @@ Livraison rapide Commencez en un seul clic Sans faille et sécurisé - Pas de seedphrase + Pas de seed phrase Facile à utiliser Créez un harware wallet avec Tangem. Aussi fin qu\'une carte bancaire, aussi sûr qu\'un coffre-fort. Créer ou importer un portefeuille logiciel @@ -2105,4 +2146,5 @@ Impossible de couvrir les frais %s Le mode rendement n\'est pas disponible pour le moment. Veuillez réessayer plus tard. Le mode Rendement est indisponible + Impossible de charger le graphique... diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index cae90804ea..770eb98276 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -294,6 +294,7 @@ トークンへ移動 わかりました 非表示 + %sまで長押し 時間 インポート 進行中 @@ -368,6 +369,7 @@ スワップ Tangem Tangem Wallet + タップして長押し 利用規約 利用規約 宛先 @@ -456,6 +458,11 @@ 他のネットワークで資産を送金すると、永久に失われます。 %sネットワーク 下記のみを使用して資金を送金する + おすすめ + すべてのネットワーク + すべての種類 + よく使われています + 運用 こんにちは、サポートチームの皆さん、コード %s のエラーが発生しました。 WalletConnectエラー 別のウォレットのカードまたはリングを使用しました。このウォレットにリンクしているカードまたはリングをタップしてください。 @@ -887,6 +894,9 @@ SEPAで暗号資産を購入 事前申し込み受付中。他にない特別なカードを、いち早く体験しよう。 Tangem Visaカード + 利用規約 + 100ドル以上を入金し、30日間保持すると、10ドルを受け取れます。 + Yield Mode キャンペーンに参加しよう! すべてのデバイスを保護するには、単一のアクセスコードを設定してください。 保護する 後で各カードおよびリングに個別のアクセスコードを設定できます。 @@ -935,11 +945,11 @@ 生体認証 シードフレーズについてもっと読む - これらの %d 単語を以下の順番通りに書き留め、安全かつ秘密の場所に保管してください。 + 以下に表示される%d個の単語を順番どおりに書き留め、安全で他人に知られない場所に保管してください。 あなたのシードフレーズ - %d 単語 + %d個の単語 ウォレットをインポートするには、下のフィールドにシードフレーズを入力してください。 シードフレーズを生成する @@ -1252,6 +1262,7 @@ ネットワークルールにより、ステーキング金額は%1$s TRX に切り上げられます。 ネットワークルールにより、ステーキング解除の量は%1$s TRX に切り上げられます。 APR %1$s%% + デリゲーションがネットワークに登録され、集計されるまでの間、ステーキング報酬は5エポック後(約25日後)から開始されます。 ステーキング解除分を請求する ステーキングアカウント手数料 ステーキングアカウントとは、ステーキングされたSOLが保管される特別なアカウントです。トークンをバリデーターに委任し、取引の検証に参加して報酬を受け取る際に作成されます。ステーキングアカウントの作成には少額の手数料がかかりますが、ステーキング完了後に返金されます。 @@ -1441,6 +1452,9 @@ 一時停止 カードが凍結されています サポートを受ける + 理由:%s + %s・%s + MCC %s その他 Root化された端末では使用できません 完了 @@ -1643,25 +1657,69 @@ ウォレット名の変更 すべてのロックを解除 %sですべてをロック解除 + AML認証済み + 利用可能 + ブロック中 + 負債 + 利用上限 + その他(OTPなし) + 単一の取引 + 合計 %d日間利用可能 + 利用可能残高は、保留中の取引、ブロック中の金額、デビット残高を考慮した、実際に利用できる資金を示します。 + %1$sまで利用可能 残高と限度額 + 支払いアカウントの情報が見つかりません。サポートにお問い合わせください。 + トークンの情報を読み込めません。 + 情報を読み込み中です。しばらくお待ちください。 + コストの管理、セキュリティの向上、リスク管理のために利用上限が設けられています。店舗でのカード決済では期間内に%1$s、サブスクリプションや支払いなどのその他の取引では%2$sまで利用できます。 アクセスコードは、支払いアカウントの管理および不正アクセスからの保護に使用されます。 アクセスコード + アカウント有効化 + 登録手続きを開始したウォレットを選択し、ブロックチェーン上でアカウントを作成するための取引に署名してください。 + 有効化をキャンセル 本当に終了してもよろしいですか?中断したところから後で続行できます。 長くはかかりません。アカウントを設定しています。 長くはかかりません。アクティベーションを完了しています。 準備完了です! + その他のウォレット 4桁のコードを設定します。 \nお支払いの際に使用されます。 + PINコード PINコードを作成する PINの認証に失敗しました。もう一度お試しいただくか、別のコードを使用してください。 無効な暗証番号:連続や繰り返しを避けてください + 準備完了です! + Tangemカードを用意し、タップして承認してください。 + Tangemウォレットを準備してください。 + \nサードパーティのWebサイト\nで接続手続きを完了し、その後Tangemアプリに戻ってください。 ウェブサイトに移動 + ウォレット接続 + ウォレットを選択 + 有効化を続行 アカウントの設定を続けましょう。 お帰りなさい! + 有効化を開始 手順に従ってアカウントを設定してください。 ようこそ! + ブロックチェーン上の金額 + 通貨コード + 日付 + エラーコード + 取引詳細 + 加盟店カテゴリーコード + 加盟店の所在地 + 加盟店の国コード + 加盟店名 + リクエストID + ステータス + 取引 + 取引金額 + トランザクションハッシュ + 取引リクエスト + 取引ステータス + 種類 この取引に異議を唱える ロック解除 カードをスキャンしてアクセスロックを解除する diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 45f190c03a..b0e42bdf47 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -925,6 +925,9 @@ Покупайте крипту через SEPA Присоединяйтесь к списку ожидания и получите уникальную платёжную карту, не похожую ни на одну другую. Tangem Visa Card + Условия программы + Внесите депозит от $100, удерживайте его 30 дней и получите бонус $10 + Подключите Режим доходности! Установите единый код доступа для защиты всех ваших карт или колец Защита Установите индивидуальный код доступа для каждой карты или кольца позже. @@ -1800,7 +1803,17 @@ Понятно! Очень круто! Обновить + Начать миграцию + Копировать + Подписание сообщений не поддерживается в этой сети + Невозможно подписать сообщение. Пожалуйста, попробуй позже. Согласно официальной документации Clore, все монеты, полученные до 21 декабря, будут мигрированы в токен Clore (ERC-20); монеты, полученные после этой даты, — нет. Решение для перевода находится в разработке — следите за обновлениями. + Сообщение + Открыть портал клейма + Чтобы сохранить доступ к своим средствам, начните миграцию в соответствии с официальной инструкцией Clore. + Миграция сети Clore + Подписать + Подпись Миграция сети Clore Вы находитесь в режиме демо Демо режим включен diff --git a/core/res/src/main/res/values-uk-rUA/strings.xml b/core/res/src/main/res/values-uk-rUA/strings.xml index 61ee270e13..67360c1ce3 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -44,6 +44,7 @@ Зберегти Назва акаунту Акаунт з такою назвою вже існує. Будь ласка, виберіть іншу назву. + Обліковий запис з таким ім’ям уже існує Акаунт Новий акаунт Додати акаунт @@ -908,6 +909,9 @@ Купуйте криптовалюту через SEPA Приєднуйтесь до списку очікування та отримайте унікальну платіжну картку, якої ще не було. Tangem Visa Card + Умови та положення + Внесіть депозит від $100, утримуйте його 30 днів та отримайте $10. + Кампанія Yield Mode! Налаштуйте єдиний код доступу для захисту всіх ваших карток або кілець Захист Встановіть індивідуальний код доступу для кожної картки або кільця пізніше. diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index f583c64401..ed8c132108 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -86,6 +86,8 @@ Send only %1$s (%2$s) from %3$s network to this address. Using other tokens and networks may result in loss of funds. Default Legacy + Try to reset biometrics on your device or contact support + Authentication error How to scan Request support Try again @@ -908,6 +910,9 @@ Buy Crypto with SEPA Join the waitlist and get a payment card unlike any other Tangem Visa Card + Terms and conditions + Deposit $100+, hold for 30 days, get $10 + Join the Yield Mode Campaign! Set up a single access code to protect all your devices. Protect Set an individual access code for each card or ring later. @@ -1843,7 +1848,18 @@ Ok, Got it! Really cool! Refresh + Start migration + Copy + To keep access to your funds, start the migration according to the official Clore guidelines. + Message signing is not supported for this network + Unable to sign message. Please try again. According to Clore’s official documentation, all coins received before December 21 will be migrated to Clore (ERC-20 token); coins received after that date will not. A transfer solution is coming — stay tuned. + Message + Open Claim portal + To continue using your Clore tokens, you must complete the token migration according to the information on the Claim Portal. + Clore Network Migration + Sign + Signature Clore Network Migration You are currently in the Demo mode Demo mode active diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt index d1cf7a2a93..52caca9648 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt @@ -15,6 +15,7 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.R import com.tangem.core.ui.components.buttons.common.* +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview @@ -320,9 +321,9 @@ fun SelectorButton(text: String, onClick: () -> Unit, modifier: Modifier = Modif @Composable fun HoldToConfirmButton( text: String, - hintText: String, onConfirm: () -> Unit, modifier: Modifier = Modifier, + hintText: String = stringResourceSafe(R.string.common_tap_and_hold_hint), enabled: Boolean = true, isLoading: Boolean = false, ) { diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/account/AccountIcon.kt b/core/ui/src/main/java/com/tangem/core/ui/components/account/AccountIcon.kt index dd5ea8c8a6..939eac7407 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/account/AccountIcon.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/account/AccountIcon.kt @@ -6,22 +6,20 @@ import androidx.compose.animation.animateContentSize import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween +import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Button import androidx.compose.material3.Icon import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp @@ -76,6 +74,33 @@ fun AccountResIcon(@DrawableRes resId: Int, color: Color, size: AccountIconSize, } } +/** + * Displays a PaymentAccount icon using a predefined painter resource. + * + * The icon size is adapted based on the provided [size]. + * + * @param size The size of the icon, defined by [AccountIconSize]. + */ +@Composable +fun PaymentAccountIcon(size: AccountIconSize, modifier: Modifier = Modifier) { + val boxSize by animateDpAsState( + targetValue = size.boxSizeInDp(), + animationSpec = animation(), + ) + val boxShapeCornerSize by animateDpAsState( + targetValue = size.boxShapeSizeInDp(), + animationSpec = animation(), + ) + Box( + contentAlignment = Alignment.Center, + modifier = modifier + .size(boxSize) + .clip(RoundedCornerShape(boxShapeCornerSize)), + ) { + Image(painter = painterResource(id = R.drawable.img_visa_36), contentDescription = null) + } +} + /** * Displays a portfolio icon using a single character. * @@ -186,6 +211,7 @@ private fun Sample() { AccountResIcon(resId = R.drawable.ic_user_24, color = Color.Magenta, size = AccountIconSize.Medium) AccountResIcon(resId = R.drawable.ic_family_24, color = Color.DarkGray, size = AccountIconSize.Small) AccountResIcon(resId = R.drawable.ic_wallet_24, color = Color.Green, size = AccountIconSize.ExtraSmall) + PaymentAccountIcon(size = AccountIconSize.Default) } Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) { AccountCharIcon(char = 'D', color = Color.Red, size = sizeState) diff --git a/data/account/src/main/kotlin/com/tangem/data/account/fetcher/DefaultWalletAccountsFetcher.kt b/data/account/src/main/kotlin/com/tangem/data/account/fetcher/DefaultWalletAccountsFetcher.kt index 12c40f608d..68bce0a025 100644 --- a/data/account/src/main/kotlin/com/tangem/data/account/fetcher/DefaultWalletAccountsFetcher.kt +++ b/data/account/src/main/kotlin/com/tangem/data/account/fetcher/DefaultWalletAccountsFetcher.kt @@ -87,6 +87,15 @@ internal class DefaultWalletAccountsFetcher @Inject constructor( store.updateData { response } } + override suspend fun update( + userWalletId: UserWalletId, + transform: (GetWalletAccountsResponse?) -> GetWalletAccountsResponse?, + ) { + val store = getAccountsResponseStore(userWalletId = userWalletId) + + store.updateData { transform(it) } + } + override suspend fun push( userWalletId: UserWalletId, accounts: List, diff --git a/data/common/src/main/kotlin/com/tangem/data/common/account/WalletAccountsSaver.kt b/data/common/src/main/kotlin/com/tangem/data/common/account/WalletAccountsSaver.kt index de6c29ccc4..09c0ea5fa7 100644 --- a/data/common/src/main/kotlin/com/tangem/data/common/account/WalletAccountsSaver.kt +++ b/data/common/src/main/kotlin/com/tangem/data/common/account/WalletAccountsSaver.kt @@ -15,6 +15,11 @@ interface WalletAccountsSaver { /** Store wallet accounts [response] by [userWalletId] */ suspend fun store(userWalletId: UserWalletId, response: GetWalletAccountsResponse) + suspend fun update( + userWalletId: UserWalletId, + transform: (GetWalletAccountsResponse?) -> GetWalletAccountsResponse?, + ) + /** Push wallet accounts [body] by [userWalletId] */ @Throws suspend fun push(userWalletId: UserWalletId, body: SaveWalletAccountsResponse): GetWalletAccountsResponse? diff --git a/data/promo/src/main/java/com/tangem/data/promo/DefaultPromoRepository.kt b/data/promo/src/main/java/com/tangem/data/promo/DefaultPromoRepository.kt index 31133bb96f..625fdc2535 100644 --- a/data/promo/src/main/java/com/tangem/data/promo/DefaultPromoRepository.kt +++ b/data/promo/src/main/java/com/tangem/data/promo/DefaultPromoRepository.kt @@ -66,6 +66,11 @@ internal class DefaultPromoRepository( PromoId.OnePlusOne -> { val isActive = getOnePlusOnePromoBanner()?.isActive == true + isActive && shouldShow + } + PromoId.YieldPromo -> { + val isActive = getYieldPromoBanner(userWalletId)?.isActive == true + isActive && shouldShow } } @@ -79,6 +84,7 @@ internal class DefaultPromoRepository( PromoId.VisaPresale -> flowOf(false) PromoId.BlackFriday -> flowOf(false) PromoId.OnePlusOne -> flowOf(false) + PromoId.YieldPromo -> flowOf(false) } } @@ -190,12 +196,22 @@ internal class DefaultPromoRepository( }.getOrNull() } + private suspend fun getYieldPromoBanner(userWalletId: UserWalletId): PromoBanner? { + return runCatching(dispatchers.io) { + val response = tangemApi.getPromoBannersV2(userWalletId.stringValue).getOrThrow() + val yieldPromotion = response.promotions.find { it.name == YIELD_PROMO_NAME } + ?: return@runCatching null + promoBannerConverter.convert(yieldPromotion) + }.getOrNull() + } + private companion object { const val SEPA_NAME = "sepa" const val VISA_NAME = "visa-waitlist" const val BLACK_FRIDAY_NAME = "black-friday" const val MOONPAY_NAME = "moonpay" const val ONE_PLUS_ONE_NAME = "one-plus-one" + const val YIELD_PROMO_NAME = "promo-yield" const val STORIES_LOAD_DELAY = 1000L } } \ No newline at end of file diff --git a/data/wallet-manager/src/main/java/com/tangem/data/walletmanager/DefaultWalletManagersFacade.kt b/data/wallet-manager/src/main/java/com/tangem/data/walletmanager/DefaultWalletManagersFacade.kt index 788165be89..b2b04e77fa 100644 --- a/data/wallet-manager/src/main/java/com/tangem/data/walletmanager/DefaultWalletManagersFacade.kt +++ b/data/wallet-manager/src/main/java/com/tangem/data/walletmanager/DefaultWalletManagersFacade.kt @@ -22,6 +22,7 @@ import com.tangem.blockchain.transactionhistory.models.TransactionHistoryRequest import com.tangem.blockchain.yieldsupply.YieldSupplyContractCallDataProviderFactory import com.tangem.blockchainsdk.BlockchainSDKFactory import com.tangem.blockchainsdk.models.UpdateWalletManagerResult +import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.blockchainsdk.utils.toBlockchain import com.tangem.blockchainsdk.utils.toNetworkId import com.tangem.crypto.hdWallet.DerivationPath @@ -629,6 +630,22 @@ internal class DefaultWalletManagersFacade @Inject constructor( ) } + override suspend fun getNativeTokenBalance( + userWalletId: UserWalletId, + networkId: String, + derivationPath: String?, + ): BigDecimal { + val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" } + val walletManager = getOrCreateWalletManager(userWalletId, blockchain, derivationPath) + + return walletManager?.wallet?.amounts + ?.firstNotNullOfOrNull { amountEntry -> + amountEntry.takeIf { amountEntry.key is AmountType.Coin } + } + ?.value?.value + ?: BigDecimal.ZERO + } + override suspend fun getAssetRequirements( userWalletId: UserWalletId, currency: CryptoCurrency, diff --git a/domain/models/src/main/kotlin/com/tangem/domain/models/wallet/UserWallet.kt b/domain/models/src/main/kotlin/com/tangem/domain/models/wallet/UserWallet.kt index 8f2b0572ff..5df91b67b1 100644 --- a/domain/models/src/main/kotlin/com/tangem/domain/models/wallet/UserWallet.kt +++ b/domain/models/src/main/kotlin/com/tangem/domain/models/wallet/UserWallet.kt @@ -108,4 +108,6 @@ val UserWallet.isLocked get() = when (this) { is UserWallet.Cold -> isLocked is UserWallet.Hot -> isLocked - } \ No newline at end of file + } + +inline val UserWallet.isHotWallet get() = this is UserWallet.Hot \ No newline at end of file diff --git a/domain/promo/models/src/main/java/com/tangem/domain/promo/models/PromoBanner.kt b/domain/promo/models/src/main/java/com/tangem/domain/promo/models/PromoBanner.kt index 9f8055dc60..ee31586b29 100644 --- a/domain/promo/models/src/main/java/com/tangem/domain/promo/models/PromoBanner.kt +++ b/domain/promo/models/src/main/java/com/tangem/domain/promo/models/PromoBanner.kt @@ -31,4 +31,5 @@ enum class PromoId { VisaPresale, BlackFriday, OnePlusOne, + YieldPromo, } \ No newline at end of file diff --git a/domain/promo/src/main/java/com/tangem/domain/promo/ShouldShowPromoWalletUseCase.kt b/domain/promo/src/main/java/com/tangem/domain/promo/ShouldShowPromoWalletUseCase.kt index 5cab5b1cde..7f6f41114d 100644 --- a/domain/promo/src/main/java/com/tangem/domain/promo/ShouldShowPromoWalletUseCase.kt +++ b/domain/promo/src/main/java/com/tangem/domain/promo/ShouldShowPromoWalletUseCase.kt @@ -33,6 +33,7 @@ class ShouldShowPromoWalletUseCase( PromoId.VisaPresale, PromoId.BlackFriday, PromoId.OnePlusOne, + PromoId.YieldPromo, -> true PromoId.Sepa -> { val walletFirstUsageDate = settingsRepository.getWalletFirstUsageDate() diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/PromoAnalyticsEvent.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/PromoAnalyticsEvent.kt index 3d3f0fbdb1..df7fde5ad2 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/PromoAnalyticsEvent.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/PromoAnalyticsEvent.kt @@ -60,5 +60,6 @@ sealed class PromoAnalyticsEvent( Sepa("Sepa"), BlackFriday("Black Friday"), OnePlusOne("One-Plus-One"), + YieldPromo("Yield Promo"), } } \ No newline at end of file diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/details/NavigationAction.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/details/NavigationAction.kt index e3343ec90b..ca8dbfd9f9 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/details/NavigationAction.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/details/NavigationAction.kt @@ -6,4 +6,5 @@ import kotlinx.serialization.Serializable sealed class NavigationAction { data object Staking : NavigationAction() data class YieldSupply(val isActive: Boolean) : NavigationAction() + data object CloreMigration : NavigationAction() } \ No newline at end of file diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/error/SignCloreMessageError.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/error/SignCloreMessageError.kt new file mode 100644 index 0000000000..a87418223c --- /dev/null +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/error/SignCloreMessageError.kt @@ -0,0 +1,10 @@ +package com.tangem.domain.transaction.error + +sealed class SignCloreMessageError { + + data object WalletManagerNotFound : SignCloreMessageError() + + data object MessageSigningNotSupported : SignCloreMessageError() + + data class SigningFailed(val message: String) : SignCloreMessageError() +} \ No newline at end of file diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SignCloreMessageUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SignCloreMessageUseCase.kt new file mode 100644 index 0000000000..c4e4913822 --- /dev/null +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SignCloreMessageUseCase.kt @@ -0,0 +1,58 @@ +package com.tangem.domain.transaction.usecase + +import arrow.core.Either +import arrow.core.left +import arrow.core.right +import com.tangem.blockchain.common.MessageSigner +import com.tangem.blockchain.common.TransactionSigner +import com.tangem.common.CompletionResult +import com.tangem.domain.card.repository.CardSdkConfigRepository +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.transaction.error.SignCloreMessageError +import com.tangem.domain.walletmanager.WalletManagersFacade + +class SignCloreMessageUseCase( + private val walletManagersFacade: WalletManagersFacade, + private val cardSdkConfigRepository: CardSdkConfigRepository, + private val getHotWalletSigner: (UserWallet.Hot) -> TransactionSigner, +) { + + suspend operator fun invoke( + userWallet: UserWallet, + currency: CryptoCurrency, + message: String, + ): Either { + val walletManager = walletManagersFacade.getOrCreateWalletManager( + userWalletId = userWallet.walletId, + network = currency.network, + ) + + if (walletManager == null) { + return SignCloreMessageError.WalletManagerNotFound.left() + } + + if (walletManager !is MessageSigner) { + return SignCloreMessageError.MessageSigningNotSupported.left() + } + + val signer = when (userWallet) { + is UserWallet.Cold -> { + val card = userWallet.scanResponse.card + val isCardNotBackedUp = card.backupStatus?.isActive != true + cardSdkConfigRepository.getCommonSigner( + cardId = card.cardId.takeIf { isCardNotBackedUp }, + twinKey = null, + ) + } + is UserWallet.Hot -> getHotWalletSigner(userWallet) + } + + return when (val result = walletManager.signMessage(message, signer)) { + is CompletionResult.Success -> result.data.right() + is CompletionResult.Failure -> SignCloreMessageError.SigningFailed( + message = result.error.message ?: "Unknown error", + ).left() + } + } +} \ No newline at end of file diff --git a/domain/wallet-manager/src/main/kotlin/com/tangem/domain/walletmanager/WalletManagersFacade.kt b/domain/wallet-manager/src/main/kotlin/com/tangem/domain/walletmanager/WalletManagersFacade.kt index 26f486dbb3..adf5c09070 100644 --- a/domain/wallet-manager/src/main/kotlin/com/tangem/domain/walletmanager/WalletManagersFacade.kt +++ b/domain/wallet-manager/src/main/kotlin/com/tangem/domain/walletmanager/WalletManagersFacade.kt @@ -228,6 +228,13 @@ interface WalletManagersFacade { id: String? = null, ): BigDecimal + @Throws(IllegalStateException::class) + suspend fun getNativeTokenBalance( + userWalletId: UserWalletId, + networkId: String, + derivationPath: String?, + ): BigDecimal + /** * Get requirements for asset(currency) * @return null if there's no requirement, otherwise [AssetRequirementsCondition]. diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/analytics/Settings.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/analytics/Settings.kt index 703dcb8cbb..7f8f5b9916 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/analytics/Settings.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/analytics/Settings.kt @@ -1,6 +1,9 @@ package com.tangem.domain.wallets.analytics import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsParam +import com.tangem.core.analytics.models.AppsFlyerIncludedEvent +import com.tangem.core.analytics.models.OneTimeAnalyticsEvent sealed class Settings( category: String = "Settings", @@ -9,4 +12,13 @@ sealed class Settings( ) : AnalyticsEvent(category, event, params) { class ButtonManageTokens : Settings(event = "Button - Manage Tokens") + + class ColdWalletAdded( + source: AnalyticsParam.ScreensSources?, + ) : Settings( + event = "Cold Wallet Added", + params = mapOf(AnalyticsParam.SOURCE to (source?.value ?: "Unknown")), + ), OneTimeAnalyticsEvent, AppsFlyerIncludedEvent { + override val oneTimeEventId: String = id + } } \ No newline at end of file diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/analytics/WalletSettingsAnalyticEvents.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/analytics/WalletSettingsAnalyticEvents.kt index 1c23c85475..be4e913124 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/analytics/WalletSettingsAnalyticEvents.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/analytics/WalletSettingsAnalyticEvents.kt @@ -4,6 +4,7 @@ import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.AnalyticsParam.Key.ACTION import com.tangem.core.analytics.models.AnalyticsParam.Key.STATUS +import com.tangem.core.analytics.models.AppsFlyerIncludedEvent sealed class WalletSettingsAnalyticEvents( category: String = "Settings / Wallet Settings", @@ -166,6 +167,10 @@ sealed class WalletSettingsAnalyticEvents( event = "Button - Start Upgrade", ) + class WalletUpgraded : WalletSettingsAnalyticEvents( + event = "Wallet Upgraded", + ), AppsFlyerIncludedEvent + enum class RecoveryPhraseScreenAction(val value: String) { Upgrade("Upgrade"), Backup("Backup"), diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/SaveWalletUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/SaveWalletUseCase.kt index 354d0b6bc8..75f322f635 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/SaveWalletUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/SaveWalletUseCase.kt @@ -6,9 +6,12 @@ import arrow.core.raise.either import arrow.core.right import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess +import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.common.wallets.error.SaveWalletError import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.wallets.analytics.Settings import com.tangem.domain.wallets.legacy.UserWalletsListError import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.repository.WalletsRepository @@ -16,8 +19,6 @@ import com.tangem.domain.wallets.repository.WalletsRepository /** * Use case for saving user wallet * - * @property userWalletsListManager user wallets list manager - * [REDACTED_AUTHOR] */ class SaveWalletUseCase( @@ -25,14 +26,21 @@ class SaveWalletUseCase( private val userWalletsListRepository: UserWalletsListRepository, private val walletsRepository: WalletsRepository, private val useNewRepository: Boolean, + private val analyticsEventHandler: AnalyticsEventHandler, ) { - suspend operator fun invoke(userWallet: UserWallet, canOverride: Boolean = false): Either { + suspend operator fun invoke( + userWallet: UserWallet, + canOverride: Boolean = false, + analyticsSource: AnalyticsParam.ScreensSources? = null, + ): Either { return if (useNewRepository) { either { val newUserWallet = userWalletsListRepository.userWalletsSync().none { it.walletId == userWallet.walletId } - val userWallet = userWalletsListRepository.saveWithoutLock(userWallet, canOverride).bind() + val userWallet = userWalletsListRepository.saveWithoutLock(userWallet, canOverride) + .onRight { trackColdWalletAddedIfNeeded(analyticsSource, it) } + .bind() if (newUserWallet) { when (userWallet) { @@ -76,4 +84,11 @@ class SaveWalletUseCase( } } } + + private suspend fun trackColdWalletAddedIfNeeded(source: AnalyticsParam.ScreensSources?, userWallet: UserWallet) { + val hasHotWallet = userWalletsListRepository.userWalletsSync().any { it is UserWallet.Hot } + if (hasHotWallet && userWallet is UserWallet.Cold) { + analyticsEventHandler.send(event = Settings.ColdWalletAdded(source)) + } + } } \ No newline at end of file diff --git a/features/account/impl/src/main/java/com/tangem/features/account/archived/entity/AccountArchivedUM.kt b/features/account/impl/src/main/java/com/tangem/features/account/archived/entity/AccountArchivedUM.kt index 42c5faa452..a9166f1318 100644 --- a/features/account/impl/src/main/java/com/tangem/features/account/archived/entity/AccountArchivedUM.kt +++ b/features/account/impl/src/main/java/com/tangem/features/account/archived/entity/AccountArchivedUM.kt @@ -1,7 +1,7 @@ package com.tangem.features.account.archived.entity import androidx.compose.runtime.Immutable -import com.tangem.common.ui.account.CryptoPortfolioIconUM +import com.tangem.common.ui.account.AccountIconUM import com.tangem.core.ui.extensions.TextReference import kotlinx.collections.immutable.ImmutableList @@ -23,7 +23,7 @@ internal sealed interface AccountArchivedUM { internal data class ArchivedAccountUM( val accountId: String, val accountName: TextReference, - val accountIconUM: CryptoPortfolioIconUM, + val accountIconUM: AccountIconUM.CryptoPortfolio, val tokensInfo: TextReference, val networksInfo: TextReference, val isLoading: Boolean, diff --git a/features/account/impl/src/main/java/com/tangem/features/account/archived/entity/AccountArchivedUMBuilder.kt b/features/account/impl/src/main/java/com/tangem/features/account/archived/entity/AccountArchivedUMBuilder.kt index 207e1f1a4a..737b5f6e86 100644 --- a/features/account/impl/src/main/java/com/tangem/features/account/archived/entity/AccountArchivedUMBuilder.kt +++ b/features/account/impl/src/main/java/com/tangem/features/account/archived/entity/AccountArchivedUMBuilder.kt @@ -1,5 +1,6 @@ package com.tangem.features.account.archived.entity +import com.tangem.common.ui.account.CryptoPortfolioIconConverter import com.tangem.common.ui.account.toUM import com.tangem.core.res.R import com.tangem.core.ui.extensions.pluralReference @@ -28,7 +29,7 @@ internal class AccountArchivedUMBuilder @Inject constructor() { ArchivedAccountUM( accountId = accountId.value, accountName = name.toUM().value, - accountIconUM = icon.toUM(), + accountIconUM = CryptoPortfolioIconConverter.convert(icon), isLoading = false, tokensInfo = pluralReference( R.plurals.common_tokens_count, diff --git a/features/account/impl/src/main/java/com/tangem/features/account/createedit/AccountCreateEditModel.kt b/features/account/impl/src/main/java/com/tangem/features/account/createedit/AccountCreateEditModel.kt index e0783453da..ebb0f334f3 100644 --- a/features/account/impl/src/main/java/com/tangem/features/account/createedit/AccountCreateEditModel.kt +++ b/features/account/impl/src/main/java/com/tangem/features/account/createedit/AccountCreateEditModel.kt @@ -2,6 +2,7 @@ package com.tangem.features.account.createedit import androidx.annotation.StringRes import com.tangem.common.ui.account.AccountNameUM +import com.tangem.common.ui.account.CryptoPortfolioIconConverter import com.tangem.common.ui.account.toDomain import com.tangem.common.ui.account.toUM import com.tangem.core.analytics.api.AnalyticsEventHandler @@ -109,7 +110,7 @@ internal class AccountCreateEditModel @Inject constructor( private suspend fun createNewCryptoPortfolio(params: AccountCreateEditComponent.Params.Create) { val state = uiState.value val name = state.account.name.toDomain().getOrNull() ?: return - val icon = state.account.portfolioIcon.toDomain() + val icon = CryptoPortfolioIconConverter.convertBack(state.account.portfolioIcon) val index = state.account.derivationInfo.index ?: return val derivationIndex = DerivationIndex(value = index).getOrNull() ?: return val event = AccountSettingsAnalyticEvents.ButtonAddNewAccount( @@ -161,7 +162,7 @@ internal class AccountCreateEditModel @Inject constructor( private suspend fun editCryptoPortfolio(params: AccountCreateEditComponent.Params.Edit) { val state = uiState.value val name = state.account.name.toDomain().getOrNull() ?: return - val icon = state.account.portfolioIcon.toDomain() + val icon = CryptoPortfolioIconConverter.convertBack(state.account.portfolioIcon) val isNewName = name != params.account.accountName val isNewIcon = icon != params.account.portfolioIcon analyticsEventHandler.send(AccountSettingsAnalyticEvents.ButtonSave(name, icon)) @@ -246,9 +247,10 @@ internal class AccountCreateEditModel @Inject constructor( is AccountCreateEditComponent.Params.Create -> isValidName is AccountCreateEditComponent.Params.Edit -> { val oldName = params.account.accountName.toUM() + val oldIcon = CryptoPortfolioIconConverter.convert(params.account.portfolioIcon) val isNewName = this.account.name.trim() != oldName - val isNewIcon = this.account.portfolioIcon != params.account.portfolioIcon.toUM() + val isNewIcon = this.account.portfolioIcon != oldIcon isValidName && (isNewName || isNewIcon) } } diff --git a/features/account/impl/src/main/java/com/tangem/features/account/createedit/entity/AccountCreateEditUM.kt b/features/account/impl/src/main/java/com/tangem/features/account/createedit/entity/AccountCreateEditUM.kt index da8220a1b5..01c83781ce 100644 --- a/features/account/impl/src/main/java/com/tangem/features/account/createedit/entity/AccountCreateEditUM.kt +++ b/features/account/impl/src/main/java/com/tangem/features/account/createedit/entity/AccountCreateEditUM.kt @@ -1,8 +1,8 @@ package com.tangem.features.account.createedit.entity import androidx.compose.runtime.Immutable +import com.tangem.common.ui.account.AccountIconUM import com.tangem.common.ui.account.AccountNameUM -import com.tangem.common.ui.account.CryptoPortfolioIconUM import com.tangem.core.ui.extensions.TextReference import com.tangem.domain.models.account.CryptoPortfolioIcon import kotlinx.collections.immutable.ImmutableList @@ -18,7 +18,7 @@ internal data class AccountCreateEditUM( data class Account( val name: AccountNameUM, - val portfolioIcon: CryptoPortfolioIconUM, + val portfolioIcon: AccountIconUM.CryptoPortfolio, val derivationInfo: DerivationInfo, val inputPlaceholder: TextReference, val onNameChange: (AccountNameUM) -> Unit, diff --git a/features/account/impl/src/main/java/com/tangem/features/account/createedit/entity/AccountCreateEditUMBuilder.kt b/features/account/impl/src/main/java/com/tangem/features/account/createedit/entity/AccountCreateEditUMBuilder.kt index b9944a8996..b98da2e8b9 100644 --- a/features/account/impl/src/main/java/com/tangem/features/account/createedit/entity/AccountCreateEditUMBuilder.kt +++ b/features/account/impl/src/main/java/com/tangem/features/account/createedit/entity/AccountCreateEditUMBuilder.kt @@ -1,6 +1,7 @@ package com.tangem.features.account.createedit.entity import com.tangem.common.ui.account.AccountNameUM +import com.tangem.common.ui.account.CryptoPortfolioIconConverter import com.tangem.common.ui.account.toUM import com.tangem.core.res.R import com.tangem.core.ui.extensions.TextReference @@ -17,7 +18,7 @@ internal class AccountCreateEditUMBuilder( private val accountColors = CryptoPortfolioIcon.Color.entries.toImmutableList() private val accountIcons = CryptoPortfolioIcon.Icon.entries.toImmutableList() - private val createIcon = CryptoPortfolioIcon.ofDefaultCustomAccount().toUM() + private val createIcon = CryptoPortfolioIconConverter.convert(CryptoPortfolioIcon.ofDefaultCustomAccount()) val toolbarTitle: TextReference get() = when (params) { @@ -36,7 +37,7 @@ internal class AccountCreateEditUMBuilder( ) is AccountCreateEditComponent.Params.Edit -> AccountCreateEditUM.Account( name = params.account.accountName.toUM(), - portfolioIcon = params.account.portfolioIcon.toUM(), + portfolioIcon = CryptoPortfolioIconConverter.convert(params.account.portfolioIcon), derivationInfo = createAccountDerivationInfo( index = (params.account as Account.CryptoPortfolio).derivationIndex.value, ), diff --git a/features/account/impl/src/main/java/com/tangem/features/account/details/AccountDetailsModel.kt b/features/account/impl/src/main/java/com/tangem/features/account/details/AccountDetailsModel.kt index fad8eeb69f..c4b4297c35 100644 --- a/features/account/impl/src/main/java/com/tangem/features/account/details/AccountDetailsModel.kt +++ b/features/account/impl/src/main/java/com/tangem/features/account/details/AccountDetailsModel.kt @@ -1,6 +1,7 @@ package com.tangem.features.account.details import com.tangem.common.routing.AppRoute +import com.tangem.common.ui.account.CryptoPortfolioIconConverter import com.tangem.common.ui.account.toUM import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.di.ModelScoped @@ -159,7 +160,7 @@ internal class AccountDetailsModel @Inject constructor( ?.isMultiCurrency == true return AccountDetailsUM( accountName = account.accountName.toUM().value, - accountIcon = account.portfolioIcon.toUM(), + accountIcon = CryptoPortfolioIconConverter.convert(account.portfolioIcon), onCloseClick = { router.pop() }, onAccountEditClick = { onEditAccountClick(account) }, onManageTokensClick = { onManageTokensClick(account) }, diff --git a/features/account/impl/src/main/java/com/tangem/features/account/details/entity/AccountDetailsUM.kt b/features/account/impl/src/main/java/com/tangem/features/account/details/entity/AccountDetailsUM.kt index 15fe34025b..67e33ebcf6 100644 --- a/features/account/impl/src/main/java/com/tangem/features/account/details/entity/AccountDetailsUM.kt +++ b/features/account/impl/src/main/java/com/tangem/features/account/details/entity/AccountDetailsUM.kt @@ -1,12 +1,12 @@ package com.tangem.features.account.details.entity import androidx.compose.runtime.Immutable -import com.tangem.common.ui.account.CryptoPortfolioIconUM +import com.tangem.common.ui.account.AccountIconUM import com.tangem.core.ui.extensions.TextReference internal data class AccountDetailsUM( val accountName: TextReference, - val accountIcon: CryptoPortfolioIconUM, + val accountIcon: AccountIconUM.CryptoPortfolio, val archiveMode: ArchiveMode, val isManageTokensAvailable: Boolean, val onCloseClick: () -> Unit, diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletSaver.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletSaver.kt index 04d7e91736..59be5a0529 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletSaver.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletSaver.kt @@ -75,7 +75,12 @@ internal class UserWalletSaver @Inject constructor( private suspend fun Raise.saveWallet(userWallet: UserWallet) { fold( - block = { saveWalletUseCase(userWallet).bind() }, + block = { + saveWalletUseCase( + userWallet = userWallet, + analyticsSource = AnalyticsParam.ScreensSources.Settings, + ).bind() + }, recover = { error -> when (error) { is SaveWalletError.WalletAlreadySaved -> { diff --git a/features/feed/api/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/AddToPortfolioPreselectedDataComponent.kt b/features/feed/api/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/AddToPortfolioPreselectedDataComponent.kt new file mode 100644 index 0000000000..713ce93cf1 --- /dev/null +++ b/features/feed/api/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/AddToPortfolioPreselectedDataComponent.kt @@ -0,0 +1,33 @@ +package com.tangem.features.feed.components.market.details.portfolio.add + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableBottomSheetComponent +import com.tangem.domain.markets.TokenMarketInfo +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.wallet.UserWalletId + +interface AddToPortfolioPreselectedDataComponent : ComposableBottomSheetComponent { + + /** + * @param tokenToAdd token and preselected network (no network selector). + * @param callback callbacks for add-to-portfolio flow. + */ + data class Params( + val tokenToAdd: TokenToAdd, + val callback: Callback, + ) + + interface Callback { + fun onDismiss() + fun onSuccess(addedToken: CryptoCurrency, walletId: UserWalletId) + } + + data class TokenToAdd( + val network: TokenMarketInfo.Network, + val id: CryptoCurrency.RawID, + val name: String, + val symbol: String, + ) + + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/features/feed/api/src/main/kotlin/com/tangem/features/feed/entry/featuretoggle/FeedFeatureToggle.kt b/features/feed/api/src/main/kotlin/com/tangem/features/feed/entry/featuretoggle/FeedFeatureToggle.kt index c04c556450..72d5656bad 100644 --- a/features/feed/api/src/main/kotlin/com/tangem/features/feed/entry/featuretoggle/FeedFeatureToggle.kt +++ b/features/feed/api/src/main/kotlin/com/tangem/features/feed/entry/featuretoggle/FeedFeatureToggle.kt @@ -2,4 +2,5 @@ package com.tangem.features.feed.entry.featuretoggle interface FeedFeatureToggle { val isFeedEnabled: Boolean + val isEarnBlockEnabled: Boolean } \ No newline at end of file diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/impl/AddToPortfolioBottomSheet.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/impl/AddToPortfolioBottomSheet.kt new file mode 100644 index 0000000000..8c6aba3001 --- /dev/null +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/impl/AddToPortfolioBottomSheet.kt @@ -0,0 +1,118 @@ +package com.tangem.features.feed.components.market.details.portfolio.add.impl + +import androidx.compose.animation.AnimatedContent +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.arkivanov.decompose.router.stack.ChildStack +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet +import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.res.TangemTheme +import com.tangem.features.account.PortfolioSelectorComponent +import com.tangem.features.feed.components.market.details.portfolio.add.impl.model.AddToPortfolioRoutes +import com.tangem.features.feed.impl.R + +@Composable +internal fun AddToPortfolioBottomSheet( + childStack: State>, + onBack: () -> Unit, + onDismiss: () -> Unit, +) { + val stack by childStack + val contentStack = remember { mutableStateOf(stack) } + val currentRoute = stack.active.configuration + val isNotEmpty = currentRoute != AddToPortfolioRoutes.Empty + if (isNotEmpty) { + contentStack.value = stack + } + + TangemModalBottomSheet( + scrollableContent = false, + onBack = onBack, + config = TangemBottomSheetConfig( + isShown = isNotEmpty, + onDismissRequest = onDismiss, + content = TangemBottomSheetConfigContent.Empty, + ), + containerColor = TangemTheme.colors.background.tertiary, + title = { + AnimatedContent(targetState = contentStack.value, label = "Title Animation") { animatedStack -> + AddToPortfolioBottomSheetTitle( + stack = animatedStack, + onBackClick = onBack, + modifier = Modifier.fillMaxWidth(), + ) + } + }, + content = { + AnimatedContent(targetState = contentStack.value, label = "Content Animation") { animatedStack -> + val paddingModifier = Modifier.padding( + start = 16.dp, + end = 16.dp, + bottom = 16.dp, + ) + val isScrollableContent = when (animatedStack.active.configuration) { + AddToPortfolioRoutes.PortfolioSelector -> false + AddToPortfolioRoutes.AddToken, + AddToPortfolioRoutes.Empty, + is AddToPortfolioRoutes.NetworkSelector, + AddToPortfolioRoutes.TokenActions, + -> true + } + if (isScrollableContent) { + Column( + modifier = paddingModifier.verticalScroll(rememberScrollState()), + ) { + animatedStack.active.instance.Content(modifier = Modifier) + } + } else { + animatedStack.active.instance.Content(modifier = paddingModifier) + } + } + }, + ) +} + +@Composable +private fun AddToPortfolioBottomSheetTitle( + stack: ChildStack, + onBackClick: () -> Unit, + modifier: Modifier = Modifier, +) { + val title: TextReference = when (stack.active.configuration) { + AddToPortfolioRoutes.AddToken -> resourceReference(R.string.common_add_token) + AddToPortfolioRoutes.Empty -> TextReference.EMPTY + is AddToPortfolioRoutes.NetworkSelector -> resourceReference(R.string.common_choose_network) + AddToPortfolioRoutes.TokenActions -> resourceReference(R.string.common_get_token) + AddToPortfolioRoutes.PortfolioSelector -> (stack.active.instance as PortfolioSelectorComponent) + .title.collectAsStateWithLifecycle().value + } + val startIconRes: Int? + val endIconRes: Int? + if (stack.backStack.isNotEmpty()) { + startIconRes = R.drawable.ic_back_24 + endIconRes = null + } else { + startIconRes = null + endIconRes = R.drawable.ic_close_24 + } + TangemModalBottomSheetTitle( + modifier = modifier, + title = title, + startIconRes = startIconRes, + endIconRes = endIconRes, + onStartClick = onBackClick, + onEndClick = onBackClick, + ) +} \ No newline at end of file diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/impl/DefaultAddToPortfolioComponent.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/impl/DefaultAddToPortfolioComponent.kt index edd7f01ef5..8f7dfb79a1 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/impl/DefaultAddToPortfolioComponent.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/impl/DefaultAddToPortfolioComponent.kt @@ -1,21 +1,8 @@ package com.tangem.features.feed.components.market.details.portfolio.add.impl -import androidx.compose.animation.AnimatedContent -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.extensions.compose.subscribeAsState -import com.arkivanov.decompose.router.stack.ChildStack import com.arkivanov.decompose.router.stack.backStack import com.arkivanov.decompose.router.stack.childStack import com.arkivanov.decompose.router.stack.pop @@ -23,19 +10,11 @@ import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.child import com.tangem.core.decompose.context.childByContext import com.tangem.core.decompose.model.getOrCreateModel -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent -import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet -import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle import com.tangem.core.ui.decompose.ComposableContentComponent -import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.res.TangemTheme import com.tangem.features.account.PortfolioSelectorComponent import com.tangem.features.feed.components.market.details.portfolio.add.AddToPortfolioComponent import com.tangem.features.feed.components.market.details.portfolio.add.impl.model.AddToPortfolioModel import com.tangem.features.feed.components.market.details.portfolio.add.impl.model.AddToPortfolioRoutes -import com.tangem.features.feed.impl.R import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -97,91 +76,10 @@ internal class DefaultAddToPortfolioComponent @AssistedInject constructor( @Composable override fun BottomSheet() { - val stack by childStack.subscribeAsState() - val contentStack = remember { mutableStateOf(stack) } - val currentRoute = stack.active.configuration - val isNotEmpty = currentRoute != AddToPortfolioRoutes.Empty - if (isNotEmpty) { - contentStack.value = stack - } - - TangemModalBottomSheet( - scrollableContent = false, + AddToPortfolioBottomSheet( + childStack = childStack.subscribeAsState(), onBack = ::onBack, - config = TangemBottomSheetConfig( - isShown = isNotEmpty, - onDismissRequest = ::dismiss, - content = TangemBottomSheetConfigContent.Empty, - ), - containerColor = TangemTheme.colors.background.tertiary, - title = { state -> - AnimatedContent(targetState = contentStack.value) { stack -> - BottomSheetTitle( - stack = stack, - onBackClick = ::onBack, - modifier = Modifier.fillMaxWidth(), - ) - } - }, - content = { state -> - AnimatedContent(targetState = contentStack.value) { stack -> - val paddingModifier = Modifier.padding( - start = 16.dp, - end = 16.dp, - bottom = 16.dp, - ) - val isScrollableContent = when (stack.active.configuration) { - AddToPortfolioRoutes.PortfolioSelector -> false - AddToPortfolioRoutes.AddToken, - AddToPortfolioRoutes.Empty, - is AddToPortfolioRoutes.NetworkSelector, - AddToPortfolioRoutes.TokenActions, - -> true - } - if (isScrollableContent) { - Column( - modifier = paddingModifier.verticalScroll(rememberScrollState()), - ) { - stack.active.instance.Content(modifier = Modifier) - } - } else { - stack.active.instance.Content(modifier = paddingModifier) - } - } - }, - ) - } - - @Composable - private fun BottomSheetTitle( - stack: ChildStack, - onBackClick: (() -> Unit), - modifier: Modifier = Modifier, - ) { - val title: TextReference = when (stack.active.configuration) { - AddToPortfolioRoutes.AddToken -> resourceReference(R.string.common_add_token) - AddToPortfolioRoutes.Empty -> TextReference.EMPTY - is AddToPortfolioRoutes.NetworkSelector -> resourceReference(R.string.common_choose_network) - AddToPortfolioRoutes.TokenActions -> resourceReference(R.string.common_get_token) - AddToPortfolioRoutes.PortfolioSelector -> (stack.active.instance as PortfolioSelectorComponent) - .title.collectAsStateWithLifecycle().value - } - val startIconRes: Int? - val endIconRes: Int? - if (stack.backStack.isNotEmpty()) { - startIconRes = R.drawable.ic_back_24 - endIconRes = null - } else { - startIconRes = null - endIconRes = R.drawable.ic_close_24 - } - TangemModalBottomSheetTitle( - modifier = modifier, - title = title, - startIconRes = startIconRes, - endIconRes = endIconRes, - onStartClick = onBackClick, - onEndClick = onBackClick, + onDismiss = ::dismiss, ) } diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/impl/DefaultAddToPortfolioPreselectedDataComponent.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/impl/DefaultAddToPortfolioPreselectedDataComponent.kt new file mode 100644 index 0000000000..95efde9298 --- /dev/null +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/impl/DefaultAddToPortfolioPreselectedDataComponent.kt @@ -0,0 +1,90 @@ +package com.tangem.features.feed.components.market.details.portfolio.add.impl + +import androidx.compose.runtime.Composable +import com.arkivanov.decompose.extensions.compose.subscribeAsState +import com.arkivanov.decompose.router.stack.backStack +import com.arkivanov.decompose.router.stack.childStack +import com.arkivanov.decompose.router.stack.pop +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.context.child +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.features.account.PortfolioSelectorComponent +import com.tangem.features.feed.components.market.details.portfolio.add.AddToPortfolioPreselectedDataComponent +import com.tangem.features.feed.components.market.details.portfolio.add.impl.model.AddToPortfolioPreselectedDataModel +import com.tangem.features.feed.components.market.details.portfolio.add.impl.model.AddToPortfolioRoutes +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +internal class DefaultAddToPortfolioPreselectedDataComponent @AssistedInject constructor( + @Assisted context: AppComponentContext, + @Assisted private val params: AddToPortfolioPreselectedDataComponent.Params, + portfolioSelectorComponentFactory: PortfolioSelectorComponent.Factory, + addTokenComponentFactory: AddTokenComponent.Factory, +) : AppComponentContext by context, AddToPortfolioPreselectedDataComponent { + + private val model: AddToPortfolioPreselectedDataModel = getOrCreateModel(params) + + private val portfolioSelectorComponent: PortfolioSelectorComponent = portfolioSelectorComponentFactory.create( + context = child("portfolioSelectorComponent"), + params = PortfolioSelectorComponent.Params( + portfolioFetcher = model.portfolioFetcher, + controller = model.portfolioSelectorController, + ), + ) + + private val addTokenComponent: AddTokenComponent = addTokenComponentFactory.create( + context = child("addTokenComponent"), + params = AddTokenComponent.Params( + eventBuilder = model.eventBuilder, + callbacks = model, + selectedPortfolio = model.selectedPortfolio, + selectedNetwork = model.selectedNetwork, + ), + ) + + private val childStack = childStack( + key = "addToPortfolioFromEarnStack", + handleBackButton = true, + source = model.navigation, + serializer = AddToPortfolioRoutes.serializer(), + initialStack = { model.currentStack }, + childFactory = { configuration, _ -> + contentChild(configuration) + }, + ) + + private fun onBack() { + if (childStack.backStack.isNotEmpty()) model.navigation.pop() else dismiss() + } + + override fun dismiss() { + params.callback.onDismiss() + } + + @Composable + override fun BottomSheet() { + AddToPortfolioBottomSheet( + childStack = childStack.subscribeAsState(), + onBack = ::onBack, + onDismiss = ::dismiss, + ) + } + + private fun contentChild(config: AddToPortfolioRoutes): ComposableContentComponent = when (config) { + AddToPortfolioRoutes.AddToken -> addTokenComponent + AddToPortfolioRoutes.PortfolioSelector -> portfolioSelectorComponent + AddToPortfolioRoutes.TokenActions -> ComposableContentComponent.EMPTY + AddToPortfolioRoutes.Empty -> ComposableContentComponent.EMPTY + is AddToPortfolioRoutes.NetworkSelector -> ComposableContentComponent.EMPTY + } + + @AssistedFactory + interface Factory : AddToPortfolioPreselectedDataComponent.Factory { + override fun create( + context: AppComponentContext, + params: AddToPortfolioPreselectedDataComponent.Params, + ): DefaultAddToPortfolioPreselectedDataComponent + } +} \ No newline at end of file diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/impl/di/AddToPortfolioModelModule.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/impl/di/AddToPortfolioModelModule.kt index b60d84f099..60b3f51d91 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/impl/di/AddToPortfolioModelModule.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/impl/di/AddToPortfolioModelModule.kt @@ -2,10 +2,7 @@ package com.tangem.features.feed.components.market.details.portfolio.add.impl.di import com.tangem.core.decompose.di.ModelComponent import com.tangem.core.decompose.model.Model -import com.tangem.features.feed.components.market.details.portfolio.add.impl.model.AddToPortfolioModel -import com.tangem.features.feed.components.market.details.portfolio.add.impl.model.AddTokenModel -import com.tangem.features.feed.components.market.details.portfolio.add.impl.model.ChooseNetworkModel -import com.tangem.features.feed.components.market.details.portfolio.add.impl.model.TokenActionsModel +import com.tangem.features.feed.components.market.details.portfolio.add.impl.model.* import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -26,6 +23,11 @@ internal interface AddToPortfolioModelModule { @ClassKey(AddToPortfolioModel::class) fun addToPortfolioModel(model: AddToPortfolioModel): Model + @Binds + @IntoMap + @ClassKey(AddToPortfolioPreselectedDataModel::class) + fun addToPortfolioPreselectedDataModel(model: AddToPortfolioPreselectedDataModel): Model + @Binds @IntoMap @ClassKey(TokenActionsModel::class) diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/impl/model/AddToPortfolioModel.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/impl/model/AddToPortfolioModel.kt index 299f1d72ed..a06d46878d 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/impl/model/AddToPortfolioModel.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/impl/model/AddToPortfolioModel.kt @@ -67,7 +67,7 @@ internal class AddToPortfolioModel @Inject constructor( private val addToPortfolioManager = params.addToPortfolioManager val portfolioFetcher = addToPortfolioManager.portfolioFetcher val eventBuilder = PortfolioAnalyticsEvent.EventBuilder( - token = addToPortfolioManager.token, + tokenSymbol = addToPortfolioManager.token.symbol, source = addToPortfolioManager.analyticsParams?.source, ) diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/impl/model/AddToPortfolioPreselectedDataModel.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/impl/model/AddToPortfolioPreselectedDataModel.kt new file mode 100644 index 0000000000..b673414488 --- /dev/null +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/impl/model/AddToPortfolioPreselectedDataModel.kt @@ -0,0 +1,280 @@ +package com.tangem.features.feed.components.market.details.portfolio.add.impl.model + +import com.arkivanov.decompose.router.stack.StackNavigation +import com.arkivanov.decompose.router.stack.replaceAll +import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.analytics.models.AnalyticsParam +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.core.decompose.ui.UiMessageSender +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.message.ToastMessage +import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase +import com.tangem.domain.markets.GetTokenMarketCryptoCurrency +import com.tangem.domain.markets.TokenMarketInfo +import com.tangem.domain.markets.TokenMarketParams +import com.tangem.domain.models.account.AccountStatus +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.features.account.PortfolioFetcher +import com.tangem.features.account.PortfolioSelectorController +import com.tangem.features.feed.components.market.details.portfolio.add.* +import com.tangem.features.feed.components.market.details.portfolio.add.impl.AddTokenComponent +import com.tangem.features.feed.components.market.details.portfolio.impl.analytics.PortfolioAnalyticsEvent +import com.tangem.features.feed.impl.R +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.* +import timber.log.Timber +import java.math.BigDecimal +import javax.inject.Inject + +@Suppress("LongParameterList") +internal class AddToPortfolioPreselectedDataModel @Inject constructor( + paramsContainer: ParamsContainer, + portfolioFetcherFactory: PortfolioFetcher.Factory, + override val dispatchers: CoroutineDispatcherProvider, + val portfolioSelectorController: PortfolioSelectorController, + private val callbackDelegate: AddToPortfolioFromEarnCallbackDelegate, + private val messageSender: UiMessageSender, + private val analyticsEventHandler: AnalyticsEventHandler, + private val getTokenMarketCryptoCurrency: GetTokenMarketCryptoCurrency, + private val getAccountCurrencyStatusUseCase: GetAccountCurrencyStatusUseCase, +) : Model(), AddTokenComponent.Callbacks by callbackDelegate { + + private val params = paramsContainer.require() + + val portfolioFetcher: PortfolioFetcher = portfolioFetcherFactory.create( + mode = PortfolioFetcher.Mode.All(isOnlyMultiCurrency = true), + scope = modelScope, + ) + + val navigation = StackNavigation() + var currentStack = listOf(AddToPortfolioRoutes.Empty) + + private val _selectedNetwork = MutableStateFlow(null) + val selectedNetwork: Flow = _selectedNetwork.asStateFlow().filterNotNull() + + private val _selectedPortfolio = MutableStateFlow(null) + val selectedPortfolio: Flow = _selectedPortfolio.asStateFlow().filterNotNull() + val eventBuilder = PortfolioAnalyticsEvent.EventBuilder( + tokenSymbol = params.tokenToAdd.symbol, + source = AnalyticsParam.ScreensSources.Markets.value, + ) + + init { + navigation.subscribe { currentStack = it.transformer.invoke(currentStack) } + startAddToPortfolioFlow() + } + + @Suppress("LongMethod") + private fun startAddToPortfolioFlow() { + channelFlow { + fun finishSuccessFlow(currency: CryptoCurrency, userWalletId: UserWalletId) { + params.callback.onSuccess(addedToken = currency, walletId = userWalletId) + channel.close() + } + + val data = createAvailableToAddDataForPreselectedNetwork(params.tokenToAdd.network) + ?: return@channelFlow + + val isAccountMode = portfolioSelectorController.isAccountModeSync() + val firstSelectedPortfolio = setupPortfolioFlow(data) + .onEach { _selectedPortfolio.value = it } + + val firstSelectedNetwork = firstSelectedPortfolio + .map { portfolio -> createSelectedNetwork(network = params.tokenToAdd.network, portfolio = portfolio) } + .filterNotNull() + .onEach { _selectedNetwork.value = it } + + val isSinglePortfolio = data.isSinglePortfolio + if (isSinglePortfolio) { + val accountId = data.availableToAddWallets.values.first() + .availableToAddAccounts.values.first() + .account.account.accountId + // force select a portfolio, triggers [selectedPortfolio] + portfolioSelectorController.selectAccount(accountId) + } else { + logAccountSelector(isAccountMode) + navigation.replaceAll(AddToPortfolioRoutes.PortfolioSelector) + } + + // main flow that combine all require data + val allRequireForAdd = combine( + flow = firstSelectedNetwork, + flow2 = firstSelectedPortfolio, + transform = { a, b -> a to b }, + ) + + // suspend until all required data is selected + val (selectedNetworkValue, selectedPortfolioValue) = allRequireForAdd.first() + + val isTokenAlreadyAdded = selectedPortfolioValue.account.addedMarketNetworks + .any { it.networkId == selectedNetworkValue.selectedNetwork.networkId } + + if (isTokenAlreadyAdded) { + finishSuccessFlow( + currency = selectedNetworkValue.cryptoCurrency, + userWalletId = selectedPortfolioValue.userWallet.walletId, + ) + return@channelFlow + } + + navigation.replaceAll(AddToPortfolioRoutes.AddToken) + val addedToken = callbackDelegate.onTokenAdded.receiveAsFlow().first() + messageSender.send(ToastMessage(message = resourceReference(R.string.markets_token_added))) + finishSuccessFlow(addedToken.currency, selectedPortfolioValue.userWallet.walletId) + } + .catch { throwable -> + Timber.e(throwable) + params.callback.onDismiss() + } + .launchIn(modelScope) + } + + private fun logAccountSelector(isAccountMode: Boolean) { + if (isAccountMode) { + analyticsEventHandler.send(eventBuilder.popupToChooseAccount()) + } + } + + private fun minimalTokenMarketParams() = with(params.tokenToAdd) { + TokenMarketParams( + id = id, + name = name, + symbol = symbol, + tokenQuotes = TokenMarketParams.Quotes( + currentPrice = BigDecimal.ZERO, + h24Percent = null, + weekPercent = null, + monthPercent = null, + ), + imageUrl = null, + ) + } + + /** + * Creates [AvailableToAddData] for preselected network when token is already added in all networks. + * This allows user to select wallet/account and do smth after it with selected info. + */ + private suspend fun createAvailableToAddDataForPreselectedNetwork( + preSelectedNetwork: TokenMarketInfo.Network, + ): AvailableToAddData? { + val portfolioData = portfolioFetcher.data.firstOrNull() ?: return null + + val availableToOpenWallets = portfolioData.balances.mapNotNull { (walletId, balance) -> + val wallet = balance.userWallet + val accounts = balance.accountsBalance.accountStatuses + + val availableToAddAccounts = accounts.mapNotNull { accountStatus -> + val accountIndex = when (accountStatus) { + is AccountStatus.CryptoPortfolio -> accountStatus.account.derivationIndex + } + + val cryptoCurrency = getTokenMarketCryptoCurrency( + userWalletId = walletId, + tokenMarketParams = minimalTokenMarketParams(), + network = preSelectedNetwork, + accountIndex = accountIndex, + ) ?: return@mapNotNull null + + val addedNetworks = getAccountCurrencyStatusUseCase.invokeSync( + userWalletId = walletId, + currency = cryptoCurrency, + ).fold( + ifEmpty = { emptySet() }, + ifSome = { setOf(it.status.currency.network) }, + ) + + AvailableToAddAccount( + account = accountStatus, + availableNetworks = setOf(preSelectedNetwork), + addedNetworks = addedNetworks, + ) + }.associateBy { it.account.account.accountId } + + if (availableToAddAccounts.isEmpty()) return@mapNotNull null + + walletId to AvailableToAddWallet( + userWallet = wallet, + accounts = accounts, + availableNetworks = setOf(preSelectedNetwork), + availableToAddAccounts = availableToAddAccounts, + ) + }.toMap() + + if (availableToOpenWallets.isEmpty()) return null + + return AvailableToAddData(availableToAddWallets = availableToOpenWallets) + } + + private suspend fun createCryptoCurrency( + userWallet: UserWallet, + network: TokenMarketInfo.Network, + account: AvailableToAddAccount, + ): CryptoCurrency? { + val accountIndex = when (val accountStatus = account.account) { + is AccountStatus.CryptoPortfolio -> accountStatus.account.derivationIndex + } + return getTokenMarketCryptoCurrency( + userWalletId = userWallet.walletId, + tokenMarketParams = minimalTokenMarketParams(), + network = network, + accountIndex = accountIndex, + ) + } + + private suspend fun createSelectedNetwork( + network: TokenMarketInfo.Network, + portfolio: SelectedPortfolio, + ): SelectedNetwork? { + val cryptoCurrency = createCryptoCurrency( + userWallet = portfolio.userWallet, + network = network, + account = portfolio.account, + ) ?: return null + + return SelectedNetwork( + cryptoCurrency = cryptoCurrency, + selectedNetwork = network, + isAvailableMoreNetwork = false, + ) + } + + private fun setupPortfolioFlow(data: AvailableToAddData): Flow = combine( + flow = portfolioSelectorController.isAccountMode, + flow2 = portfolioSelectorController.selectedAccount, + transform = { isAccountMode, selectedAccountId -> + selectedAccountId ?: return@combine null + val availableToAddWallets = + data.availableToAddWallets[selectedAccountId.userWalletId] ?: return@combine null + val availableToAddAccount = + availableToAddWallets.availableToAddAccounts[selectedAccountId] ?: return@combine null + if (!isAccountMode) analyticsEventHandler.send(eventBuilder.addToPortfolioWalletChanged()) + SelectedPortfolio( + isAccountMode = isAccountMode, + userWallet = availableToAddWallets.userWallet, + account = availableToAddAccount, + isAvailableMorePortfolio = false, + ) + }, + ) + .filterNotNull() +} + +@ModelScoped +internal class AddToPortfolioFromEarnCallbackDelegate @Inject constructor() : + AddTokenComponent.Callbacks { + val onTokenAdded = Channel() + + override fun onChangeNetworkClick() = Unit + + override fun onChangePortfolioClick() = Unit + + override fun onTokenAdded(status: CryptoCurrencyStatus) { + onTokenAdded.trySend(status) + } +} \ No newline at end of file diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/impl/model/AddTokenUiBuilder.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/impl/model/AddTokenUiBuilder.kt index b6ad346e0c..5d5c7e379e 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/impl/model/AddTokenUiBuilder.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/impl/model/AddTokenUiBuilder.kt @@ -1,6 +1,7 @@ package com.tangem.features.feed.components.market.details.portfolio.add.impl.model -import com.tangem.common.ui.account.CryptoPortfolioIconUM +import com.tangem.common.ui.account.AccountIconUM +import com.tangem.common.ui.account.CryptoPortfolioIconConverter import com.tangem.common.ui.account.PortfolioSelectUM import com.tangem.common.ui.account.toUM import com.tangem.common.ui.addtoken.AddTokenUM @@ -35,7 +36,7 @@ internal class AddTokenUiBuilder @Inject constructor( } private fun createPortfolio(selectedPortfolio: SelectedPortfolio): PortfolioSelectUM { - val accountIcon: CryptoPortfolioIconUM? + val accountIcon: AccountIconUM.CryptoPortfolio? val portfolioName: TextReference when (selectedPortfolio.isAccountMode) { false -> { @@ -46,7 +47,7 @@ internal class AddTokenUiBuilder @Inject constructor( val accountStatus = selectedPortfolio.account.account portfolioName = accountStatus.account.accountName.toUM().value accountIcon = when (accountStatus) { - is AccountStatus.CryptoPortfolio -> accountStatus.account.icon.toUM() + is AccountStatus.CryptoPortfolio -> CryptoPortfolioIconConverter.convert(accountStatus.account.icon) } } } diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/impl/model/TokenActionsModel.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/impl/model/TokenActionsModel.kt index 0bb9a30fd9..65e1cf28d1 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/impl/model/TokenActionsModel.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/add/impl/model/TokenActionsModel.kt @@ -47,7 +47,6 @@ internal class TokenActionsModel @Inject constructor( private val tokenActionsHandler: TokenActionsHandler = tokenActionsIntentsFactory.create( currentAppCurrency = Provider { currentAppCurrency.value }, - updateTokenReceiveBSConfig = { }, onHandleQuickAction = { handledAction -> handledQuickAction(handledAction) }, ) diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/analytics/PortfolioAnalyticsEvent.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/analytics/PortfolioAnalyticsEvent.kt index 87ea980421..b75d67abf4 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/analytics/PortfolioAnalyticsEvent.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/analytics/PortfolioAnalyticsEvent.kt @@ -1,7 +1,6 @@ package com.tangem.features.feed.components.market.details.portfolio.impl.analytics import com.tangem.core.analytics.models.AnalyticsEvent -import com.tangem.domain.markets.TokenMarketParams import com.tangem.features.feed.components.market.details.portfolio.impl.ui.state.TokenActionsBSContentUM internal class PortfolioAnalyticsEvent( @@ -10,14 +9,14 @@ internal class PortfolioAnalyticsEvent( ) : AnalyticsEvent(category = "Markets / Chart", event = event, params = params) { data class EventBuilder( - val token: TokenMarketParams, + val tokenSymbol: String, val source: String?, ) { fun addToPortfolioClicked() = PortfolioAnalyticsEvent( event = "Button - Add To Portfolio", params = mapOf( - "Token" to token.symbol, + "Token" to tokenSymbol, ), ) @@ -35,7 +34,7 @@ internal class PortfolioAnalyticsEvent( event = "Token Network Selected", params = mapOf( "Count" to blockchainNames.size.toString(), - "Token" to token.symbol, + "Token" to tokenSymbol, "blockchain" to blockchainNames.joinToString(separator = ", "), ), ) @@ -51,7 +50,7 @@ internal class PortfolioAnalyticsEvent( else -> "error" }, params = buildMap { - put("Token", token.symbol) + put("Token", tokenSymbol) source?.let { put("Source", it) } put("blockchain", blockchainName) }, diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/MarketsPortfolioModel.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/MarketsPortfolioModel.kt index 1715e60e47..71d48270b1 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/MarketsPortfolioModel.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/MarketsPortfolioModel.kt @@ -39,7 +39,6 @@ import com.tangem.features.feed.components.market.details.portfolio.impl.loader. import com.tangem.features.feed.components.market.details.portfolio.impl.ui.state.MyPortfolioUM import com.tangem.features.feed.components.market.details.portfolio.impl.ui.state.TokenActionsBSContentUM import com.tangem.features.feed.impl.R -import com.tangem.features.tokenreceive.TokenReceiveFeatureToggle import com.tangem.features.wallet.utils.UserWalletImageFetcher import com.tangem.lib.crypto.BlockchainUtils import com.tangem.operations.attestation.ArtworkSize @@ -68,7 +67,6 @@ internal class MarketsPortfolioModel @Inject constructor( private val saveMarketTokensUseCase: SaveMarketTokensUseCase, private val addToPortfolioManager: AddToPortfolioManager, private val analyticsEventHandler: AnalyticsEventHandler, - private val tokenReceiveFeatureToggle: TokenReceiveFeatureToggle, private val userWalletImageFetcher: UserWalletImageFetcher, private val receiveAddressesFactory: ReceiveAddressesFactory, accountsFeatureToggles: AccountsFeatureToggles, @@ -81,7 +79,7 @@ internal class MarketsPortfolioModel @Inject constructor( private val params = paramsContainer.require() private val analyticsEventBuilder = PortfolioAnalyticsEvent.EventBuilder( - token = params.token, + tokenSymbol = params.token.symbol, source = params.analyticsParams?.source, ) @@ -111,13 +109,6 @@ internal class MarketsPortfolioModel @Inject constructor( private val tokenActionsHandler = tokenActionsIntentsFactory.create( currentAppCurrency = Provider { currentAppCurrency.value }, - updateTokenReceiveBSConfig = { updateBlock -> - if (tokenReceiveFeatureToggle.isNewTokenReceiveEnabled.not()) { - updateTokensState { - it.copy(tokenReceiveBSConfig = updateBlock(it.tokenReceiveBSConfig)) - } - } - }, onHandleQuickAction = { handledAction -> analyticsEventHandler.send( analyticsEventBuilder.quickActionClick( @@ -130,9 +121,7 @@ internal class MarketsPortfolioModel @Inject constructor( .name, ), ) - if (tokenReceiveFeatureToggle.isNewTokenReceiveEnabled) { - configureReceiveAddresses(handledAction) - } + configureReceiveAddresses(handledAction) }, ) @@ -426,8 +415,7 @@ internal class MarketsPortfolioModel @Inject constructor( } private fun configureReceiveAddresses(quickAction: TokenActionsHandler.HandledQuickAction) { - val isNewReceive = quickAction.action == TokenActionsBSContentUM.Action.Receive && - tokenReceiveFeatureToggle.isNewTokenReceiveEnabled + val isNewReceive = quickAction.action == TokenActionsBSContentUM.Action.Receive if (isNewReceive) { modelScope.launch { val tokenConfig = receiveAddressesFactory.create( diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/NewMarketsPortfolioDelegate.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/NewMarketsPortfolioDelegate.kt index 28c78b4d76..21a34d471c 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/NewMarketsPortfolioDelegate.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/NewMarketsPortfolioDelegate.kt @@ -3,6 +3,7 @@ package com.tangem.features.feed.components.market.details.portfolio.impl.model import arrow.core.getOrElse import com.tangem.blockchainsdk.compatibility.getTokenIdIfL2Network import com.tangem.common.ui.account.AccountTitleUM +import com.tangem.common.ui.account.CryptoPortfolioIconConverter import com.tangem.common.ui.account.toUM import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.extensions.TextReference @@ -285,7 +286,7 @@ internal class NewMarketsPortfolioDelegate @AssistedInject constructor( prefixText = TextReference.EMPTY, name = this.accountName.toUM().value, icon = when (this) { - is Account.CryptoPortfolio -> this.icon.toUM() + is Account.CryptoPortfolio -> CryptoPortfolioIconConverter.convert(this.icon) is Account.Payment -> TODO("[REDACTED_JIRA]") }, ), diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/TokenActionsHandler.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/TokenActionsHandler.kt index 55d52cea7d..7bfe5544bd 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/TokenActionsHandler.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/TokenActionsHandler.kt @@ -1,20 +1,16 @@ package com.tangem.features.feed.components.market.details.portfolio.impl.model import com.tangem.common.routing.AppRoute -import com.tangem.common.ui.bottomsheet.receive.TokenReceiveBottomSheetConfig import com.tangem.common.ui.bottomsheet.receive.mapToAddressModels import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.decompose.navigation.Router import com.tangem.core.decompose.ui.UiMessageSender -import com.tangem.core.navigation.share.ShareManager import com.tangem.core.ui.clipboard.ClipboardManager -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.message.DialogMessage import com.tangem.core.ui.message.SnackbarMessage import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.demo.IsDemoCardUseCase -import com.tangem.domain.models.network.Network import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.onramp.model.OnrampSource import com.tangem.domain.redux.ReduxStateHolder @@ -36,11 +32,9 @@ internal class TokenActionsHandler @AssistedInject constructor( private val uiMessageSender: UiMessageSender, private val reduxStateHolder: ReduxStateHolder, @Assisted private val currentAppCurrency: Provider, - @Assisted private val updateTokenReceiveBSConfig: ((TangemBottomSheetConfig) -> TangemBottomSheetConfig) -> Unit, @Assisted private val onHandleQuickAction: (HandledQuickAction) -> Unit, private val isDemoCardUseCase: IsDemoCardUseCase, private val messageSender: UiMessageSender, - private val shareManager: ShareManager, ) { private val disabledActionsInDemoMode = buildSet { @@ -60,7 +54,7 @@ internal class TokenActionsHandler @AssistedInject constructor( when (action) { TokenActionsBSContentUM.Action.Buy -> onBuyClick(cryptoCurrencyData) TokenActionsBSContentUM.Action.Exchange -> onExchangeClick(cryptoCurrencyData) - TokenActionsBSContentUM.Action.Receive -> onReceiveClick(cryptoCurrencyData) + TokenActionsBSContentUM.Action.Receive -> Unit TokenActionsBSContentUM.Action.CopyAddress -> onCopyAddress(cryptoCurrencyData) TokenActionsBSContentUM.Action.Sell -> onSellClick(cryptoCurrencyData) TokenActionsBSContentUM.Action.Send -> onSendClick(cryptoCurrencyData) @@ -87,34 +81,6 @@ internal class TokenActionsHandler @AssistedInject constructor( messageSender.send(message) } - private fun onReceiveClick(cryptoCurrencyData: PortfolioData.CryptoCurrencyData) { - val cryptoCurrencyStatus = cryptoCurrencyData.status - val currency = cryptoCurrencyStatus.currency - val networkAddress = cryptoCurrencyStatus.value.networkAddress ?: return - - updateTokenReceiveBSConfig { - TangemBottomSheetConfig( - isShown = true, - onDismissRequest = { - updateTokenReceiveBSConfig { - it.copy(isShown = false) - } - }, - content = TokenReceiveBottomSheetConfig( - asset = TokenReceiveBottomSheetConfig.Asset.Currency( - name = currency.name, - symbol = currency.symbol, - ), - network = currency.network, - networkAddress = networkAddress, - showMemoDisclaimer = currency.network.transactionExtrasType != Network.TransactionExtrasType.NONE, - onCopyClick = { clipboardManager.setText(networkAddress.defaultAddress.value, isSensitive = true) }, - onShareClick = { shareManager.shareText(networkAddress.defaultAddress.value) }, - ), - ) - } - } - private fun onCopyAddress(cryptoCurrencyData: PortfolioData.CryptoCurrencyData) { val cryptoCurrencyStatus = cryptoCurrencyData.status val networkAddress = cryptoCurrencyStatus.value.networkAddress ?: return @@ -196,7 +162,6 @@ internal class TokenActionsHandler @AssistedInject constructor( interface Factory { fun create( currentAppCurrency: Provider, - updateTokenReceiveBSConfig: ((TangemBottomSheetConfig) -> TangemBottomSheetConfig) -> Unit, onHandleQuickAction: (HandledQuickAction) -> Unit, ): TokenActionsHandler } diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/TokensPortfolioUMConverter.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/TokensPortfolioUMConverter.kt index 4444e6a82f..249415e21f 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/TokensPortfolioUMConverter.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/TokensPortfolioUMConverter.kt @@ -12,7 +12,6 @@ import com.tangem.utils.converter.Converter import com.tangem.utils.extensions.isZero import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList -import kotlin.collections.map /** * Converter from [Map] of [UserWallet] and [CryptoCurrencyStatus] to [MyPortfolioUM.Tokens] @@ -50,9 +49,6 @@ internal class TokensPortfolioUMConverter( buttonState = addButtonState, addToPortfolioBSConfig = bsConfig, onAddClick = onAddClick, - tokenReceiveBSConfig = (currentState() as? MyPortfolioUM.Tokens) - ?.tokenReceiveBSConfig - ?: TangemBottomSheetConfig.Empty, ) } diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/MyPortfolio.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/MyPortfolio.kt index 7ff006cdb9..8d6911c448 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/MyPortfolio.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/MyPortfolio.kt @@ -16,7 +16,6 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastForEachIndexed import com.tangem.common.ui.account.AccountTitle -import com.tangem.common.ui.bottomsheet.receive.TokenReceiveBottomSheet import com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.components.SmallButtonShimmer @@ -140,8 +139,6 @@ private fun TokenList(state: MyPortfolioUM.Tokens, modifier: Modifier = Modifier } } } - - TokenReceiveBottomSheet(config = state.tokenReceiveBSConfig) } @Composable diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/preview/PreviewMyPortfolioUMProvider.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/preview/PreviewMyPortfolioUMProvider.kt index 3a40be94b8..8c6bf7a7a1 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/preview/PreviewMyPortfolioUMProvider.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/preview/PreviewMyPortfolioUMProvider.kt @@ -23,21 +23,18 @@ internal class PreviewMyPortfolioUMProvider : PreviewParameterProvider, val buttonState: AddButtonState, - val tokenReceiveBSConfig: TangemBottomSheetConfig, val onAddClick: () -> Unit, ) : MyPortfolioUM() { diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/featuretoggle/DefaultFeedFeatureToggle.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/featuretoggle/DefaultFeedFeatureToggle.kt index c15096b261..38b3458572 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/featuretoggle/DefaultFeedFeatureToggle.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/featuretoggle/DefaultFeedFeatureToggle.kt @@ -9,4 +9,7 @@ internal class DefaultFeedFeatureToggle( override val isFeedEnabled: Boolean get() = featureTogglesManager.isFeatureEnabled("FEED_ENABLED") + + override val isEarnBlockEnabled: Boolean + get() = featureTogglesManager.isFeatureEnabled("EARN_BLOCK_ENABLED") } \ No newline at end of file diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/feed/FeedComponentModel.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/feed/FeedComponentModel.kt index 769c975519..8c1774fcf8 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/feed/FeedComponentModel.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/feed/FeedComponentModel.kt @@ -47,10 +47,10 @@ internal class FeedComponentModel @Inject constructor( private val fetchTrendingNewsUseCase: FetchTrendingNewsUseCase, private val manageTrendingNewsUseCase: ManageTrendingNewsUseCase, private val analyticsEventHandler: AnalyticsEventHandler, + private val stateController: FeedStateController, getTopFiveMarketTokenUseCase: GetTopFiveMarketTokenUseCase, getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, paramsContainer: ParamsContainer, - private val stateController: FeedStateController, ) : Model() { private val params = paramsContainer.require() @@ -80,11 +80,9 @@ internal class FeedComponentModel @Inject constructor( init { initializeState() updateCallbacks() - - modelScope.launch(dispatchers.default) { - fetchTrendingNewsUseCase() - } - + fetchTrendingNews() + subscribeOnCurrencyUpdate() + loadCharts() modelScope.launch(dispatchers.default) { combine( flow = marketsBatchFlowManager.itemsByOrder, @@ -144,13 +142,23 @@ internal class FeedComponentModel @Inject constructor( } }.collect() } + } + private fun fetchTrendingNews() { + modelScope.launch(dispatchers.default) { + fetchTrendingNewsUseCase() + } + } + + private fun subscribeOnCurrencyUpdate() { modelScope.launch(dispatchers.default) { currentAppCurrency.drop(1).collect { marketsBatchFlowManager.reloadAll() } } + } + private fun loadCharts() { modelScope.launch(dispatchers.default) { TokenMarketListConfig.Order.entries.forEach { order -> marketsBatchFlowManager.getOnLastBatchLoadedSuccessFlow(order)?.collect { batchKey -> diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/feed/state/EarnListUM.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/feed/state/EarnListUM.kt new file mode 100644 index 0000000000..f703b958c0 --- /dev/null +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/feed/state/EarnListUM.kt @@ -0,0 +1,44 @@ +package com.tangem.features.feed.ui.feed.state + +import androidx.compose.runtime.Immutable +import com.tangem.core.ui.components.currency.icon.CurrencyIconState +import com.tangem.core.ui.extensions.TextReference +import com.tangem.domain.models.serialization.SerializedBigDecimal +import kotlinx.collections.immutable.ImmutableList + +internal data class EarnListUM( + val items: ImmutableList, + val contentState: EarnListContentState, +) + +@Immutable +internal data class EarnListItemUM( + val network: TextReference.Str, + val symbol: String, + val tokenName: String, + val currencyIconState: CurrencyIconState, + val earnValue: EarnValueUM, + val earnType: EarnType, + val onItemClick: () -> Unit, +) + +@Immutable +internal data class EarnValueUM( + val percent: SerializedBigDecimal, + val earnValueType: EarnValueType, +) + +internal enum class EarnType { + Staking, Yield +} + +internal enum class EarnValueType { + APR, APY +} + +@Immutable +internal sealed interface EarnListContentState { + data object Loading : EarnListContentState + data object Content : EarnListContentState + data class Error(val onRetryClicked: () -> Unit) : EarnListContentState +} \ No newline at end of file diff --git a/features/home/impl/src/main/kotlin/com/tangem/features/home/impl/model/HomeModel.kt b/features/home/impl/src/main/kotlin/com/tangem/features/home/impl/model/HomeModel.kt index e650912dc6..b261bf588d 100644 --- a/features/home/impl/src/main/kotlin/com/tangem/features/home/impl/model/HomeModel.kt +++ b/features/home/impl/src/main/kotlin/com/tangem/features/home/impl/model/HomeModel.kt @@ -190,7 +190,10 @@ internal class HomeModel @Inject constructor( return } - saveWalletUseCase(userWallet).fold( + saveWalletUseCase( + userWallet = userWallet, + analyticsSource = AnalyticsParam.ScreensSources.Intro, + ).fold( ifLeft = { delay(HIDE_PROGRESS_DELAY) setLoading(false) diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/createhardwarewallet/CreateHardwareWalletModel.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/createhardwarewallet/CreateHardwareWalletModel.kt index 39a2c8b242..5d32c8952b 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/createhardwarewallet/CreateHardwareWalletModel.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/createhardwarewallet/CreateHardwareWalletModel.kt @@ -133,7 +133,10 @@ internal class CreateHardwareWalletModel @Inject constructor( return } - saveWalletUseCase(userWallet = userWallet).fold( + saveWalletUseCase( + userWallet = userWallet, + analyticsSource = AnalyticsParam.ScreensSources.AddNew, + ).fold( ifLeft = { saveWalletError -> delay(HIDE_PROGRESS_DELAY) setLoading(false) diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/model/AddTokenUiBuilder.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/model/AddTokenUiBuilder.kt index 1d95ddde68..107c7a8c3b 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/model/AddTokenUiBuilder.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/model/AddTokenUiBuilder.kt @@ -1,8 +1,10 @@ package com.tangem.features.markets.portfolio.add.impl.model -import com.tangem.common.ui.account.CryptoPortfolioIconUM +import com.tangem.common.ui.account.AccountIconUM +import com.tangem.common.ui.account.CryptoPortfolioIconConverter import com.tangem.common.ui.account.PortfolioSelectUM import com.tangem.common.ui.account.toUM +import com.tangem.common.ui.addtoken.AddTokenUM import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter @@ -16,7 +18,6 @@ import com.tangem.features.markets.impl.R import com.tangem.features.markets.portfolio.add.api.SelectedNetwork import com.tangem.features.markets.portfolio.add.api.SelectedPortfolio import com.tangem.features.markets.portfolio.add.impl.AddTokenComponent -import com.tangem.common.ui.addtoken.AddTokenUM import javax.inject.Inject @ModelScoped @@ -35,7 +36,7 @@ internal class AddTokenUiBuilder @Inject constructor( } private fun createPortfolio(selectedPortfolio: SelectedPortfolio): PortfolioSelectUM { - val accountIcon: CryptoPortfolioIconUM? + val accountIcon: AccountIconUM.CryptoPortfolio? val portfolioName: TextReference when (selectedPortfolio.isAccountMode) { false -> { @@ -46,7 +47,7 @@ internal class AddTokenUiBuilder @Inject constructor( val accountStatus = selectedPortfolio.account.account portfolioName = accountStatus.account.accountName.toUM().value accountIcon = when (accountStatus) { - is AccountStatus.CryptoPortfolio -> accountStatus.account.icon.toUM() + is AccountStatus.CryptoPortfolio -> CryptoPortfolioIconConverter.convert(accountStatus.account.icon) } } } diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/model/TokenActionsModel.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/model/TokenActionsModel.kt index d3dd8d929b..466befc011 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/model/TokenActionsModel.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/model/TokenActionsModel.kt @@ -48,7 +48,6 @@ internal class TokenActionsModel @Inject constructor( private val tokenActionsHandler: TokenActionsHandler = tokenActionsIntentsFactory.create( currentAppCurrency = Provider { currentAppCurrency.value }, - updateTokenReceiveBSConfig = { }, onHandleQuickAction = { handledAction -> handledQuickAction(handledAction) }, ) diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt index 529f20adbc..0157de78d9 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt @@ -40,7 +40,6 @@ import com.tangem.features.markets.portfolio.impl.loader.PortfolioDataLoader import com.tangem.features.markets.portfolio.impl.ui.state.MyPortfolioUM import com.tangem.features.markets.portfolio.impl.ui.state.MyPortfolioUM.Tokens.AddButtonState import com.tangem.features.markets.portfolio.impl.ui.state.TokenActionsBSContentUM -import com.tangem.features.tokenreceive.TokenReceiveFeatureToggle import com.tangem.features.wallet.utils.UserWalletImageFetcher import com.tangem.lib.crypto.BlockchainUtils import com.tangem.operations.attestation.ArtworkSize @@ -69,12 +68,11 @@ internal class MarketsPortfolioModel @Inject constructor( private val saveMarketTokensUseCase: SaveMarketTokensUseCase, private val addToPortfolioManager: AddToPortfolioManager, private val analyticsEventHandler: AnalyticsEventHandler, - private val tokenReceiveFeatureToggle: TokenReceiveFeatureToggle, private val userWalletImageFetcher: UserWalletImageFetcher, private val receiveAddressesFactory: ReceiveAddressesFactory, - private val accountsFeatureToggles: AccountsFeatureToggles, + accountsFeatureToggles: AccountsFeatureToggles, newAddToPortfolioManagerFactory: NewAddToPortfolioManager.Factory, - private val newMarketsPortfolioDelegateFactory: NewMarketsPortfolioDelegate.Factory, + newMarketsPortfolioDelegateFactory: NewMarketsPortfolioDelegate.Factory, ) : Model() { private val _state: MutableStateFlow = MutableStateFlow(value = MyPortfolioUM.Loading) @@ -111,13 +109,6 @@ internal class MarketsPortfolioModel @Inject constructor( private val tokenActionsHandler = tokenActionsIntentsFactory.create( currentAppCurrency = Provider { currentAppCurrency.value }, - updateTokenReceiveBSConfig = { updateBlock -> - if (tokenReceiveFeatureToggle.isNewTokenReceiveEnabled.not()) { - updateTokensState { - it.copy(tokenReceiveBSConfig = updateBlock(it.tokenReceiveBSConfig)) - } - } - }, onHandleQuickAction = { handledAction -> val currencyNetwork = handledAction.cryptoCurrencyData.status.currency.network analyticsEventHandler.send( @@ -126,9 +117,7 @@ internal class MarketsPortfolioModel @Inject constructor( blockchainName = currencyNetwork.name, ), ) - if (tokenReceiveFeatureToggle.isNewTokenReceiveEnabled) { - configureReceiveAddresses(handledAction) - } + configureReceiveAddresses(handledAction) }, ) @@ -423,8 +412,7 @@ internal class MarketsPortfolioModel @Inject constructor( } private fun configureReceiveAddresses(quickAction: TokenActionsHandler.HandledQuickAction) { - val isNewReceive = quickAction.action == TokenActionsBSContentUM.Action.Receive && - tokenReceiveFeatureToggle.isNewTokenReceiveEnabled + val isNewReceive = quickAction.action == TokenActionsBSContentUM.Action.Receive if (isNewReceive) { modelScope.launch { val tokenConfig = receiveAddressesFactory.create( diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/NewMarketsPortfolioDelegate.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/NewMarketsPortfolioDelegate.kt index 15f9bd9da6..286e1dc18e 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/NewMarketsPortfolioDelegate.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/NewMarketsPortfolioDelegate.kt @@ -3,6 +3,7 @@ package com.tangem.features.markets.portfolio.impl.model import arrow.core.getOrElse import com.tangem.blockchainsdk.compatibility.getTokenIdIfL2Network import com.tangem.common.ui.account.AccountTitleUM +import com.tangem.common.ui.account.CryptoPortfolioIconConverter import com.tangem.common.ui.account.toUM import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.extensions.TextReference @@ -286,7 +287,7 @@ internal class NewMarketsPortfolioDelegate @AssistedInject constructor( prefixText = TextReference.EMPTY, name = this.accountName.toUM().value, icon = when (this) { - is Account.CryptoPortfolio -> this.icon.toUM() + is Account.CryptoPortfolio -> CryptoPortfolioIconConverter.convert(this.icon) is Account.Payment -> TODO("[REDACTED_JIRA]") }, ), diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt index 853080d794..66ff85116c 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokenActionsHandler.kt @@ -1,20 +1,16 @@ package com.tangem.features.markets.portfolio.impl.model import com.tangem.common.routing.AppRoute -import com.tangem.common.ui.bottomsheet.receive.TokenReceiveBottomSheetConfig import com.tangem.common.ui.bottomsheet.receive.mapToAddressModels import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.decompose.navigation.Router import com.tangem.core.decompose.ui.UiMessageSender -import com.tangem.core.navigation.share.ShareManager import com.tangem.core.ui.clipboard.ClipboardManager -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.message.DialogMessage import com.tangem.core.ui.message.SnackbarMessage import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.demo.IsDemoCardUseCase -import com.tangem.domain.models.network.Network import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.onramp.model.OnrampSource import com.tangem.domain.redux.ReduxStateHolder @@ -36,11 +32,9 @@ internal class TokenActionsHandler @AssistedInject constructor( private val uiMessageSender: UiMessageSender, private val reduxStateHolder: ReduxStateHolder, @Assisted private val currentAppCurrency: Provider, - @Assisted private val updateTokenReceiveBSConfig: ((TangemBottomSheetConfig) -> TangemBottomSheetConfig) -> Unit, @Assisted private val onHandleQuickAction: (HandledQuickAction) -> Unit, private val isDemoCardUseCase: IsDemoCardUseCase, private val messageSender: UiMessageSender, - private val shareManager: ShareManager, ) { private val disabledActionsInDemoMode = buildSet { @@ -60,7 +54,7 @@ internal class TokenActionsHandler @AssistedInject constructor( when (action) { TokenActionsBSContentUM.Action.Buy -> onBuyClick(cryptoCurrencyData) TokenActionsBSContentUM.Action.Exchange -> onExchangeClick(cryptoCurrencyData) - TokenActionsBSContentUM.Action.Receive -> onReceiveClick(cryptoCurrencyData) + TokenActionsBSContentUM.Action.Receive -> Unit TokenActionsBSContentUM.Action.CopyAddress -> onCopyAddress(cryptoCurrencyData) TokenActionsBSContentUM.Action.Sell -> onSellClick(cryptoCurrencyData) TokenActionsBSContentUM.Action.Send -> onSendClick(cryptoCurrencyData) @@ -87,34 +81,6 @@ internal class TokenActionsHandler @AssistedInject constructor( messageSender.send(message) } - private fun onReceiveClick(cryptoCurrencyData: PortfolioData.CryptoCurrencyData) { - val cryptoCurrencyStatus = cryptoCurrencyData.status - val currency = cryptoCurrencyStatus.currency - val networkAddress = cryptoCurrencyStatus.value.networkAddress ?: return - - updateTokenReceiveBSConfig { - TangemBottomSheetConfig( - isShown = true, - onDismissRequest = { - updateTokenReceiveBSConfig { - it.copy(isShown = false) - } - }, - content = TokenReceiveBottomSheetConfig( - asset = TokenReceiveBottomSheetConfig.Asset.Currency( - name = currency.name, - symbol = currency.symbol, - ), - network = currency.network, - networkAddress = networkAddress, - showMemoDisclaimer = currency.network.transactionExtrasType != Network.TransactionExtrasType.NONE, - onCopyClick = { clipboardManager.setText(networkAddress.defaultAddress.value, isSensitive = true) }, - onShareClick = { shareManager.shareText(networkAddress.defaultAddress.value) }, - ), - ) - } - } - private fun onCopyAddress(cryptoCurrencyData: PortfolioData.CryptoCurrencyData) { val cryptoCurrencyStatus = cryptoCurrencyData.status val networkAddress = cryptoCurrencyStatus.value.networkAddress ?: return @@ -196,7 +162,6 @@ internal class TokenActionsHandler @AssistedInject constructor( interface Factory { fun create( currentAppCurrency: Provider, - updateTokenReceiveBSConfig: ((TangemBottomSheetConfig) -> TangemBottomSheetConfig) -> Unit, onHandleQuickAction: (HandledQuickAction) -> Unit, ): TokenActionsHandler } diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokensPortfolioUMConverter.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokensPortfolioUMConverter.kt index 47fe6e3cf3..16f7f6554a 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokensPortfolioUMConverter.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/TokensPortfolioUMConverter.kt @@ -50,9 +50,6 @@ internal class TokensPortfolioUMConverter( buttonState = addButtonState, addToPortfolioBSConfig = bsConfig, onAddClick = onAddClick, - tokenReceiveBSConfig = (currentState() as? MyPortfolioUM.Tokens) - ?.tokenReceiveBSConfig - ?: TangemBottomSheetConfig.Empty, ) } diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/MyPortfolio.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/MyPortfolio.kt index 582bcc4690..1ea37db49c 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/MyPortfolio.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/MyPortfolio.kt @@ -16,7 +16,6 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastForEachIndexed import com.tangem.common.ui.account.AccountTitle -import com.tangem.common.ui.bottomsheet.receive.TokenReceiveBottomSheet import com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.components.SmallButtonShimmer @@ -141,8 +140,6 @@ private fun TokenList(state: MyPortfolioUM.Tokens, modifier: Modifier = Modifier } } } - - TokenReceiveBottomSheet(config = state.tokenReceiveBSConfig) } @Composable diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/preview/PreviewMyPortfolioUMProvider.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/preview/PreviewMyPortfolioUMProvider.kt index 4e1b5c5bd9..e6deaad138 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/preview/PreviewMyPortfolioUMProvider.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/preview/PreviewMyPortfolioUMProvider.kt @@ -93,21 +93,18 @@ internal class PreviewMyPortfolioUMProvider : PreviewParameterProvider, val buttonState: AddButtonState, - val tokenReceiveBSConfig: TangemBottomSheetConfig, val onAddClick: () -> Unit, ) : MyPortfolioUM() { diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/collections/entity/transformer/UpdateDataStateTransformer.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/collections/entity/transformer/UpdateDataStateTransformer.kt index e0256c93ba..6a2f95803d 100644 --- a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/collections/entity/transformer/UpdateDataStateTransformer.kt +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/collections/entity/transformer/UpdateDataStateTransformer.kt @@ -1,6 +1,7 @@ package com.tangem.features.nft.collections.entity.transformer import com.tangem.common.ui.account.AccountTitleUM +import com.tangem.common.ui.account.CryptoPortfolioIconConverter import com.tangem.common.ui.account.toUM import com.tangem.core.ui.components.fields.entity.SearchBarUM import com.tangem.core.ui.components.notifications.NotificationConfig @@ -100,7 +101,7 @@ internal class UpdateDataStateTransformer( prefixText = TextReference.EMPTY, name = this.accountName.toUM().value, icon = when (this) { - is Account.CryptoPortfolio -> this.icon.toUM() + is Account.CryptoPortfolio -> CryptoPortfolioIconConverter.convert(this.icon) is Account.Payment -> TODO("[REDACTED_JIRA]") }, ), diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/block/DefaultNFTDetailsBlockComponent.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/block/DefaultNFTDetailsBlockComponent.kt index a2b7f2f839..ff963aefcb 100644 --- a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/block/DefaultNFTDetailsBlockComponent.kt +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/block/DefaultNFTDetailsBlockComponent.kt @@ -3,6 +3,7 @@ package com.tangem.features.nft.details.block import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import com.tangem.common.ui.account.AccountTitleUM +import com.tangem.common.ui.account.CryptoPortfolioIconConverter import com.tangem.common.ui.account.toUM import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.ui.extensions.getActiveIconRes @@ -32,7 +33,7 @@ class DefaultNFTDetailsBlockComponent @AssistedInject constructor( AccountTitleUM.Account( prefixText = resourceReference(R.string.common_from), name = account.accountName.toUM().value, - icon = account.icon.toUM(), + icon = CryptoPortfolioIconConverter.convert(account.icon), ) } else { AccountTitleUM.Text(params.walletTitle) diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/entity/transformer/ShowReceiveBottomSheetTransformer.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/entity/transformer/ShowReceiveBottomSheetTransformer.kt deleted file mode 100644 index 65e142aaf6..0000000000 --- a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/entity/transformer/ShowReceiveBottomSheetTransformer.kt +++ /dev/null @@ -1,49 +0,0 @@ -package com.tangem.features.nft.receive.entity.transformer - -import com.tangem.common.ui.bottomsheet.receive.TokenReceiveBottomSheetConfig -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig -import com.tangem.core.ui.components.notifications.NotificationConfig -import com.tangem.domain.models.network.Network -import com.tangem.domain.models.network.NetworkAddress -import com.tangem.features.nft.receive.entity.NFTReceiveUM -import com.tangem.utils.transformer.Transformer -import com.tangem.core.ui.R -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.lib.crypto.BlockchainUtils -import kotlinx.collections.immutable.toPersistentList - -internal class ShowReceiveBottomSheetTransformer( - private val network: Network, - private val networkAddress: NetworkAddress, - private val onDismissBottomSheet: () -> Unit, - private val onCopyClick: (String) -> Unit, - private val onShareClick: (String) -> Unit, -) : Transformer { - - override fun transform(prevState: NFTReceiveUM): NFTReceiveUM = prevState.copy( - bottomSheetConfig = TangemBottomSheetConfig( - isShown = true, - onDismissRequest = onDismissBottomSheet, - content = TokenReceiveBottomSheetConfig( - asset = TokenReceiveBottomSheetConfig.Asset.NFT, - network = network, - networkAddress = networkAddress, - showMemoDisclaimer = network.transactionExtrasType != Network.TransactionExtrasType.NONE, - customNotifications = buildList { - if (BlockchainUtils.isSolana(network.rawId)) { - add( - NotificationConfig( - title = resourceReference(R.string.nft_receive_unsupported_types), - subtitle = resourceReference(R.string.nft_receive_unsupported_types_description), - iconResId = R.drawable.ic_alert_circle_24, - iconTint = NotificationConfig.IconTint.Attention, - ), - ) - } - }.toPersistentList(), - onCopyClick = onCopyClick, - onShareClick = onShareClick, - ), - ), - ) -} \ No newline at end of file diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/model/NFTReceiveModel.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/model/NFTReceiveModel.kt index 889afd28d6..6a32daa0a5 100644 --- a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/model/NFTReceiveModel.kt +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/model/NFTReceiveModel.kt @@ -8,8 +8,6 @@ import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.ui.UiMessageSender -import com.tangem.core.navigation.share.ShareManager -import com.tangem.core.ui.clipboard.ClipboardManager import com.tangem.core.ui.components.fields.InputManager import com.tangem.core.ui.components.fields.entity.SearchBarUM import com.tangem.core.ui.extensions.TextReference @@ -37,11 +35,9 @@ import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.features.nft.impl.R import com.tangem.features.nft.receive.NFTReceiveComponent import com.tangem.features.nft.receive.entity.NFTReceiveUM -import com.tangem.features.nft.receive.entity.transformer.ShowReceiveBottomSheetTransformer import com.tangem.features.nft.receive.entity.transformer.ToggleSearchBarTransformer import com.tangem.features.nft.receive.entity.transformer.UpdateDataStateTransformer import com.tangem.features.nft.receive.entity.transformer.UpdateSearchQueryTransformer -import com.tangem.features.tokenreceive.TokenReceiveFeatureToggle import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.flow.* @@ -56,11 +52,8 @@ internal class NFTReceiveModel @Inject constructor( private val getNFTNetworksUseCase: GetNFTNetworksUseCase, private val filterNFTAvailableNetworksUseCase: FilterNFTAvailableNetworksUseCase, private val getNFTNetworkStatusUseCase: GetNFTNetworkStatusUseCase, - private val clipboardManager: ClipboardManager, - private val shareManager: ShareManager, private val analyticsEventHandler: AnalyticsEventHandler, private val messageSender: UiMessageSender, - private val tokenReceiveFeatureToggle: TokenReceiveFeatureToggle, private val getNFTCurrencyUseCase: GetNFTCurrencyUseCase, private val receiveAddressesFactory: ReceiveAddressesFactory, private val getUserWalletUseCase: GetUserWalletUseCase, @@ -167,14 +160,6 @@ internal class NFTReceiveModel @Inject constructor( } } - private fun onReceiveBottomSheetDismiss() { - _state.update { - it.copy( - bottomSheetConfig = it.bottomSheetConfig?.copy(isShown = false), - ) - } - } - private fun onNetworkClick(network: Network, enabled: Boolean) { if (!enabled) { val message = DialogMessage( @@ -194,24 +179,12 @@ internal class NFTReceiveModel @Inject constructor( when (val value = networkStatus.value) { is NetworkStatus.Verified -> { - if (tokenReceiveFeatureToggle.isNewTokenReceiveEnabled) { - bottomSheetNavigation.activate( - configuration = configureReceiveAddresses( - addresses = value.address, - network = network, - ), - ) - } else { - _state.update { - ShowReceiveBottomSheetTransformer( - network = network, - networkAddress = value.address, - onDismissBottomSheet = ::onReceiveBottomSheetDismiss, - onCopyClick = { text -> onCopyClick(text, network) }, - onShareClick = { text -> onShareClick(text, network) }, - ).transform(it) - } - } + bottomSheetNavigation.activate( + configuration = configureReceiveAddresses( + addresses = value.address, + network = network, + ), + ) } is NetworkStatus.MissedDerivation, is NetworkStatus.NoAccount, @@ -222,16 +195,6 @@ internal class NFTReceiveModel @Inject constructor( } } - private fun onCopyClick(text: String, network: Network) { - analyticsEventHandler.send(NFTAnalyticsEvent.Receive.CopyAddress(network.name)) - clipboardManager.setText(text = text, isSensitive = true) - } - - private fun onShareClick(text: String, network: Network) { - analyticsEventHandler.send(NFTAnalyticsEvent.Receive.ShareAddress(network.name)) - shareManager.shareText(text = text) - } - private suspend fun configureReceiveAddresses(addresses: NetworkAddress, network: Network): TokenReceiveConfig { val cryptoCurrency = getNFTCurrencyUseCase.invoke(network) return receiveAddressesFactory.createForNft( diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/ui/NFTReceive.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/ui/NFTReceive.kt index dd9cfc1aa7..10a0c8c9f0 100644 --- a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/ui/NFTReceive.kt +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/receive/ui/NFTReceive.kt @@ -2,14 +2,13 @@ package com.tangem.features.nft.receive.ui import androidx.activity.compose.BackHandler import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import com.tangem.common.ui.bottomsheet.receive.TokenReceiveBottomSheet -import com.tangem.common.ui.bottomsheet.receive.TokenReceiveBottomSheetConfig import com.tangem.core.ui.components.appbar.TangemTopAppBar import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.fields.SearchBar import com.tangem.core.ui.components.fields.TangemSearchBarDefaults import com.tangem.core.ui.extensions.resolveReference @@ -48,17 +47,4 @@ internal fun NFTReceive(state: NFTReceiveUM, modifier: Modifier = Modifier) { is NFTReceiveUM.Networks.Empty -> NFTReceiveNetworksEmpty() } } - - ShowBottomSheet(state.bottomSheetConfig) -} - -@Composable -private fun ShowBottomSheet(bottomSheetConfig: TangemBottomSheetConfig?) { - if (bottomSheetConfig != null) { - when (bottomSheetConfig.content) { - is TokenReceiveBottomSheetConfig -> { - TokenReceiveBottomSheet(config = bottomSheetConfig) - } - } - } } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/chooseoption/model/Wallet1ChooseOptionModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/chooseoption/model/Wallet1ChooseOptionModel.kt index 8d508c9a4b..32c406b3b1 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/chooseoption/model/Wallet1ChooseOptionModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/chooseoption/model/Wallet1ChooseOptionModel.kt @@ -2,6 +2,7 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.child.chooseoption.mo import androidx.compose.runtime.Stable import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer @@ -50,17 +51,20 @@ internal class Wallet1ChooseOptionModel @Inject constructor( val scanResponse = params.multiWalletState.value.currentScanResponse val userWallet = createUserWallet(scanResponse) - saveWalletUseCase(userWallet, canOverride = true) - .onRight { - cardRepository.finishCardActivation(scanResponse.card.cardId) + saveWalletUseCase( + userWallet = userWallet, + canOverride = true, + analyticsSource = AnalyticsParam.ScreensSources.Onboarding, + ).onRight { + cardRepository.finishCardActivation(scanResponse.card.cardId) - // save user wallet for manage tokens screen - params.multiWalletState.update { - it.copy(resultUserWallet = userWallet) - } - - returnToParentFlow.emit(Unit) + // save user wallet for manage tokens screen + params.multiWalletState.update { + it.copy(resultUserWallet = userWallet) } + + returnToParentFlow.emit(Unit) + } } } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/MultiWalletCreateWalletModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/MultiWalletCreateWalletModel.kt index 1cb37f4eb6..42d2db0372 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/MultiWalletCreateWalletModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/MultiWalletCreateWalletModel.kt @@ -135,17 +135,20 @@ internal class MultiWalletCreateWalletModel @Inject constructor( val scanResponse = params.multiWalletState.value.currentScanResponse val userWallet = createUserWallet(scanResponse) - saveWalletUseCase(userWallet, canOverride = true) - .onRight { - cardRepository.finishCardActivation(scanResponse.card.cardId) + saveWalletUseCase( + userWallet = userWallet, + canOverride = true, + analyticsSource = AnalyticsParam.ScreensSources.Onboarding, + ).onRight { + cardRepository.finishCardActivation(scanResponse.card.cardId) - // save user wallet for manage tokens screen - params.multiWalletState.update { - it.copy(resultUserWallet = userWallet) - } - - onDone.emit(Step.Done) + // save user wallet for manage tokens screen + params.multiWalletState.update { + it.copy(resultUserWallet = userWallet) } + + onDone.emit(Step.Done) + } } } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/finalize/model/MultiWalletFinalizeModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/finalize/model/MultiWalletFinalizeModel.kt index 159ccf4bd9..0fb3c2f011 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/finalize/model/MultiWalletFinalizeModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/finalize/model/MultiWalletFinalizeModel.kt @@ -5,6 +5,7 @@ import arrow.core.getOrElse import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.ByteArrayKey +import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer @@ -267,6 +268,7 @@ internal class MultiWalletFinalizeModel @Inject constructor( scanResponse = scanResponse.updateScanResponseAfterBackup(), ), canOverride = true, + analyticsSource = AnalyticsParam.ScreensSources.Onboarding, ) userWalletCreated } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/upgradewallet/model/MultiWalletUpgradeWalletModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/upgradewallet/model/MultiWalletUpgradeWalletModel.kt index f6b6306cd6..d7e2c6e3e9 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/upgradewallet/model/MultiWalletUpgradeWalletModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/upgradewallet/model/MultiWalletUpgradeWalletModel.kt @@ -4,6 +4,7 @@ import androidx.compose.runtime.Stable import arrow.core.getOrElse import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError +import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer @@ -134,17 +135,20 @@ internal class MultiWalletUpgradeWalletModel @Inject constructor( val scanResponse = params.multiWalletState.value.currentScanResponse val userWallet = createUserWallet(scanResponse) - saveWalletUseCase(userWallet, canOverride = true) - .onRight { - cardRepository.finishCardActivation(scanResponse.card.cardId) + saveWalletUseCase( + userWallet, + canOverride = true, + analyticsSource = AnalyticsParam.ScreensSources.Onboarding, + ).onRight { + cardRepository.finishCardActivation(scanResponse.card.cardId) - // save user wallet for manage tokens screen - params.multiWalletState.update { - it.copy(resultUserWallet = userWallet) - } - - onDone.emit(Step.Done) + // save user wallet for manage tokens screen + params.multiWalletState.update { + it.copy(resultUserWallet = userWallet) } + + onDone.emit(Step.Done) + } } } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/create/model/OnboardingNoteCreateWalletModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/create/model/OnboardingNoteCreateWalletModel.kt index 51db083121..b286fead4f 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/create/model/OnboardingNoteCreateWalletModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/create/model/OnboardingNoteCreateWalletModel.kt @@ -82,7 +82,11 @@ internal class OnboardingNoteCreateWalletModel @Inject constructor( private fun createWalletAndNavigateBackWithDone(scanResponse: ScanResponse) { modelScope.launch { val userWallet = createUserWallet(scanResponse) - saveWalletUseCase(userWallet, canOverride = true).onRight { + saveWalletUseCase( + userWallet, + canOverride = true, + analyticsSource = AnalyticsParam.ScreensSources.Onboarding, + ).onRight { params.onWalletCreated(userWallet) } _uiState.update { diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/twin/impl/model/OnboardingTwinModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/twin/impl/model/OnboardingTwinModel.kt index e70843bd7b..79c8741734 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/twin/impl/model/OnboardingTwinModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/twin/impl/model/OnboardingTwinModel.kt @@ -300,6 +300,7 @@ internal class OnboardingTwinModel @Inject constructor( saveWalletUseCase( userWallet = userWallet, canOverride = true, + analyticsSource = AnalyticsParam.ScreensSources.Onboarding, ).onLeft { Timber.e("Unable to save user wallet: $it") setLoading(false) @@ -329,6 +330,7 @@ internal class OnboardingTwinModel @Inject constructor( saveWalletUseCase( userWallet = userWallet, canOverride = true, + analyticsSource = AnalyticsParam.ScreensSources.Onboarding, ).onLeft { Timber.e("Unable to save user wallet: $it") setLoading(false) diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/inprogress/model/OnboardingVisaInProgressModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/inprogress/model/OnboardingVisaInProgressModel.kt index 235c201961..abc373283f 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/inprogress/model/OnboardingVisaInProgressModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/inprogress/model/OnboardingVisaInProgressModel.kt @@ -4,6 +4,7 @@ import androidx.compose.runtime.Stable import arrow.core.getOrElse import com.tangem.common.extensions.toHexString import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer @@ -173,7 +174,10 @@ internal class OnboardingVisaInProgressModel @Inject constructor( } val userWallet = createUserWallet(params.scanResponse, newTokens) - saveWalletUseCase(userWallet) + saveWalletUseCase( + userWallet = userWallet, + analyticsSource = AnalyticsParam.ScreensSources.Onboarding, + ) visaAuthTokenStorage.remove(params.scanResponse.card.cardId) otpStorage.removeOTP(params.scanResponse.card.cardId) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/entity/OnrampAddTokenUiBuilder.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/entity/OnrampAddTokenUiBuilder.kt index edd05f63b2..a7b415212a 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/entity/OnrampAddTokenUiBuilder.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/portfolio/entity/OnrampAddTokenUiBuilder.kt @@ -1,6 +1,7 @@ package com.tangem.features.onramp.hottokens.portfolio.entity -import com.tangem.common.ui.account.CryptoPortfolioIconUM +import com.tangem.common.ui.account.AccountIconUM +import com.tangem.common.ui.account.CryptoPortfolioIconConverter import com.tangem.common.ui.account.PortfolioSelectUM import com.tangem.common.ui.account.toUM import com.tangem.common.ui.addtoken.AddTokenUM @@ -36,7 +37,7 @@ internal class OnrampAddTokenUiBuilder @Inject constructor( } private suspend fun createPortfolio(tokenToAdd: AddHotCryptoData): PortfolioSelectUM { - val accountIcon: CryptoPortfolioIconUM? + val accountIcon: AccountIconUM.CryptoPortfolio? val portfolioName: TextReference val isAccountMode = isAccountsModeEnabledUseCase.invokeSync() when (isAccountMode) { @@ -48,7 +49,7 @@ internal class OnrampAddTokenUiBuilder @Inject constructor( val accountStatus = tokenToAdd.account portfolioName = accountStatus.account.accountName.toUM().value accountIcon = when (accountStatus) { - is AccountStatus.CryptoPortfolio -> accountStatus.account.icon.toUM() + is AccountStatus.CryptoPortfolio -> CryptoPortfolioIconConverter.convert(accountStatus.account.icon) } } } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/ExchangeCardUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/ExchangeCardUM.kt index 0b159c1423..1d5877f437 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/ExchangeCardUM.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/ExchangeCardUM.kt @@ -1,7 +1,7 @@ package com.tangem.features.onramp.swap.entity import androidx.compose.runtime.Immutable -import com.tangem.common.ui.account.CryptoPortfolioIconUM +import com.tangem.common.ui.account.AccountIconUM import com.tangem.core.ui.components.token.state.TokenItemState import com.tangem.core.ui.extensions.TextReference @@ -57,7 +57,7 @@ internal sealed interface ExchangeCardUM { data class Account( val prefixText: TextReference, val name: TextReference, - val icon: CryptoPortfolioIconUM, + val icon: AccountIconUM.CryptoPortfolio, ) : TitleUM } } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/utils/ExchangeCardUMExt.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/utils/ExchangeCardUMExt.kt index 757945de5a..892b627a0c 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/utils/ExchangeCardUMExt.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/entity/utils/ExchangeCardUMExt.kt @@ -1,5 +1,6 @@ package com.tangem.features.onramp.swap.entity.utils +import com.tangem.common.ui.account.CryptoPortfolioIconConverter import com.tangem.common.ui.account.toUM import com.tangem.core.ui.components.token.state.TokenItemState import com.tangem.core.ui.extensions.resourceReference @@ -45,7 +46,7 @@ internal fun ExchangeCardUM.toFilled( resourceReference(R.string.common_to) }, name = account.accountName.toUM().value, - icon = account.icon.toUM(), + icon = CryptoPortfolioIconConverter.convert(account.icon), ) } else { titleUM diff --git a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt index 02ddfaaf01..c7f8ef28fe 100644 --- a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt +++ b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt @@ -10,23 +10,23 @@ import com.tangem.domain.models.account.DerivationIndex import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase +import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.usecase.DerivePublicKeysUseCase import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.feature.referral.domain.errors.ReferralError import com.tangem.feature.referral.domain.models.ReferralData import com.tangem.feature.referral.domain.models.TokenData -import com.tangem.lib.crypto.UserWalletManager import timber.log.Timber @Suppress("LongParameterList") internal class ReferralInteractorImpl( private val repository: ReferralRepository, - private val userWalletManager: UserWalletManager, private val derivePublicKeysUseCase: DerivePublicKeysUseCase, private val getUserWalletUseCase: GetUserWalletUseCase, private val addCryptoCurrenciesUseCase: AddCryptoCurrenciesUseCase, private val manageCryptoCurrenciesUseCase: ManageCryptoCurrenciesUseCase, private val singleAccountSupplier: SingleAccountSupplier, + private val walletManagersFacade: WalletManagersFacade, ) : ReferralInteractor { private val tokensForReferral = mutableListOf() @@ -85,13 +85,14 @@ internal class ReferralInteractorImpl( } .onLeft(Timber::e) - val publicAddress = userWalletManager.getWalletAddress( - networkId = tokenData.networkId, - derivationPath = cryptoCurrency.network.derivationPath.value, + val publicAddress = walletManagersFacade.getDefaultAddress( + userWalletId = userWalletId, + network = cryptoCurrency.network, ) + ?: error("Address not found: ${cryptoCurrency.network.id}") return repository.startReferral( - walletId = userWalletManager.getWalletId(), + walletId = userWalletId.stringValue, networkId = tokenData.networkId, tokenId = tokenData.id, address = publicAddress, diff --git a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/di/ReferralDomainModule.kt b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/di/ReferralDomainModule.kt index 09a1b77423..c82165bda3 100644 --- a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/di/ReferralDomainModule.kt +++ b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/di/ReferralDomainModule.kt @@ -5,12 +5,12 @@ import com.tangem.core.decompose.di.ModelScoped import com.tangem.domain.account.status.usecase.ManageCryptoCurrenciesUseCase import com.tangem.domain.account.supplier.SingleAccountSupplier import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase +import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.usecase.DerivePublicKeysUseCase import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.feature.referral.domain.ReferralInteractor import com.tangem.feature.referral.domain.ReferralInteractorImpl import com.tangem.feature.referral.domain.ReferralRepository -import com.tangem.lib.crypto.UserWalletManager import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -23,21 +23,21 @@ class ReferralDomainModule { @ModelScoped fun provideReferralInteractor( referralRepository: ReferralRepository, - userWalletManager: UserWalletManager, derivePublicKeysUseCase: DerivePublicKeysUseCase, getUserWalletUseCase: GetUserWalletUseCase, addCryptoCurrenciesUseCase: AddCryptoCurrenciesUseCase, manageCryptoCurrenciesUseCase: ManageCryptoCurrenciesUseCase, singleAccountSupplier: SingleAccountSupplier, + walletManagersFacade: WalletManagersFacade, ): ReferralInteractor { return ReferralInteractorImpl( repository = referralRepository, - userWalletManager = userWalletManager, derivePublicKeysUseCase = derivePublicKeysUseCase, getUserWalletUseCase = getUserWalletUseCase, addCryptoCurrenciesUseCase = addCryptoCurrenciesUseCase, manageCryptoCurrenciesUseCase = manageCryptoCurrenciesUseCase, singleAccountSupplier = singleAccountSupplier, + walletManagersFacade = walletManagersFacade, ) } } \ No newline at end of file diff --git a/features/referral/impl/src/main/java/com/tangem/feature/referral/model/AccountAwardConverter.kt b/features/referral/impl/src/main/java/com/tangem/feature/referral/model/AccountAwardConverter.kt index e5f6db7819..e5ad207a29 100644 --- a/features/referral/impl/src/main/java/com/tangem/feature/referral/model/AccountAwardConverter.kt +++ b/features/referral/impl/src/main/java/com/tangem/feature/referral/model/AccountAwardConverter.kt @@ -1,5 +1,6 @@ package com.tangem.feature.referral.model +import com.tangem.common.ui.account.CryptoPortfolioIconConverter import com.tangem.common.ui.account.PortfolioSelectUM import com.tangem.common.ui.account.toUM import com.tangem.common.ui.tokens.TokenItemStateConverter.Companion.getFormattedCryptoAmount @@ -62,7 +63,7 @@ internal class AccountAwardConverter( isBalanceHidden = isBalanceHidden, tokenState = tokenState, accountSelectUM = PortfolioSelectUM( - icon = cryptoPortfolio.account.icon.toUM(), + icon = CryptoPortfolioIconConverter.convert(cryptoPortfolio.account.icon), name = cryptoPortfolio.account.accountName.toUM().value, isAccountMode = true, onClick = onAccountClick, diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/SendConfirmModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/SendConfirmModel.kt index 31b351c7d3..8f3da0e8ed 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/SendConfirmModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/SendConfirmModel.kt @@ -19,8 +19,11 @@ import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.navigation.Router import com.tangem.core.navigation.share.ShareManager import com.tangem.core.navigation.url.UrlOpener +import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.extensions.wrappedList +import com.tangem.domain.models.wallet.isHotWallet import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles import com.tangem.domain.account.status.usecase.ManageCryptoCurrenciesUseCase import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase @@ -592,21 +595,16 @@ internal class SendConfirmModel @Inject constructor( private fun primaryButtonUM(): NavigationButton { val confirmUM = uiState.value.confirmUM - val isReadyToSend = confirmUM is ConfirmUM.Content && !confirmUM.isSending + val isContent = confirmUM is ConfirmUM.Content + val isReadyToSend = isContent && !confirmUM.isSending + val isHoldToConfirm = userWallet.isHotWallet && isContent return NavigationButton( - textReference = when (confirmUM) { - is ConfirmUM.Success -> resourceReference(R.string.common_close) - is ConfirmUM.Content -> if (confirmUM.isSending) { - resourceReference(R.string.send_sending) - } else { - resourceReference(R.string.common_send) - } - else -> resourceReference(R.string.common_send) - }, + textReference = getPrimaryButtonText(confirmUM, isHoldToConfirm), iconRes = walletInterationIcon(userWallet), - isIconVisible = isReadyToSend, + isIconVisible = isReadyToSend && !isHoldToConfirm, isEnabled = confirmUM.isPrimaryButtonEnabled, isHapticClick = isReadyToSend, + isHoldToConfirm = isHoldToConfirm, onClick = { when (confirmUM) { is ConfirmUM.Success -> appRouter.pop() @@ -621,6 +619,18 @@ internal class SendConfirmModel @Inject constructor( ) } + private fun getPrimaryButtonText(confirmUM: ConfirmUM, isHoldToConfirm: Boolean): TextReference { + return when { + isHoldToConfirm -> resourceReference( + id = com.tangem.core.ui.R.string.common_hold_to, + formatArgs = wrappedList(resourceReference(R.string.common_send)), + ) + confirmUM is ConfirmUM.Success -> resourceReference(R.string.common_close) + confirmUM is ConfirmUM.Content && confirmUM.isSending -> resourceReference(R.string.send_sending) + else -> resourceReference(R.string.common_send) + } + } + override fun onFeeResult(feeSelectorUM: FeeSelectorUMRedesigned) { sendIdleTimer = SystemClock.elapsedRealtime() _uiState.update { it.copy(feeSelectorUM = feeSelectorUM) } diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/converters/SendRecipientWalletListConverter.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/converters/SendRecipientWalletListConverter.kt index 94223cd153..c70c805b16 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/converters/SendRecipientWalletListConverter.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/converters/SendRecipientWalletListConverter.kt @@ -1,6 +1,7 @@ package com.tangem.features.send.v2.subcomponents.destination.model.converters import com.tangem.common.ui.account.AccountTitleUM +import com.tangem.common.ui.account.CryptoPortfolioIconConverter import com.tangem.common.ui.account.toUM import com.tangem.core.ui.extensions.stringReference import com.tangem.domain.models.currency.CryptoCurrency @@ -58,7 +59,7 @@ internal class SendRecipientWalletListConverter( accountTitleUM = if (account != null && isAccountsMode) { AccountTitleUM.Account( name = account.accountName.toUM().value, - icon = account.icon.toUM(), + icon = CryptoPortfolioIconConverter.convert(account.icon), prefixText = stringReference(StringsSigns.DOT), ) } else { diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/ListItemWithIcon.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/ListItemWithIcon.kt index 484ac8b89a..51573ec74f 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/ListItemWithIcon.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/ListItemWithIcon.kt @@ -27,7 +27,7 @@ import androidx.compose.ui.unit.dp import com.tangem.common.ui.account.AccountNameUM import com.tangem.common.ui.account.AccountTitle import com.tangem.common.ui.account.AccountTitleUM -import com.tangem.common.ui.account.toUM +import com.tangem.common.ui.account.CryptoPortfolioIconConverter import com.tangem.core.ui.components.CircleShimmer import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.components.atoms.text.EllipsisText @@ -271,7 +271,7 @@ private class ListItemWithIconPreviewProvider : CollectionPreviewParameterProvid subtitle = "Wallet", accountTitleUM = AccountTitleUM.Account( name = AccountNameUM.DefaultMain.value, - icon = CryptoPortfolioIcon.ofDefaultCustomAccount().toUM(), + icon = CryptoPortfolioIconConverter.convert(CryptoPortfolioIcon.ofDefaultCustomAccount()), prefixText = stringReference(StringsSigns.DOT), ), ), diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt index 6f6b6b2c2e..66d822dd46 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt @@ -18,8 +18,11 @@ import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.navigation.Router +import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.wrappedList import com.tangem.domain.express.models.ExpressOperationType +import com.tangem.domain.models.wallet.isHotWallet import com.tangem.domain.express.models.ExpressProviderType import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus @@ -485,7 +488,9 @@ internal class SendWithSwapConfirmModel @Inject constructor( it.second is SendWithSwapRoute.Confirm }.onEach { (state, _) -> val confirmUM = state.confirmUM - val isReadyToSend = confirmUM is ConfirmUM.Content && !confirmUM.isTransactionInProcess + val isContent = confirmUM is ConfirmUM.Content + val isReadyToSend = isContent && !confirmUM.isTransactionInProcess + val isHoldToConfirm = params.userWallet.isHotWallet && isContent params.callback.onResult( route = SendWithSwapRoute.Confirm, sendWithSwapUM = state.copy( @@ -496,18 +501,11 @@ internal class SendWithSwapConfirmModel @Inject constructor( backIconRes = R.drawable.ic_back_24, backIconClick = router::pop, primaryButton = NavigationButton( - textReference = when (confirmUM) { - is ConfirmUM.Success -> resourceReference(R.string.common_close) - is ConfirmUM.Content -> if (confirmUM.isTransactionInProcess) { - resourceReference(R.string.send_sending) - } else { - resourceReference(R.string.common_send) - } - else -> resourceReference(R.string.common_send) - }, + textReference = getPrimaryButtonText(confirmUM, isHoldToConfirm), iconRes = walletInterationIcon(params.userWallet), - isIconVisible = isReadyToSend, + isIconVisible = isReadyToSend && !isHoldToConfirm, isHapticClick = isReadyToSend, + isHoldToConfirm = isHoldToConfirm, isEnabled = confirmUM.isPrimaryButtonEnabled, onClick = { when (confirmUM) { @@ -525,4 +523,17 @@ internal class SendWithSwapConfirmModel @Inject constructor( ) }.launchIn(modelScope) } + + private fun getPrimaryButtonText(confirmUM: ConfirmUM, isHoldToConfirm: Boolean): TextReference { + return when { + isHoldToConfirm -> resourceReference( + id = com.tangem.core.ui.R.string.common_hold_to, + formatArgs = wrappedList(resourceReference(R.string.common_send)), + ) + confirmUM is ConfirmUM.Success -> resourceReference(R.string.common_close) + confirmUM is ConfirmUM.Content && confirmUM.isTransactionInProcess -> + resourceReference(R.string.send_sending) + else -> resourceReference(R.string.common_send) + } + } } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index 217144c02a..a8b55e5af2 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -50,6 +50,7 @@ import com.tangem.domain.transaction.usecase.gasless.EstimateFeeForGaslessTxUseC import com.tangem.domain.transaction.usecase.gasless.EstimateFeeForTokenUseCase import com.tangem.domain.transaction.usecase.gasless.GetFeeForTokenUseCase import com.tangem.domain.utils.convertToSdkAmount +import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.feature.swap.domain.api.SwapRepository import com.tangem.feature.swap.domain.models.ExpressDataError @@ -58,8 +59,6 @@ import com.tangem.feature.swap.domain.models.domain.* import com.tangem.feature.swap.domain.models.toStringWithRightOffset import com.tangem.feature.swap.domain.models.ui.* import com.tangem.lib.crypto.BlockchainUtils.SOLANA_TRANSACTION_SIZE_THRESHOLD_BYTES -import com.tangem.lib.crypto.UserWalletManager -import com.tangem.lib.crypto.models.ProxyAmount import com.tangem.utils.coroutines.runSuspendCatching import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -72,7 +71,6 @@ import java.math.RoundingMode @Suppress("LargeClass", "LongParameterList") internal class SwapInteractorImpl @AssistedInject constructor( - private val userWalletManager: UserWalletManager, private val repository: SwapRepository, private val allowPermissionsHandler: AllowPermissionsHandler, private val getMultiCryptoCurrencyStatusUseCase: GetMultiCryptoCurrencyStatusUseCase, @@ -107,6 +105,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier, private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase, private val accountsFeatureToggles: AccountsFeatureToggles, + private val walletManagersFacade: WalletManagersFacade, @Assisted private val userWalletId: UserWalletId, ) : SwapInteractor { @@ -706,11 +705,13 @@ internal class SwapInteractorImpl @AssistedInject constructor( ) when (feePaidCurrency) { FeePaidCurrency.Coin -> { - val nativeBalance = userWalletManager.getNativeTokenBalance( + val nativeBalance = walletManagersFacade.getNativeTokenBalance( + userWalletId = userWalletId, networkId = fromTokenStatus.currency.network.backendId, derivationPath = fromTokenStatus.currency.network.derivationPath.value, ) - nativeBalance?.let { it.value - fee } + + nativeBalance - fee } else -> null // it doesnt matter for this fun } @@ -1292,8 +1293,8 @@ internal class SwapInteractorImpl @AssistedInject constructor( private suspend fun storeLastCryptoCurrencyId(cryptoCurrency: CryptoCurrency) { swapTransactionRepository.storeLastSwappedCryptoCurrencyId( - UserWalletId(userWalletManager.getWalletId()), - cryptoCurrency.id, + userWalletId = userWalletId, + cryptoCurrencyId = cryptoCurrency.id, ) } @@ -1705,12 +1706,12 @@ internal class SwapInteractorImpl @AssistedInject constructor( feeValue: BigDecimal, fromToken: CryptoCurrency, ): IncludeFeeInAmount { - val tokenForFeeBalance = - userWalletManager.getNativeTokenBalance( - networkId, - fromToken.network.derivationPath.value, - ) ?: ProxyAmount.empty() - val reducedBalance = tokenForFeeBalance.value - reduceBalanceBy + val tokenForFeeBalance = walletManagersFacade.getNativeTokenBalance( + userWalletId = userWalletId, + networkId = networkId, + derivationPath = fromToken.network.derivationPath.value, + ) + val reducedBalance = tokenForFeeBalance - reduceBalanceBy val amountWithFee = amount.value + feeValue return when { fromToken is CryptoCurrency.Token -> { @@ -1941,13 +1942,14 @@ internal class SwapInteractorImpl @AssistedInject constructor( fromToken: CryptoCurrency, selectedToken: CryptoCurrencyStatus? = null, ): Either = either { - val nativeBalance = userWalletManager.getNativeTokenBalance( + val nativeBalance = walletManagersFacade.getNativeTokenBalance( + userWalletId = userWalletId, networkId = network.backendId, derivationPath = fromToken.network.derivationPath.value, - ) ?: ProxyAmount.empty() + ) // if native balance is zero - we can't calculate fee - if (nativeBalance.value.signum() == 0) { + if (nativeBalance.signum() == 0) { raise(ExpressDataError.UnknownError) } @@ -1956,7 +1958,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( val amountToSend = createNativeAmountForDex(txAmountValue, fromToken.network) // transaction.txValue is always native coin - if (nativeBalance.value < amountToSend.value) { + if (nativeBalance < amountToSend.value) { error("It's impossible to calculate fee for nativeBalance.value < amountToSend.value") } @@ -2105,7 +2107,6 @@ internal class SwapInteractorImpl @AssistedInject constructor( permissionState = PermissionDataState.PermissionLoading, ) } - val derivationPath = fromToken.network.derivationPath.value // setting up amount for approve with given amount for swap [SwapApproveType.Limited] val callData = SmartContractCallDataProviderFactory.getApprovalCallData( spenderAddress = requireNotNull(spenderAddress) { "spenderAddress cant be null" }, @@ -2164,7 +2165,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( permissionState = PermissionDataState.PermissionReadyForRequest( currency = fromToken.symbol, amount = INFINITY_SYMBOL, - walletAddress = getWalletAddress(networkId, derivationPath), + walletAddress = getWalletAddress(fromToken.network), spenderAddress = getTokenAddress(fromToken), requestApproveData = RequestApproveStateData( fee = feeState, @@ -2403,8 +2404,9 @@ internal class SwapInteractorImpl @AssistedInject constructor( ) } - private suspend fun getWalletAddress(networkId: String, derivationPath: String?): String { - return userWalletManager.getWalletAddress(networkId, derivationPath) + private suspend fun getWalletAddress(network: Network): String { + return walletManagersFacade.getDefaultAddress(userWalletId, network) + ?: error("Address not found for network: ${network.id}") } private fun getTokenAddress(currency: CryptoCurrency): String { @@ -2436,31 +2438,29 @@ internal class SwapInteractorImpl @AssistedInject constructor( val percentsToFeeIncrease = BigDecimal.ONE return when (val feePaidCurrency = getFeePaidCurrency(fromTokenStatus.currency)) { FeePaidCurrency.Coin -> { - val nativeTokenBalance = userWalletManager.getNativeTokenBalance( - networkId, - fromTokenStatus.currency.network.derivationPath.value, + val nativeTokenBalance = walletManagersFacade.getNativeTokenBalance( + userWalletId = userWalletId, + networkId = networkId, + derivationPath = fromTokenStatus.currency.network.derivationPath.value, ) - nativeTokenBalance?.let { balance -> - val balanceToCheck = when (fromTokenStatus.currency) { - is CryptoCurrency.Token -> { - balance.value - } - is CryptoCurrency.Coin -> { - // need to check balance minus amount only if amount to swap in native token - balance.value.minus(spendAmount.value) - } + + val balanceToCheck = when (fromTokenStatus.currency) { + is CryptoCurrency.Token -> nativeTokenBalance + is CryptoCurrency.Coin -> { + // need to check balance minus amount only if amount to swap in native token + nativeTokenBalance.minus(spendAmount.value) } - if (balanceToCheck > fee.multiply(percentsToFeeIncrease)) { - SwapFeeState.Enough - } else { - val nativeToken = getNativeToken(fromTokenStatus.currency.network.backendId) - SwapFeeState.NotEnough( - feeCurrency = nativeToken, - currencyName = nativeToken.network.name, - currencySymbol = nativeToken.symbol, - ) - } - } ?: SwapFeeState.NotEnough() + } + if (balanceToCheck > fee.multiply(percentsToFeeIncrease)) { + SwapFeeState.Enough + } else { + val nativeToken = getNativeToken(fromTokenStatus.currency.network.backendId) + SwapFeeState.NotEnough( + feeCurrency = nativeToken, + currencyName = nativeToken.network.name, + currencySymbol = nativeToken.symbol, + ) + } } FeePaidCurrency.SameCurrency -> { val balance = fromTokenStatus.value.amount ?: return SwapFeeState.NotEnough() diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt index a007a6697f..8f5bd07b10 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt @@ -104,6 +104,7 @@ import com.tangem.utils.TangemBlogUrlBuilder.RESOURCE_TO_LEARN_ABOUT_APPROVING_I import com.tangem.utils.coroutines.* import com.tangem.utils.isNullOrZero import kotlinx.coroutines.NonCancellable +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -957,6 +958,10 @@ internal class SwapModel @Inject constructor( if (fee == null && tangemPayInput?.isWithdrawal != true) { makeDefaultAlert(resourceReference(R.string.swapping_fee_estimation_error_text)) + modelScope.launch { + delay(SWAP_IN_PROGRESS_DELAY) + startLoadingQuotesFromLastState() + } return } modelScope.launch(dispatchers.main) { @@ -2250,6 +2255,7 @@ internal class SwapModel @Inject constructor( const val DEBOUNCE_AMOUNT_DELAY = 1000L const val DEBOUNCE_SEARCH_DELAY = 500L const val UPDATE_BALANCE_DELAY_MILLIS = 11000L + const val SWAP_IN_PROGRESS_DELAY = 200L const val CHANGELLY_PROVIDER_ID = "changelly" } } \ No newline at end of file diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt index f37d7dd2fe..47517e2801 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt @@ -79,6 +79,7 @@ data class SwapButton( @DrawableRes val walletInteractionIcon: Int?, val isEnabled: Boolean, val isInProgress: Boolean = false, + val isHoldToConfirm: Boolean = false, val onClick: () -> Unit, ) diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/preview/SwapSuccessStatePreview.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/preview/SwapSuccessStatePreview.kt index 77c343f43b..df255cc346 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/preview/SwapSuccessStatePreview.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/preview/SwapSuccessStatePreview.kt @@ -2,7 +2,7 @@ package com.tangem.feature.swap.preview import com.tangem.common.ui.account.AccountNameUM import com.tangem.common.ui.account.AccountTitleUM -import com.tangem.common.ui.account.toUM +import com.tangem.common.ui.account.CryptoPortfolioIconConverter import com.tangem.core.ui.components.currency.icon.CurrencyIconState import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.stringReference @@ -22,12 +22,12 @@ internal data object SwapSuccessStatePreview { fromTitle = AccountTitleUM.Account( prefixText = stringReference("From"), name = AccountNameUM.DefaultMain.value, - icon = CryptoPortfolioIcon.ofDefaultCustomAccount().toUM(), + icon = CryptoPortfolioIconConverter.convert(CryptoPortfolioIcon.ofDefaultCustomAccount()), ), toTitle = AccountTitleUM.Account( prefixText = stringReference("To"), name = AccountNameUM.DefaultMain.value, - icon = CryptoPortfolioIcon.ofDefaultCustomAccount().toUM(), + icon = CryptoPortfolioIconConverter.convert(CryptoPortfolioIcon.ofDefaultCustomAccount()), ), fromTokenAmount = TextReference.Str("1 000 DAI"), toTokenAmount = TextReference.Str("1 000 MATIC"), diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index 33bf20ea2b..f43c1f4caa 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -3,6 +3,7 @@ package com.tangem.feature.swap.ui import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.input.TextFieldValue import com.tangem.common.ui.account.AccountTitleUM +import com.tangem.common.ui.account.CryptoPortfolioIconConverter import com.tangem.common.ui.account.toUM import com.tangem.common.ui.alerts.models.AlertDemoModeUM import com.tangem.common.ui.bottomsheet.permission.state.* @@ -24,6 +25,7 @@ import com.tangem.domain.models.account.Account import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.models.wallet.isHotWallet import com.tangem.domain.promo.models.StoryContent import com.tangem.domain.transaction.usecase.gasless.IsGaslessFeeSupportedForNetwork import com.tangem.feature.swap.converters.SwapTransactionErrorStateConverter @@ -125,6 +127,7 @@ internal class StateBuilder( swapButton = SwapButton( walletInteractionIcon = walletInterationIcon(userWalletProvider()), isEnabled = false, + isHoldToConfirm = userWalletProvider().isHotWallet, onClick = {}, ), onRefresh = {}, @@ -184,6 +187,7 @@ internal class StateBuilder( swapButton = SwapButton( walletInteractionIcon = walletInterationIcon(userWalletProvider()), isEnabled = false, + isHoldToConfirm = userWalletProvider().isHotWallet, onClick = { }, ), changeCardsButtonState = ChangeCardsButtonState.DISABLED, @@ -249,6 +253,7 @@ internal class StateBuilder( swapButton = SwapButton( walletInteractionIcon = walletInterationIcon(userWalletProvider()), isEnabled = false, + isHoldToConfirm = userWalletProvider().isHotWallet, onClick = {}, ), providerState = ProviderState.Loading(), @@ -371,6 +376,7 @@ internal class StateBuilder( swapButton = SwapButton( walletInteractionIcon = walletInterationIcon(userWalletProvider()), isEnabled = getSwapButtonEnabled(notifications), + isHoldToConfirm = userWalletProvider().isHotWallet, onClick = actions.onSwapClick, ), changeCardsButtonState = getChangeCardsButtonState(isReverseSwapPossible), @@ -499,6 +505,7 @@ internal class StateBuilder( swapButton = SwapButton( walletInteractionIcon = walletInterationIcon(userWalletProvider()), isEnabled = false, + isHoldToConfirm = userWalletProvider().isHotWallet, onClick = actions.onSwapClick, ), changeCardsButtonState = getChangeCardsButtonState(isReverseSwapPossible), @@ -596,6 +603,7 @@ internal class StateBuilder( swapButton = SwapButton( walletInteractionIcon = walletInterationIcon(userWalletProvider()), isEnabled = false, + isHoldToConfirm = userWalletProvider().isHotWallet, onClick = { }, ), changeCardsButtonState = getChangeCardsButtonState(isReverseSwapPossible), @@ -1421,7 +1429,7 @@ internal class StateBuilder( AccountTitleUM.Account( prefixText = resourceReference(R.string.common_from), name = fromAccount.accountName.toUM().value, - icon = fromAccount.icon.toUM(), + icon = CryptoPortfolioIconConverter.convert(fromAccount.icon), ) } else { AccountTitleUM.Text(resourceReference(R.string.swapping_from_title)) @@ -1433,7 +1441,7 @@ internal class StateBuilder( AccountTitleUM.Account( prefixText = resourceReference(R.string.common_to), name = toAccount.accountName.toUM().value, - icon = toAccount.icon.toUM(), + icon = CryptoPortfolioIconConverter.convert(toAccount.icon), ) } else { AccountTitleUM.Text(resourceReference(R.string.swapping_to_title)) diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt index a1cb85f872..e55c8398bf 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt @@ -370,25 +370,42 @@ private fun SwapNotifications(notifications: List) { @Composable private fun MainButton(state: SwapStateHolder) { - if (state.isInsufficientFunds) { - PrimaryButton( - modifier = Modifier.fillMaxWidth(), - text = stringResourceSafe(id = R.string.swapping_insufficient_funds), - enabled = false, - onClick = state.swapButton.onClick, - ) - } else { - PrimaryButtonIconEnd( - modifier = Modifier.fillMaxWidth(), - text = if (state.swapButton.isInProgress) { - stringResourceSafe(id = R.string.swapping_swap_action_in_progress) - } else { - stringResourceSafe(id = R.string.swapping_swap_action) - }, - iconResId = state.swapButton.walletInteractionIcon, - enabled = state.swapButton.isEnabled, - onClick = state.swapButton.onClick, - ) + when { + state.isInsufficientFunds -> { + PrimaryButton( + modifier = Modifier.fillMaxWidth(), + text = stringResourceSafe(id = R.string.swapping_insufficient_funds), + enabled = false, + onClick = state.swapButton.onClick, + ) + } + + state.swapButton.isHoldToConfirm -> { + HoldToConfirmButton( + modifier = Modifier.fillMaxWidth(), + text = stringResourceSafe( + R.string.common_hold_to, + stringResourceSafe(id = R.string.swapping_swap_action), + ), + enabled = state.swapButton.isEnabled, + onConfirm = state.swapButton.onClick, + isLoading = state.swapButton.isInProgress, + ) + } + + else -> { + PrimaryButtonIconEnd( + modifier = Modifier.fillMaxWidth(), + text = if (state.swapButton.isInProgress) { + stringResourceSafe(id = R.string.swapping_swap_action_in_progress) + } else { + stringResourceSafe(id = R.string.swapping_swap_action) + }, + iconResId = state.swapButton.walletInteractionIcon, + enabled = state.swapButton.isEnabled, + onClick = state.swapButton.onClick, + ) + } } } diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt index 98ad517f01..373d309f0f 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt @@ -38,7 +38,7 @@ import coil.request.ImageRequest import com.tangem.common.ui.account.AccountNameUM import com.tangem.common.ui.account.AccountTitle import com.tangem.common.ui.account.AccountTitleUM -import com.tangem.common.ui.account.toUM +import com.tangem.common.ui.account.CryptoPortfolioIconConverter import com.tangem.core.ui.R import com.tangem.core.ui.components.* import com.tangem.core.ui.extensions.resourceReference @@ -604,7 +604,7 @@ private fun TransactionCardPreviewWithPriceImpact() { accountTitleUM = AccountTitleUM.Account( prefixText = resourceReference(R.string.common_from), name = AccountNameUM.DefaultMain.value, - icon = CryptoPortfolioIcon.ofDefaultCustomAccount().toUM(), + icon = CryptoPortfolioIconConverter.convert(CryptoPortfolioIcon.ofDefaultCustomAccount()), ), ), amountEquivalent = "1 000 000", @@ -626,7 +626,7 @@ private fun TransactionCardPreviewWithoutPriceImpact() { accountTitleUM = AccountTitleUM.Account( prefixText = resourceReference(R.string.common_from), name = AccountNameUM.DefaultMain.value, - icon = CryptoPortfolioIcon.ofDefaultCustomAccount().toUM(), + icon = CryptoPortfolioIconConverter.convert(CryptoPortfolioIcon.ofDefaultCustomAccount()), ), ), amountEquivalent = "1 000 000", diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsViewModel.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsViewModel.kt index 1c351420c7..599f82475f 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsViewModel.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/actions/TesterActionsViewModel.kt @@ -6,14 +6,15 @@ import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import arrow.core.getOrElse +import com.tangem.data.common.account.WalletAccountsSaver import com.tangem.domain.apptheme.ChangeAppThemeModeUseCase import com.tangem.domain.apptheme.GetAppThemeModeUseCase import com.tangem.domain.apptheme.model.AppThemeMode +import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.feedback.repository.FeedbackRepository import com.tangem.feature.tester.presentation.actions.TesterActionsContentState.HideAllCurrenciesUM import com.tangem.feature.tester.presentation.actions.TesterActionsContentState.ToggleAppThemeUM import com.tangem.feature.tester.presentation.navigation.InnerTesterRouter -import com.tangem.lib.crypto.UserWalletManager import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn @@ -24,10 +25,11 @@ import javax.inject.Inject @HiltViewModel internal class TesterActionsViewModel @Inject constructor( - private val userWalletManager: UserWalletManager, private val changeAppThemeModeUseCase: ChangeAppThemeModeUseCase, private val getAppThemeModeUseCase: GetAppThemeModeUseCase, private val feedbackRepository: FeedbackRepository, + private val userWalletsListRepository: UserWalletsListRepository, + private val walletAccountsSaver: WalletAccountsSaver, ) : ViewModel() { var uiState: TesterActionsContentState by mutableStateOf(initialState) @@ -56,7 +58,20 @@ internal class TesterActionsViewModel @Inject constructor( uiState = uiState.copy( hideAllCurrenciesUM = HideAllCurrenciesUM.Progress, ) - userWalletManager.hideAllTokens() + + val userWalletId = userWalletsListRepository.selectedUserWalletSync()?.walletId + + if (userWalletId != null) { + walletAccountsSaver.update(userWalletId = userWalletId) { response -> + response ?: return@update response + + response.copy( + accounts = response.accounts.map { accountDTO -> + accountDTO.copy(tokens = emptyList()) + }, + ) + } + } uiState = uiState.copy( hideAllCurrenciesUM = HideAllCurrenciesUM.Clickable(this@TesterActionsViewModel::hideAllCurrencies), diff --git a/features/token-recieve/api/src/main/kotlin/com/tangem/features/tokenreceive/TokenReceiveFeatureToggle.kt b/features/token-recieve/api/src/main/kotlin/com/tangem/features/tokenreceive/TokenReceiveFeatureToggle.kt deleted file mode 100644 index 62175072dd..0000000000 --- a/features/token-recieve/api/src/main/kotlin/com/tangem/features/tokenreceive/TokenReceiveFeatureToggle.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.tangem.features.tokenreceive - -interface TokenReceiveFeatureToggle { - - val isNewTokenReceiveEnabled: Boolean -} \ No newline at end of file diff --git a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/DefaultTokenReceiveFeatureToggle.kt b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/DefaultTokenReceiveFeatureToggle.kt deleted file mode 100644 index 209317d97b..0000000000 --- a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/DefaultTokenReceiveFeatureToggle.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.tangem.features.tokenreceive - -import com.tangem.core.configtoggle.feature.FeatureTogglesManager - -internal class DefaultTokenReceiveFeatureToggle( - private val featureTogglesManager: FeatureTogglesManager, -) : TokenReceiveFeatureToggle { - - override val isNewTokenReceiveEnabled: Boolean - get() = featureTogglesManager.isFeatureEnabled("NEW_TOKEN_RECEIVE_ENABLED") -} \ No newline at end of file diff --git a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/di/TokenReceiveModule.kt b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/di/TokenReceiveModule.kt index 44cfdf6d8b..1b1f5035e2 100644 --- a/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/di/TokenReceiveModule.kt +++ b/features/token-recieve/impl/src/main/java/com/tangem/features/tokenreceive/di/TokenReceiveModule.kt @@ -1,11 +1,8 @@ package com.tangem.features.tokenreceive.di -import com.tangem.core.configtoggle.feature.FeatureTogglesManager import com.tangem.core.decompose.di.ModelComponent import com.tangem.core.decompose.model.Model -import com.tangem.features.tokenreceive.DefaultTokenReceiveFeatureToggle import com.tangem.features.tokenreceive.TokenReceiveComponent -import com.tangem.features.tokenreceive.TokenReceiveFeatureToggle import com.tangem.features.tokenreceive.component.DefaultTokenReceiveComponent import com.tangem.features.tokenreceive.model.TokenReceiveAssetsModel import com.tangem.features.tokenreceive.model.TokenReceiveModel @@ -13,26 +10,12 @@ import com.tangem.features.tokenreceive.model.TokenReceiveQrCodeModel import com.tangem.features.tokenreceive.model.TokenReceiveWarningModel import dagger.Binds import dagger.Module -import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap import javax.inject.Singleton -@Module -@InstallIn(SingletonComponent::class) -internal object FeatureToggleModule { - - @Provides - @Singleton - fun provideTokenReceiveFeatureToggle(featureTogglesManager: FeatureTogglesManager): TokenReceiveFeatureToggle { - return DefaultTokenReceiveFeatureToggle( - featureTogglesManager = featureTogglesManager, - ) - } -} - @Module @InstallIn(SingletonComponent::class) internal interface ComponentModule { diff --git a/features/tokendetails/impl/build.gradle.kts b/features/tokendetails/impl/build.gradle.kts index 05ec40ad40..10cb869ad1 100644 --- a/features/tokendetails/impl/build.gradle.kts +++ b/features/tokendetails/impl/build.gradle.kts @@ -48,6 +48,7 @@ dependencies { /** Core modules */ implementation(projects.common.routing) implementation(projects.core.navigation) + implementation(projects.core.res) implementation(projects.core.ui) implementation(projects.core.utils) implementation(projects.core.analytics) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/CloreMigrationModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/CloreMigrationModel.kt new file mode 100644 index 0000000000..51e1125b6b --- /dev/null +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/CloreMigrationModel.kt @@ -0,0 +1,113 @@ +package com.tangem.feature.tokendetails.presentation.tokendetails.model + +import com.tangem.core.decompose.ui.UiMessageSender +import com.tangem.core.ui.clipboard.ClipboardManager +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.message.SnackbarMessage +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.transaction.error.SignCloreMessageError +import com.tangem.domain.transaction.usecase.SignCloreMessageUseCase +import com.tangem.feature.tokendetails.presentation.router.InnerTokenDetailsRouter +import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState +import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.TokenDetailsStateFactory +import com.tangem.features.tokendetails.impl.R +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.launch + +// TODO: Remove after Clore migration ends ([REDACTED_TASK_KEY]) +@Suppress("LongParameterList") +internal class CloreMigrationModel( + private val stateFactory: TokenDetailsStateFactory, + private val signCloreMessageUseCase: SignCloreMessageUseCase, + private val clipboardManager: ClipboardManager, + private val uiMessageSender: UiMessageSender, + private val router: InnerTokenDetailsRouter, + private val userWallet: UserWallet, + private val cryptoCurrency: CryptoCurrency, + private val coroutineScope: CoroutineScope, + private val dispatchers: CoroutineDispatcherProvider, + private val onStateUpdate: (TokenDetailsState) -> Unit, +) { + + private val state = MutableStateFlow(CloreMigrationState()) + + fun onCloreMigrationClick() { + state.value = CloreMigrationState() + updateBottomSheet() + } + + fun onCloreSignMessage(message: String) { + if (message.isBlank()) return + + state.value = state.value.copy(message = message) + updateBottomSheet(isSigningInProgress = true) + + coroutineScope.launch(dispatchers.main) { + signCloreMessageUseCase( + userWallet = userWallet, + currency = cryptoCurrency, + message = message, + ).fold( + ifLeft = { error -> + val errorMessage = when (error) { + is SignCloreMessageError.SigningFailed -> stringReference(error.message) + SignCloreMessageError.MessageSigningNotSupported -> + resourceReference(R.string.warning_clore_migration_error_signing_not_supported) + SignCloreMessageError.WalletManagerNotFound -> + resourceReference(R.string.warning_clore_migration_error_wallet_manager_not_found) + } + state.value = state.value.copy(signature = "") + updateBottomSheet() + uiMessageSender.send(SnackbarMessage(errorMessage)) + }, + ifRight = { signature -> + state.value = state.value.copy(signature = signature) + onStateUpdate(stateFactory.getStateWithUpdatedCloreMigrationSignature(signature)) + }, + ) + } + } + + fun onOpenCloreClaimPortal() { + router.openUrl(CLAIM_PORTAL_URL) + } + + private fun updateBottomSheet(isSigningInProgress: Boolean = false) { + val currentState = state.value + onStateUpdate( + stateFactory.getStateWithCloreMigrationBottomSheet( + message = currentState.message, + signature = currentState.signature, + isSigningInProgress = isSigningInProgress, + onMessageChange = { newMessage -> + state.value = state.value.copy(message = newMessage) + updateBottomSheet() + }, + onSignClick = { onCloreSignMessage(state.value.message) }, + onCopyClick = { + val signature = state.value.signature + if (signature.isNotBlank()) { + clipboardManager.setText(text = signature, isSensitive = false) + uiMessageSender.send( + SnackbarMessage(resourceReference(R.string.wallet_notification_address_copied)), + ) + } + }, + onOpenPortalClick = { onOpenCloreClaimPortal() }, + ), + ) + } + + private companion object { + const val CLAIM_PORTAL_URL = "https://claim-portal.clore.ai/" + } +} + +private data class CloreMigrationState( + val message: String = "", + val signature: String = "", +) \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsClickIntents.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsClickIntents.kt index bf41cabfe1..bc6d1d4b7e 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsClickIntents.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsClickIntents.kt @@ -64,7 +64,26 @@ interface TokenDetailsClickIntents { fun onBalanceSelect(config: TokenBalanceSegmentedButtonConfig) + fun onGoToRefundedTokenClick(cryptoCurrency: CryptoCurrency) + + fun onOpenUrlClick(url: String) + + fun onConfirmDisposeExpressStatus() + + fun onDisposeExpressStatus() + fun onYieldInfoClick() + + // region Clore migration + // TODO: Remove after 2025-04-01 when Clore migration ends ([REDACTED_TASK_KEY]) + + fun onCloreMigrationClick() + + fun onCloreSignMessage(message: String) + + fun onOpenCloreClaimPortal() + + // endregion Clore migration } interface ExpressTransactionsClickIntents { @@ -146,4 +165,23 @@ internal class EmptyTokenDetailsClickIntents : TokenDetailsClickIntents { /* no op */ return null } + + override fun onGoToRefundedTokenClick(cryptoCurrency: CryptoCurrency) { /* no op */ } + + override fun onOpenUrlClick(url: String) { /* no op */ } + + override fun onConfirmDisposeExpressStatus() { /* no op */ } + + override fun onDisposeExpressStatus() { /* no op */ } + + // region Clore migration + // TODO: Remove after 2025-04-01 when Clore migration ends ([REDACTED_TASK_KEY]) + + override fun onCloreMigrationClick() { /* no op */ } + + override fun onCloreSignMessage(message: String) { /* no op */ } + + override fun onOpenCloreClaimPortal() { /* no op */ } + + // endregion Clore migration } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt index 6a929eb75f..a137a93bf5 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/model/TokenDetailsModel.kt @@ -23,7 +23,6 @@ import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.ui.UiMessageSender -import com.tangem.core.navigation.share.ShareManager import com.tangem.core.ui.clipboard.ClipboardManager import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference @@ -59,7 +58,10 @@ import com.tangem.domain.tokens.* import com.tangem.domain.tokens.legacy.TradeCryptoAction import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason import com.tangem.domain.tokens.model.TokenActionsState -import com.tangem.domain.tokens.model.analytics.* +import com.tangem.domain.tokens.model.analytics.PromoAnalyticsEvent +import com.tangem.domain.tokens.model.analytics.TokenReceiveCopyActionSource +import com.tangem.domain.tokens.model.analytics.TokenReceiveNewAnalyticsEvent +import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent.Companion.toReasonAnalyticsText import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent.DetailsScreenOpened.TokenBalance import com.tangem.domain.tokens.model.details.NavigationAction @@ -87,7 +89,6 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.T import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.express.ExpressStatusFactory import com.tangem.features.tokendetails.TokenDetailsComponent import com.tangem.features.tokendetails.impl.R -import com.tangem.features.tokenreceive.TokenReceiveFeatureToggle import com.tangem.features.txhistory.entity.TxHistoryContentUpdateEmitter import com.tangem.features.yield.supply.api.YieldSupplyDepositedWarningComponent import com.tangem.features.yield.supply.api.YieldSupplyFeatureToggles @@ -133,7 +134,6 @@ internal class TokenDetailsModel @Inject constructor( private val analyticsEventsHandler: AnalyticsEventHandler, private val vibratorHapticManager: VibratorHapticManager, private val clipboardManager: ClipboardManager, - private val shareManager: ShareManager, @GlobalUiMessageSender private val uiMessageSender: UiMessageSender, private val txHistoryContentUpdateEmitter: TxHistoryContentUpdateEmitter, paramsContainer: ParamsContainer, @@ -143,7 +143,6 @@ internal class TokenDetailsModel @Inject constructor( private val router: InnerTokenDetailsRouter, private val tokenDetailsDeepLinkActionListener: TokenDetailsDeepLinkActionListener, private val analyticsExceptionHandler: AnalyticsExceptionHandler, - private val tokenReceiveFeatureToggle: TokenReceiveFeatureToggle, private val receiveAddressesFactory: ReceiveAddressesFactory, private val yieldSupplyFeatureToggles: YieldSupplyFeatureToggles, private val saveViewedYieldSupplyWarningUseCase: SaveViewedYieldSupplyWarningUseCase, @@ -153,6 +152,7 @@ internal class TokenDetailsModel @Inject constructor( private val getAccountCryptoCurrencyStatusUseCase: GetAccountCurrencyStatusUseCase, private val manageCryptoCurrenciesUseCase: ManageCryptoCurrenciesUseCase, private val yieldSupplyGetRewardsBalanceUseCase: YieldSupplyGetRewardsBalanceUseCase, + private val signCloreMessageUseCase: SignCloreMessageUseCase, ) : Model(), TokenDetailsClickIntents, ExpressTransactionsClickIntents, @@ -162,7 +162,8 @@ internal class TokenDetailsModel @Inject constructor( private val userWalletId: UserWalletId = params.userWalletId private val cryptoCurrency: CryptoCurrency = params.currency - private val userWallet: UserWallet = getUserWalletUseCase(userWalletId).getOrNull() ?: error("UserWallet not found") + private val userWallet: UserWallet = getUserWalletUseCase(userWalletId).getOrNull() + ?: error("UserWallet not found") private val marketPriceJobHolder = JobHolder() private val refreshStateJobHolder = JobHolder() @@ -195,6 +196,27 @@ internal class TokenDetailsModel @Inject constructor( yieldSupplyFeatureToggles = yieldSupplyFeatureToggles, ) + private val internalUiState = MutableStateFlow(stateFactory.getInitialState(cryptoCurrency)) + val uiState: StateFlow = internalUiState + + // region Clore migration + // TODO: Remove after Clore migration ends ([REDACTED_TASK_KEY]) + private val cloreMigrationModel by lazy(mode = LazyThreadSafetyMode.NONE) { + CloreMigrationModel( + stateFactory = stateFactory, + signCloreMessageUseCase = signCloreMessageUseCase, + clipboardManager = clipboardManager, + uiMessageSender = uiMessageSender, + router = router, + userWallet = userWallet, + cryptoCurrency = cryptoCurrency, + coroutineScope = modelScope, + dispatchers = dispatchers, + onStateUpdate = { internalUiState.value = it }, + ) + } + // endregion + private val expressStatusFactory by lazy(mode = LazyThreadSafetyMode.NONE) { expressStatusFactory.create( clickIntents = this, @@ -217,9 +239,6 @@ internal class TokenDetailsModel @Inject constructor( TokenDetailsCurrencyStatusAnalyticsSender(analyticsEventsHandler) } - private val internalUiState = MutableStateFlow(stateFactory.getInitialState(cryptoCurrency)) - val uiState: StateFlow = internalUiState - init { updateTopBarMenu() initButtons() @@ -668,9 +687,8 @@ internal class TokenDetailsModel @Inject constructor( vibratorHapticManager.performOneTime(TangemHapticEffect.OneTime.Click) clipboardManager.setText(text = extendedKey, isSensitive = true) - uiMessageSender.send( - message = SnackbarMessage(message = resourceReference(R.string.wallet_notification_address_copied)), - ) + val message = resourceReference(R.string.wallet_notification_address_copied) + uiMessageSender.send(message = SnackbarMessage(message = message)) } } } @@ -787,7 +805,8 @@ internal class TokenDetailsModel @Inject constructor( modelScope.launch(dispatchers.main) { when (val addresses = currencyStatus.value.networkAddress) { is NetworkAddress.Selectable -> { - internalUiState.value = stateFactory.getStateWithChooseAddressBottomSheet(cryptoCurrency, addresses) + internalUiState.value = + stateFactory.getStateWithChooseAddressBottomSheet(cryptoCurrency, addresses) } is NetworkAddress.Single -> { router.openUrl( @@ -1250,37 +1269,33 @@ internal class TokenDetailsModel @Inject constructor( } private fun navigateToReceive() { - val networkAddress = cryptoCurrencyStatus?.value?.networkAddress ?: return - if (tokenReceiveFeatureToggle.isNewTokenReceiveEnabled) { - modelScope.launch { - configureReceiveAddresses(cryptoCurrencyStatus = cryptoCurrencyStatus) - ?.let { bottomSheetNavigation.activate(it) } - } - } else { - analyticsEventsHandler.send(TokenReceiveAnalyticsEvent.ReceiveScreenOpened(cryptoCurrency.symbol)) - internalUiState.value = stateFactory.getStateWithReceiveBottomSheet( - currency = cryptoCurrency, - networkAddress = networkAddress, - onCopyClick = { - analyticsEventsHandler.send(TokenReceiveAnalyticsEvent.ButtonCopyAddress(cryptoCurrency.symbol)) - clipboardManager.setText(text = it, isSensitive = true) - }, - onShareClick = { - analyticsEventsHandler.send( - TokenReceiveAnalyticsEvent.ButtonShareAddress(cryptoCurrency.symbol), - ) - shareManager.shareText(text = it) - }, - ) + modelScope.launch { + configureReceiveAddresses(cryptoCurrencyStatus = cryptoCurrencyStatus) + ?.let { bottomSheetNavigation.activate(it) } } } private fun handleNavigationParam() { - if (params.navigationAction is NavigationAction.Staking) { - openStaking() + when (params.navigationAction) { + is NavigationAction.Staking -> openStaking() + is NavigationAction.CloreMigration -> onCloreMigrationClick() + is NavigationAction.YieldSupply, + null, + -> Unit } } + // region Clore migration + // TODO: Remove after Clore migration ends ([REDACTED_TASK_KEY]) + + override fun onCloreMigrationClick() = cloreMigrationModel.onCloreMigrationClick() + + override fun onCloreSignMessage(message: String) = cloreMigrationModel.onCloreSignMessage(message) + + override fun onOpenCloreClaimPortal() = cloreMigrationModel.onOpenCloreClaimPortal() + + // endregion Clore migration + private companion object { const val EXPRESS_STATUS_UPDATE_DELAY = 10_000L } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt index 5733db0e18..feb6279306 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt @@ -258,9 +258,15 @@ internal sealed class TokenDetailsNotification(val config: NotificationConfig) { subtitle = resourceReference(id = R.string.warning_matic_migration_message), ) - data object MigrationClore : Warning( + data class MigrationClore( + private val onMigrationClick: () -> Unit, + ) : Warning( title = resourceReference(id = R.string.warning_clore_migration_title), - subtitle = resourceReference(id = R.string.warning_clore_migration_message), + subtitle = resourceReference(id = R.string.warning_clore_migration_description), + buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( + text = resourceReference(id = R.string.warning_clore_migration_button), + onClick = onMigrationClick, + ), ) data object UsedOutdatedData : TokenDetailsNotification( diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt index eb54aca878..38588b2f69 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt @@ -155,7 +155,9 @@ internal class TokenDetailsNotificationConverter( }, ) is CryptoCurrencyWarning.MigrationMaticToPol -> MigrationMaticToPol - is CryptoCurrencyWarning.MigrationClore -> MigrationClore + is CryptoCurrencyWarning.MigrationClore -> MigrationClore( + onMigrationClick = clickIntents::onCloreMigrationClick, + ) is CryptoCurrencyWarning.UsedOutdatedDataWarning -> UsedOutdatedData } } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt index 87552d058e..323b323e72 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt @@ -2,9 +2,9 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory import arrow.core.Either import com.tangem.common.ui.bottomsheet.chooseaddress.ChooseAddressBottomSheetConfig -import com.tangem.common.ui.bottomsheet.receive.TokenReceiveBottomSheetConfig import com.tangem.common.ui.tokendetails.TokenDetailsDialogConfig import com.tangem.common.ui.tokens.getUnavailabilityReasonText +import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.clore.CloreMigrationBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.dropdownmenu.TangemDropdownMenuItem import com.tangem.core.ui.extensions.TextReference @@ -15,7 +15,6 @@ import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.card.common.util.cardTypesResolver import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus -import com.tangem.domain.models.network.Network import com.tangem.domain.models.network.NetworkAddress import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId @@ -210,31 +209,6 @@ internal class TokenDetailsStateFactory( return refreshStateConverter.convert(false) } - fun getStateWithReceiveBottomSheet( - currency: CryptoCurrency, - networkAddress: NetworkAddress, - onCopyClick: (String) -> Unit, - onShareClick: (String) -> Unit, - ): TokenDetailsState { - return currentStateProvider().copy( - bottomSheetConfig = TangemBottomSheetConfig( - isShown = true, - onDismissRequest = expressTransactionsClickIntents::onDismissBottomSheet, - content = TokenReceiveBottomSheetConfig( - asset = TokenReceiveBottomSheetConfig.Asset.Currency( - name = currency.name, - symbol = currency.symbol, - ), - network = currency.network, - networkAddress = networkAddress, - showMemoDisclaimer = currency.network.transactionExtrasType != Network.TransactionExtrasType.NONE, - onCopyClick = onCopyClick, - onShareClick = onShareClick, - ), - ), - ) - } - fun getStateWithChooseAddressBottomSheet( currency: CryptoCurrency, networkAddress: NetworkAddress, @@ -244,11 +218,7 @@ internal class TokenDetailsStateFactory( isShown = true, onDismissRequest = expressTransactionsClickIntents::onDismissBottomSheet, content = ChooseAddressBottomSheetConfig( - asset = TokenReceiveBottomSheetConfig.Asset.Currency( - name = currency.name, - symbol = currency.symbol, - ), - network = currency.network, + currency = currency, networkAddress = networkAddress, onClick = tokenDetailsClickIntents::onAddressTypeSelected, ), @@ -380,4 +350,62 @@ internal class TokenDetailsStateFactory( }.toImmutableList(), ) } + + // region Clore migration + // TODO: Remove after Clore migration ends ([REDACTED_TASK_KEY]) + + fun getStateWithCloreMigrationBottomSheet( + message: String, + signature: String, + isSigningInProgress: Boolean, + onMessageChange: (String) -> Unit, + onSignClick: () -> Unit, + onCopyClick: () -> Unit, + onOpenPortalClick: () -> Unit, + ): TokenDetailsState { + return currentStateProvider().copy( + bottomSheetConfig = TangemBottomSheetConfig( + isShown = true, + onDismissRequest = expressTransactionsClickIntents::onDismissBottomSheet, + content = CloreMigrationBottomSheetConfig( + message = message, + signature = signature, + isSigningInProgress = isSigningInProgress, + onMessageChange = onMessageChange, + onSignClick = onSignClick, + onCopyClick = onCopyClick, + onOpenPortalClick = onOpenPortalClick, + ), + ), + ) + } + + fun getStateWithUpdatedCloreMigrationSignature(signature: String): TokenDetailsState { + val state = currentStateProvider() + val bottomSheetConfig = state.bottomSheetConfig ?: return state + val content = bottomSheetConfig.content as? CloreMigrationBottomSheetConfig ?: return state + + return state.copy( + bottomSheetConfig = bottomSheetConfig.copy( + content = content.copy( + signature = signature, + isSigningInProgress = false, + ), + ), + ) + } + + fun getStateWithCloreMigrationSigning(): TokenDetailsState { + val state = currentStateProvider() + val bottomSheetConfig = state.bottomSheetConfig ?: return state + val content = bottomSheetConfig.content as? CloreMigrationBottomSheetConfig ?: return state + + return state.copy( + bottomSheetConfig = bottomSheetConfig.copy( + content = content.copy(isSigningInProgress = true), + ), + ) + } + + // endregion Clore migration } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt index 2e2ea39238..abf30bd8f0 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt @@ -16,8 +16,6 @@ import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameter import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.tangem.common.ui.bottomsheet.chooseaddress.ChooseAddressBottomSheet import com.tangem.common.ui.bottomsheet.chooseaddress.ChooseAddressBottomSheetConfig -import com.tangem.common.ui.bottomsheet.receive.TokenReceiveBottomSheet -import com.tangem.common.ui.bottomsheet.receive.TokenReceiveBottomSheetConfig import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig import com.tangem.common.ui.expressStatus.expressTransactionsItems import com.tangem.core.ui.components.containers.pullToRefresh.TangemPullToRefreshContainer @@ -35,6 +33,8 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.T import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenDetailsDialogs import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenDetailsTopAppBar import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenInfoBlock +import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.clore.CloreMigrationBottomSheet +import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.clore.CloreMigrationBottomSheetConfig import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.ExpressStatusBottomSheet import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.staking.TokenStakingBlock import com.tangem.features.markets.token.block.TokenMarketBlockComponent @@ -169,15 +169,15 @@ internal fun TokenDetailsScreen( state.bottomSheetConfig?.let { config -> when (config.content) { - is TokenReceiveBottomSheetConfig -> { - TokenReceiveBottomSheet(config = config) - } is ChooseAddressBottomSheetConfig -> { ChooseAddressBottomSheet(config = config) } is ExpressStatusBottomSheetConfig -> { ExpressStatusBottomSheet(config = config) } + is CloreMigrationBottomSheetConfig -> { + CloreMigrationBottomSheet(config = config) + } } } } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/clore/CloreMigrationBottomSheet.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/clore/CloreMigrationBottomSheet.kt new file mode 100644 index 0000000000..9875449e80 --- /dev/null +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/clore/CloreMigrationBottomSheet.kt @@ -0,0 +1,220 @@ +package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.clore + +import android.content.res.Configuration +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.* +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.R as CoreR +import com.tangem.core.ui.components.PrimaryButton +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet +import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle +import com.tangem.core.ui.components.buttons.SecondarySmallButton +import com.tangem.core.ui.components.buttons.SmallButtonConfig +import com.tangem.core.ui.components.fields.SimpleTextField +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.stringResourceSafe +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.features.tokendetails.impl.R + +@Composable +internal fun CloreMigrationBottomSheet(config: TangemBottomSheetConfig) { + TangemModalBottomSheet( + config = config, + title = { + TangemModalBottomSheetTitle( + endIconRes = CoreR.drawable.ic_close_24, + onEndClick = config.onDismissRequest, + ) + }, + ) { content: CloreMigrationBottomSheetConfig -> + CloreMigrationBottomSheetContent(content = content) + } +} + +@Composable +private fun CloreMigrationBottomSheetContent(content: CloreMigrationBottomSheetConfig) { + Column( + modifier = Modifier + .padding(bottom = 16.dp) + .padding(horizontal = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(space = 16.dp), + ) { + Text( + text = stringResourceSafe(R.string.warning_clore_migration_sheet_title), + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + style = TangemTheme.typography.h3, + ) + + Text( + text = stringResourceSafe(R.string.warning_clore_migration_sheet_description), + color = TangemTheme.colors.text.secondary, + textAlign = TextAlign.Center, + style = TangemTheme.typography.body2, + modifier = Modifier.padding(horizontal = 8.dp), + ) + + Spacer(modifier = Modifier.height(8.dp)) + + MessageField( + placeholder = stringResourceSafe(R.string.warning_clore_migration_message_label), + value = content.message, + onValueChange = content.onMessageChange, + buttonText = stringResourceSafe(R.string.warning_clore_migration_sign_button), + onButtonClick = content.onSignClick, + isButtonEnabled = content.message.isNotBlank() && !content.isSigningInProgress, + isLoading = content.isSigningInProgress, + ) + + SignatureField( + placeholder = stringResourceSafe(R.string.warning_clore_migration_signature_label), + value = content.signature, + onCopyClick = content.onCopyClick, + ) + + Spacer(modifier = Modifier.height(8.dp)) + + PrimaryButton( + text = stringResourceSafe(R.string.warning_clore_migration_open_portal_button), + onClick = content.onOpenPortalClick, + modifier = Modifier.fillMaxWidth(), + ) + } +} + +@Suppress("LongParameterList") +@Composable +private fun MessageField( + placeholder: String, + value: String, + onValueChange: (String) -> Unit, + buttonText: String, + onButtonClick: () -> Unit, + isButtonEnabled: Boolean, + isLoading: Boolean, +) { + Row( + modifier = Modifier + .fillMaxWidth() + .border( + width = 1.dp, + color = TangemTheme.colors.stroke.primary, + shape = TangemTheme.shapes.roundedCornersSmall, + ) + .heightIn(min = 56.dp) + .padding(12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + SimpleTextField( + value = value, + onValueChange = onValueChange, + placeholder = TextReference.Str(placeholder), + modifier = Modifier.weight(1f), + singleLine = false, + ) + + Spacer(modifier = Modifier.width(8.dp)) + + SecondarySmallButton( + config = SmallButtonConfig( + text = TextReference.Str(buttonText), + onClick = onButtonClick, + isEnabled = isButtonEnabled, + isLoading = isLoading, + ), + ) + } +} + +@Suppress("FunctionSignature") +@Composable +private fun SignatureField(placeholder: String, value: String, onCopyClick: () -> Unit) { + Row( + modifier = Modifier + .fillMaxWidth() + .border( + width = 1.dp, + color = TangemTheme.colors.stroke.primary, + shape = TangemTheme.shapes.roundedCornersSmall, + ) + .heightIn(min = 56.dp) + .padding(12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + SimpleTextField( + value = value, + onValueChange = {}, + placeholder = TextReference.Str(placeholder), + modifier = Modifier.weight(1f), + readOnly = true, + singleLine = false, + ) + + Spacer(modifier = Modifier.width(8.dp)) + + SecondarySmallButton( + config = SmallButtonConfig( + text = TextReference.Res(R.string.warning_clore_migration_copy_button), + onClick = onCopyClick, + isEnabled = value.isNotBlank(), + ), + ) + } +} + +// region Preview +@Composable +@Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) +private fun Preview_CloreMigrationBottomSheet() { + TangemThemePreview { + val config = TangemBottomSheetConfig( + isShown = true, + content = CloreMigrationBottomSheetConfig( + message = "Claim request for CLORE tokens to Ethereum address " + + "0x742d35Cc6634C0532925a3b844Bc9e7595f60126 from CEsMERNgBVPo9Dkc99pRDMPD3mxwPMxHZi", + signature = "H4sIAAAAAAAEAGNgGAWjYBSMglEwCkYBHQEA", + isSigningInProgress = false, + onMessageChange = {}, + onSignClick = {}, + onCopyClick = {}, + onOpenPortalClick = {}, + ), + onDismissRequest = {}, + ) + + CloreMigrationBottomSheet(config) + } +} + +@Composable +@Preview(showBackground = true, widthDp = 360) +private fun Preview_CloreMigrationBottomSheet_Empty() { + TangemThemePreview { + val config = TangemBottomSheetConfig( + isShown = true, + content = CloreMigrationBottomSheetConfig( + message = "", + signature = "", + isSigningInProgress = false, + onMessageChange = {}, + onSignClick = {}, + onCopyClick = {}, + onOpenPortalClick = {}, + ), + onDismissRequest = {}, + ) + + CloreMigrationBottomSheet(config) + } +} +// endregion Preview \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/clore/CloreMigrationBottomSheetConfig.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/clore/CloreMigrationBottomSheetConfig.kt new file mode 100644 index 0000000000..9adf70e4af --- /dev/null +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/clore/CloreMigrationBottomSheetConfig.kt @@ -0,0 +1,15 @@ +package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.clore + +import androidx.compose.runtime.Immutable +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent + +@Immutable +data class CloreMigrationBottomSheetConfig( + val message: String = "", + val signature: String = "", + val isSigningInProgress: Boolean = false, + val onMessageChange: (String) -> Unit, + val onSignClick: () -> Unit, + val onCopyClick: () -> Unit, + val onOpenPortalClick: () -> Unit, +) : TangemBottomSheetConfigContent \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt index b9373e40e8..58d1a6866a 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt @@ -199,6 +199,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor( cryptoCurrencyStatus = currencyStatus, apy = apy, ) + is NavigationAction.CloreMigration -> Unit } } @@ -273,6 +274,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor( }, ) } + is NavigationAction.CloreMigration -> return } analyticsEventHandler.send(event) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletCurrencyActionsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletCurrencyActionsClickIntents.kt index 9cc74286c9..78956da6ab 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletCurrencyActionsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletCurrencyActionsClickIntents.kt @@ -5,7 +5,6 @@ import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRouter import com.tangem.common.ui.bottomsheet.chooseaddress.ChooseAddressBottomSheetConfig import com.tangem.common.ui.bottomsheet.receive.AddressModel -import com.tangem.common.ui.bottomsheet.receive.TokenReceiveBottomSheetConfig import com.tangem.common.ui.bottomsheet.receive.mapToAddressModels import com.tangem.common.ui.tokens.getUnavailabilityReasonText import com.tangem.core.analytics.api.AnalyticsEventHandler @@ -13,9 +12,7 @@ import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent import com.tangem.core.decompose.di.ModelScoped -import com.tangem.core.navigation.share.ShareManager import com.tangem.core.ui.clipboard.ClipboardManager -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.WrappedList @@ -35,7 +32,6 @@ import com.tangem.domain.markets.TokenMarketParams import com.tangem.domain.models.TokenReceiveConfig import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus -import com.tangem.domain.models.network.Network import com.tangem.domain.models.network.NetworkAddress import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId @@ -47,7 +43,6 @@ import com.tangem.domain.staking.model.StakingOption import com.tangem.domain.tokens.* import com.tangem.domain.tokens.legacy.TradeCryptoAction import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason -import com.tangem.domain.tokens.model.analytics.TokenReceiveAnalyticsEvent import com.tangem.domain.tokens.model.analytics.TokenReceiveCopyActionSource import com.tangem.domain.tokens.model.analytics.TokenReceiveNewAnalyticsEvent import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent @@ -67,7 +62,6 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState import com.tangem.feature.wallet.presentation.wallet.state.transformers.CloseBottomSheetTransformer import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender -import com.tangem.features.tokenreceive.TokenReceiveFeatureToggle import com.tangem.features.yield.supply.api.YieldSupplyFeatureToggles import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.collections.immutable.toImmutableList @@ -144,10 +138,8 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( private val reduxStateHolder: ReduxStateHolder, private val vibratorHapticManager: VibratorHapticManager, private val clipboardManager: ClipboardManager, - private val shareManager: ShareManager, private val appRouter: AppRouter, private val rampStateManager: RampStateManager, - private val tokenReceiveFeatureToggle: TokenReceiveFeatureToggle, private val saveViewedTokenReceiveWarningUseCase: SaveViewedTokenReceiveWarningUseCase, private val receiveAddressesFactory: ReceiveAddressesFactory, private val yieldSupplyFeatureToggles: YieldSupplyFeatureToggles, @@ -249,29 +241,6 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( return resourceReference(R.string.wallet_notification_address_copied) } - private fun createReceiveBottomSheetContent( - currency: CryptoCurrency, - addresses: NetworkAddress, - ): TangemBottomSheetConfigContent { - return TokenReceiveBottomSheetConfig( - asset = TokenReceiveBottomSheetConfig.Asset.Currency( - name = currency.name, - symbol = currency.symbol, - ), - network = currency.network, - networkAddress = addresses, - showMemoDisclaimer = currency.network.transactionExtrasType != Network.TransactionExtrasType.NONE, - onCopyClick = { - analyticsEventHandler.send(TokenReceiveAnalyticsEvent.ButtonCopyAddress(currency.symbol)) - clipboardManager.setText(text = it, isSensitive = true) - }, - onShareClick = { - analyticsEventHandler.send(TokenReceiveAnalyticsEvent.ButtonShareAddress(currency.symbol)) - shareManager.shareText(text = it) - }, - ) - } - override fun onCopyAddressClick(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus) { analyticsEventHandler.send( event = TokenReceiveNewAnalyticsEvent.ButtonCopyAddress( @@ -596,11 +565,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( ) { stateHolder.showBottomSheet( ChooseAddressBottomSheetConfig( - asset = TokenReceiveBottomSheetConfig.Asset.Currency( - name = currency.name, - symbol = currency.symbol, - ), - network = currency.network, + currency = currency, networkAddress = addresses, onClick = { onAddressTypeSelected( @@ -768,25 +733,11 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( } private fun navigateToReceive(cryptoCurrencyStatus: CryptoCurrencyStatus) { - val userWalletId = stateHolder.getSelectedWalletId() - if (tokenReceiveFeatureToggle.isNewTokenReceiveEnabled) { - stateHolder.hideBottomSheet() - modelScope.launch { - configureReceiveAddresses(cryptoCurrencyStatus = cryptoCurrencyStatus)?.let { - router.openTokenReceiveBottomSheet(it) - } + stateHolder.hideBottomSheet() + modelScope.launch { + configureReceiveAddresses(cryptoCurrencyStatus = cryptoCurrencyStatus)?.let { + router.openTokenReceiveBottomSheet(it) } - } else { - analyticsEventHandler.send( - event = TokenReceiveAnalyticsEvent.ReceiveScreenOpened(cryptoCurrencyStatus.currency.symbol), - ) - stateHolder.showBottomSheet( - createReceiveBottomSheetContent( - currency = cryptoCurrencyStatus.currency, - addresses = cryptoCurrencyStatus.value.networkAddress ?: return, - ), - userWalletId, - ) } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt index ed19a09083..55dd5e9a5c 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt @@ -18,6 +18,7 @@ import com.tangem.domain.feedback.GetWalletMetaInfoUseCase import com.tangem.domain.feedback.SendFeedbackEmailUseCase import com.tangem.domain.feedback.models.FeedbackEmailType import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.requireColdWallet @@ -35,6 +36,7 @@ import com.tangem.domain.staking.multi.MultiStakingBalanceFetcher import com.tangem.domain.tokens.model.analytics.PromoAnalyticsEvent import com.tangem.domain.tokens.model.analytics.PromoAnalyticsEvent.Program import com.tangem.domain.tokens.model.analytics.PromoAnalyticsEvent.PromotionBannerClicked +import com.tangem.domain.tokens.model.details.NavigationAction import com.tangem.domain.wallets.legacy.UserWalletsListManager.Lockable.UnlockType import com.tangem.domain.wallets.models.UnlockWalletsError import com.tangem.domain.wallets.usecase.* @@ -65,6 +67,8 @@ internal interface WalletWarningsClickIntents { fun onAddBackupCardClick() + fun onCloreMigrationClick(cryptoCurrencyStatus: CryptoCurrencyStatus) + fun onCloseAlreadySignedHashesWarningClick() fun onGenerateMissedAddressesClick(missedAddressCurrencies: List) @@ -104,6 +108,8 @@ internal interface WalletWarningsClickIntents { fun onDenyPermissions() fun onFinishWalletActivationClick(isBackupExists: Boolean) + + fun onYieldPromoTermsAndConditionsClick() } @Suppress("LargeClass", "LongParameterList", "TooManyFunctions") @@ -146,6 +152,16 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( prepareAndStartOnboardingProcess() } + override fun onCloreMigrationClick(cryptoCurrencyStatus: CryptoCurrencyStatus) { + val userWallet = getSelectedUserWallet() ?: return + + router.openTokenDetails( + userWalletId = userWallet.walletId, + currencyStatus = cryptoCurrencyStatus, + navigationAction = NavigationAction.CloreMigration, + ) + } + private fun prepareAndStartOnboardingProcess() { modelScope.launch(dispatchers.main) { getSelectedUserWallet()?.requireColdWallet()?.let { @@ -323,6 +339,11 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( program = Program.OnePlusOne, action = PromotionBannerClicked.BannerAction.Closed(), ) + PromoId.YieldPromo -> PromotionBannerClicked( + source = AnalyticsParam.ScreensSources.Main, + program = Program.YieldPromo, + action = PromotionBannerClicked.BannerAction.Closed(), + ) }, ) @@ -380,6 +401,7 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( ) urlOpener.openUrl(ONE_PLUS_ONE_PROMO_LINK) } + PromoId.YieldPromo -> Unit // banner is not clickable, only terms and conditions button } } @@ -597,6 +619,17 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( } } + override fun onYieldPromoTermsAndConditionsClick() { + analyticsEventHandler.send( + PromotionBannerClicked( + source = AnalyticsParam.ScreensSources.Main, + program = Program.YieldPromo, + action = PromotionBannerClicked.BannerAction.Clicked(), + ), + ) + urlOpener.openUrl(YIELD_PROMO_TERMS_LINK) + } + private companion object { const val VISA_PROMO_LINK = "https://tangem.com/en/cardwaitlist/?utm_source=tangem-app-banner" + "&utm_medium=banner" + @@ -611,5 +644,6 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( "&utm_source=tangem-app-banner" + "&utm_medium=banner" + "&utm_campaign=BOGO50" + const val YIELD_PROMO_TERMS_LINK = "https://tangem.com/docs/yield-mode-toc.html" } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/deeplink/DefaultPromoDeeplinkHandler.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/deeplink/DefaultPromoDeeplinkHandler.kt index af455b27ac..0cb63b07c9 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/deeplink/DefaultPromoDeeplinkHandler.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/deeplink/DefaultPromoDeeplinkHandler.kt @@ -59,14 +59,15 @@ internal class DefaultPromoDeeplinkHandler @AssistedInject constructor( init { analyticsEventsHandler.send(PromoActivationAnalytics.PromoDeepLinkActivationStart()) + + saveAndBindRefcode() + val promoCode = queryParams[PROMO_CODE_KEY].orEmpty() if (promoCode.isEmpty()) { showAlert(InvalidPromoCode) } else { findSelectedWallet(promoCode) } - - saveAndBindRefcode() } private fun findSelectedWallet(promoCode: String) { @@ -183,14 +184,19 @@ internal class DefaultPromoDeeplinkHandler @AssistedInject constructor( message = message, dismissOnFirstAction = true, firstActionBuilder = { - okAction { } + okAction { + uiMessageSender.send(GlobalLoadingMessage(false)) + } }, ), ) } + @Suppress("NullableToStringCall") private fun saveAndBindRefcode() { scope.launch(dispatchers.default) { + Timber.i("saveAndBindRefcode: refcode = $refcode, campaign = $campaign") + if (!refcode.isNullOrBlank()) { val conversionData = AppsFlyerConversionData(refcode = refcode, campaign = campaign) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt index 1eb6944175..e766bf4333 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt @@ -72,7 +72,7 @@ sealed class WalletScreenAnalyticsEvent { } put("Wallet Type", seedPhrase) }, - ) + ), AppsFlyerIncludedEvent data class NoticeFinishActivation( private val activationState: ActivationState, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt index 94ede58c14..3852ada00e 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt @@ -66,6 +66,10 @@ internal class WalletWarningsAnalyticsSender @Inject constructor( source = AnalyticsParam.ScreensSources.Main, program = Program.OnePlusOne, ) + is WalletNotification.YieldPromo -> NoticePromotionBanner( + source = AnalyticsParam.ScreensSources.Main, + program = Program.YieldPromo, + ) is WalletNotification.ReferralPromo -> MainScreen.ReferralPromo() is WalletNotification.VisaPresalePromo -> VisaWaitlistPromo() is WalletNotification.UnlockWallets -> null // See [SelectedWalletAnalyticsSender] @@ -76,6 +80,7 @@ internal class WalletWarningsAnalyticsSender @Inject constructor( is WalletNotification.UsedOutdatedData, is WalletNotification.UnlockVisaAccess, is WalletNotification.Warning.YeildSupplyApprove, // TODO apply correct event + is WalletNotification.CloreMigration, -> null is WalletNotification.FinishWalletActivation -> { val activationState = if (warning.isBackupExists) { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt index b757338349..fe3861e0c2 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt @@ -33,6 +33,7 @@ import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.account.AccountDependencies import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification import com.tangem.hot.sdk.model.HotWalletId +import com.tangem.lib.crypto.BlockchainUtils import com.tangem.utils.extensions.addIf import com.tangem.utils.extensions.isPositive import com.tangem.utils.extensions.orZero @@ -95,6 +96,8 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( notificationsRepository.getShouldShowNotification(NotificationId.EnablePushesReminderNotification.key) .distinctUntilChanged(), getAccessCodeSkippedUseCase(userWallet.walletId).distinctUntilChanged(), + shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.YieldPromo) + .distinctUntilChanged(), ) { array -> array } .combine(tokenListFlow()) { array, any: Any -> arrayOf(any).plus(elements = array) } .map { array -> @@ -107,6 +110,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( val shouldShowOnePlusOnePromo = array[4] as Boolean val shouldShowEnablePushesReminderNotification = array[5] as Boolean val shouldAccessCodeSkipped = array[6] as Boolean + val shouldShowYieldPromo = array[7] as Boolean buildList { addUsedOutdatedDataNotification(totalFiatBalance) @@ -122,6 +126,8 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( addOnePlusOnePromoNotification(clickIntents, shouldShowOnePlusOnePromo) + addYieldPromoNotification(clickIntents, shouldShowYieldPromo) + addInformationalNotifications(userWallet, cardTypesResolver, flattenCurrencies, clickIntents) addWarningNotifications(cardTypesResolver, flattenCurrencies, isNeedToBackup, clickIntents) @@ -286,6 +292,19 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( ) } + private fun MutableList.addYieldPromoNotification( + clickIntents: WalletClickIntents, + shouldShowPromo: Boolean, + ) { + addIf( + element = WalletNotification.YieldPromo( + onCloseClick = { clickIntents.onClosePromoClick(promoId = PromoId.YieldPromo) }, + onTermsAndConditionsClick = { clickIntents.onYieldPromoTermsAndConditionsClick() }, + ), + condition = shouldShowPromo, + ) + } + // private fun MutableList.addYieldSupplyNotifications( // flattenCurrencies: Lce>, // ) { @@ -317,6 +336,29 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( element = WalletNotification.Warning.SomeNetworksUnreachable, condition = flattenCurrencies.hasUnreachableNetworks(), ) + + addCloreMigrationNotification(flattenCurrencies, clickIntents) + } + + private fun MutableList.addCloreMigrationNotification( + flattenCurrencies: Lce>, + clickIntents: WalletClickIntents, + ) { + val cloreCurrency = flattenCurrencies.findCloreCurrency() ?: return + + add( + WalletNotification.CloreMigration( + onStartMigrationClick = { clickIntents.onCloreMigrationClick(cloreCurrency) }, + ), + ) + } + + private fun Lce>.findCloreCurrency(): CryptoCurrencyStatus? { + val currencies = getOrNull(isPartialContentAccepted = true) ?: return null + + return currencies.find { currencyStatus -> + BlockchainUtils.isClore(currencyStatus.currency.network.rawId) + } } private fun MutableList.addPushReminderNotification( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt index e2dc6648fc..9abf719a45 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt @@ -396,6 +396,23 @@ sealed class WalletNotification(val config: NotificationConfig) { ), ) + data class YieldPromo( + val onCloseClick: () -> Unit, + val onTermsAndConditionsClick: () -> Unit, + ) : WalletNotification( + config = NotificationConfig( + title = resourceReference(R.string.notification_yield_promo_title), + subtitle = resourceReference(R.string.notification_yield_promo_text), + iconResId = R.drawable.ic_yield_promo_36, + onCloseClick = onCloseClick, + buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( + text = resourceReference(R.string.notification_yield_promo_button), + onClick = onTermsAndConditionsClick, + ), + iconSize = 36.dp, + ), + ) + data class PushNotifications( val onCloseClick: () -> Unit, val onEnabledClick: () -> Unit, @@ -414,4 +431,18 @@ sealed class WalletNotification(val config: NotificationConfig) { iconSize = 54.dp, ), ) + + data class CloreMigration( + val onStartMigrationClick: () -> Unit, + ) : WalletNotification( + config = NotificationConfig( + title = resourceReference(com.tangem.core.res.R.string.warning_clore_migration_title), + subtitle = resourceReference(com.tangem.core.res.R.string.warning_clore_migration_description), + iconResId = R.drawable.img_attention_20, + buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( + text = resourceReference(com.tangem.core.res.R.string.warning_clore_migration_button), + onClick = onStartMigrationClick, + ), + ), + ) } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt index 5790de110c..eacebf5eb3 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt @@ -42,8 +42,6 @@ import androidx.compose.ui.unit.dp import androidx.paging.compose.collectAsLazyPagingItems import com.tangem.common.ui.bottomsheet.chooseaddress.ChooseAddressBottomSheet import com.tangem.common.ui.bottomsheet.chooseaddress.ChooseAddressBottomSheetConfig -import com.tangem.common.ui.bottomsheet.receive.TokenReceiveBottomSheet -import com.tangem.common.ui.bottomsheet.receive.TokenReceiveBottomSheetConfig import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheet import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig import com.tangem.common.ui.expressStatus.expressTransactionsItems @@ -709,7 +707,6 @@ private fun ShowBottomSheet(bottomSheetConfig: TangemBottomSheetConfig?) { if (bottomSheetConfig != null) { when (bottomSheetConfig.content) { is WalletBottomSheetConfig -> WalletBottomSheet(config = bottomSheetConfig) - is TokenReceiveBottomSheetConfig -> TokenReceiveBottomSheet(config = bottomSheetConfig) is ActionsBottomSheetConfig -> TokenActionsBottomSheet(config = bottomSheetConfig) is ChooseAddressBottomSheetConfig -> ChooseAddressBottomSheet(config = bottomSheetConfig) is ExpressStatusBottomSheetConfig -> ExpressStatusBottomSheet(config = bottomSheetConfig) diff --git a/features/wallet/impl/src/main/res/drawable/ic_yield_promo_36.xml b/features/wallet/impl/src/main/res/drawable/ic_yield_promo_36.xml new file mode 100644 index 0000000000..4a4921466d --- /dev/null +++ b/features/wallet/impl/src/main/res/drawable/ic_yield_promo_36.xml @@ -0,0 +1,15 @@ + + + + diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcPairModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcPairModel.kt index bdc6f34464..285229cc87 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcPairModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcPairModel.kt @@ -5,7 +5,8 @@ import com.arkivanov.decompose.router.stack.StackNavigation import com.arkivanov.decompose.router.stack.pop import com.arkivanov.decompose.router.stack.pushNew import com.domain.blockaid.models.dapp.CheckDAppResult -import com.tangem.common.ui.account.CryptoPortfolioIconUM +import com.tangem.common.ui.account.AccountIconUM +import com.tangem.common.ui.account.CryptoPortfolioIconConverter import com.tangem.common.ui.account.PortfolioSelectUM import com.tangem.common.ui.account.toUM import com.tangem.core.analytics.api.AnalyticsEventHandler @@ -276,10 +277,10 @@ internal class WcPairModel @Inject constructor( is Account.Payment -> TODO("[REDACTED_JIRA]") } val isAccountMode = selectorController.isAccountMode.first() - val icon: CryptoPortfolioIconUM? + val icon: AccountIconUM.CryptoPortfolio? val name: TextReference if (isAccountMode) { - icon = account.icon.toUM() + icon = CryptoPortfolioIconConverter.convert(account.icon) name = account.accountName.toUM().value } else { icon = null diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/transformers/WcSessionsAccountModeTransformer.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/transformers/WcSessionsAccountModeTransformer.kt index 598ca31911..fa672da841 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/transformers/WcSessionsAccountModeTransformer.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/transformers/WcSessionsAccountModeTransformer.kt @@ -1,6 +1,7 @@ package com.tangem.features.walletconnect.connections.model.transformers import com.tangem.common.ui.account.AccountTitleUM +import com.tangem.common.ui.account.CryptoPortfolioIconConverter import com.tangem.common.ui.account.toUM import com.tangem.core.ui.extensions.TextReference import com.tangem.domain.account.models.AccountList @@ -70,7 +71,7 @@ internal class WcSessionsAccountModeTransformer( val accountTitle = AccountTitleUM.Account( prefixText = TextReference.EMPTY, name = account.accountName.toUM().value, - icon = accountIcon.toUM(), + icon = CryptoPortfolioIconConverter.convert(accountIcon), ) val accountConnections = WcConnectionsItem.PortfolioConnections( diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcPortfolioNameDelegate.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcPortfolioNameDelegate.kt index ce57447912..5df48f4e4d 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcPortfolioNameDelegate.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcPortfolioNameDelegate.kt @@ -1,6 +1,7 @@ package com.tangem.features.walletconnect.transaction.converter import com.tangem.common.ui.account.AccountTitleUM +import com.tangem.common.ui.account.CryptoPortfolioIconConverter import com.tangem.common.ui.account.toUM import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.stringReference @@ -32,7 +33,7 @@ internal class WcPortfolioNameDelegate @AssistedInject constructor( AccountTitleUM.Account( prefixText = TextReference.EMPTY, name = account.accountName.toUM().value, - icon = account.icon.toUM(), + icon = CryptoPortfolioIconConverter.convert(account.icon), ) } else { AccountTitleUM.Text(stringReference(value.wallet.name)) diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSendTransactionUMConverter.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSendTransactionUMConverter.kt index dbba45773d..e4969a134c 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSendTransactionUMConverter.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSendTransactionUMConverter.kt @@ -16,6 +16,7 @@ import com.tangem.features.walletconnect.transaction.entity.common.WcTransaction import com.tangem.features.walletconnect.transaction.entity.send.WcSendTransactionItemUM import com.tangem.features.walletconnect.transaction.entity.send.WcSendTransactionUM import com.tangem.features.walletconnect.utils.WcNotificationsFactory +import com.tangem.domain.models.wallet.isHotWallet import com.tangem.utils.converter.Converter import kotlinx.collections.immutable.toImmutableList import javax.inject.Inject @@ -62,6 +63,7 @@ internal class WcSendTransactionUMConverter @Inject constructor( } }, feeErrorNotification = feeErrorNotification, + isHoldToConfirmEnabled = value.context.session.wallet.isHotWallet, ), feeSelectorUM = when (value.feeState) { WcTransactionFeeState.None -> FeeSelectorUM.Loading diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSignTransactionUMConverter.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSignTransactionUMConverter.kt index 91f3cbf530..4fd75570a0 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSignTransactionUMConverter.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSignTransactionUMConverter.kt @@ -10,6 +10,7 @@ import com.tangem.features.walletconnect.transaction.entity.common.WcTransaction import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionRequestInfoUM import com.tangem.features.walletconnect.transaction.entity.sign.WcSignTransactionItemUM import com.tangem.features.walletconnect.transaction.entity.sign.WcSignTransactionUM +import com.tangem.domain.models.wallet.isHotWallet import com.tangem.utils.converter.Converter import kotlinx.collections.immutable.toImmutableList import javax.inject.Inject @@ -35,6 +36,7 @@ internal class WcSignTransactionUMConverter @Inject constructor( isLoading = value.signState.domainStep == WcSignStep.Signing, address = WcAddressConverter.convert(value.context.derivationState), walletInteractionIcon = walletInterationIcon(value.context.session.wallet), + isHoldToConfirmEnabled = value.context.session.wallet.isHotWallet, ), transactionRequestInfo = WcTransactionRequestInfoUM( requestBlockUMConverter.convert( diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSignTypedDataUMConverter.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSignTypedDataUMConverter.kt index 5bc7aa70e3..6f185ecfd9 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSignTypedDataUMConverter.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/converter/WcSignTypedDataUMConverter.kt @@ -10,6 +10,7 @@ import com.tangem.features.walletconnect.transaction.entity.common.WcTransaction import com.tangem.features.walletconnect.transaction.entity.common.WcTransactionRequestInfoUM import com.tangem.features.walletconnect.transaction.entity.sign.WcSignTransactionItemUM import com.tangem.features.walletconnect.transaction.entity.sign.WcSignTransactionUM +import com.tangem.domain.models.wallet.isHotWallet import com.tangem.utils.converter.Converter import kotlinx.collections.immutable.toImmutableList import javax.inject.Inject @@ -35,6 +36,7 @@ internal class WcSignTypedDataUMConverter @Inject constructor( address = WcAddressConverter.convert(value.context.derivationState), isLoading = value.signState.domainStep == WcSignStep.Signing, walletInteractionIcon = walletInterationIcon(value.context.session.wallet), + isHoldToConfirmEnabled = value.context.session.wallet.isHotWallet, ), transactionRequestInfo = WcTransactionRequestInfoUM( blocks = buildList { diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/send/WcSendTransactionUM.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/send/WcSendTransactionUM.kt index f98dcecdc5..61a43bb901 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/send/WcSendTransactionUM.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/send/WcSendTransactionUM.kt @@ -34,4 +34,5 @@ internal data class WcSendTransactionItemUM( val feeErrorNotification: NotificationUM.Info?, val isLoading: Boolean = false, val walletInteractionIcon: Int? = null, + val isHoldToConfirmEnabled: Boolean = false, ) : TangemBottomSheetConfigContent \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/sign/WcSignTransactionUM.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/sign/WcSignTransactionUM.kt index 3d63016c10..0316e5b72b 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/sign/WcSignTransactionUM.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/entity/sign/WcSignTransactionUM.kt @@ -21,4 +21,5 @@ internal data class WcSignTransactionItemUM( val address: String?, val walletInteractionIcon: Int?, val isLoading: Boolean = false, + val isHoldToConfirmEnabled: Boolean = false, ) : TangemBottomSheetConfigContent \ No newline at end of file diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcTransactionRequestButtons.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcTransactionRequestButtons.kt index f60920591d..199cc48016 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcTransactionRequestButtons.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/common/WcTransactionRequestButtons.kt @@ -8,9 +8,11 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import com.domain.blockaid.models.transaction.ValidationResult +import com.tangem.core.ui.components.HoldToConfirmButton import com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.components.PrimaryButtonIconEnd import com.tangem.core.ui.components.SecondaryButton +import com.tangem.core.ui.R as CoreR import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.stringResourceSafe @@ -28,37 +30,50 @@ internal fun WcTransactionRequestButtons( onClickActiveButton: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, + isHoldToConfirmEnabled: Boolean = false, ) { WcCommonButtons( onDismiss = onDismiss, modifier = modifier, primaryButton = { + val buttonModifier = Modifier + .fillMaxWidth() + .weight(1f) + // Before change, make sure you are align with WcSendTransactionModel::onSign - when (validationResult) { - ValidationResult.UNSAFE, - ValidationResult.WARNING, - -> PrimaryButton( - modifier = Modifier - .fillMaxWidth() - .weight(1f), - text = stringResourceSafe(R.string.common_continue), - onClick = onClickActiveButton, - showProgress = isLoading, - enabled = enabled, - ) - ValidationResult.SAFE, - ValidationResult.FAILED_TO_VALIDATE, - null, - -> PrimaryButtonIconEnd( - modifier = Modifier - .fillMaxWidth() - .weight(1f), - text = activeButtonText.resolveReference(), - onClick = onClickActiveButton, - iconResId = walletInteractionIcon, - showProgress = isLoading, - enabled = enabled, - ) + when { + validationResult == ValidationResult.UNSAFE || + validationResult == ValidationResult.WARNING -> { + PrimaryButton( + modifier = buttonModifier, + text = stringResourceSafe(R.string.common_continue), + onClick = onClickActiveButton, + showProgress = isLoading, + enabled = enabled, + ) + } + isHoldToConfirmEnabled -> { + HoldToConfirmButton( + modifier = buttonModifier, + text = stringResourceSafe( + CoreR.string.common_hold_to, + activeButtonText.resolveReference(), + ), + onConfirm = onClickActiveButton, + isLoading = isLoading, + enabled = enabled, + ) + } + else -> { + PrimaryButtonIconEnd( + modifier = buttonModifier, + text = activeButtonText.resolveReference(), + onClick = onClickActiveButton, + iconResId = walletInteractionIcon, + showProgress = isLoading, + enabled = enabled, + ) + } } }, ) diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/send/WcSendTransactionModalBottomSheet.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/send/WcSendTransactionModalBottomSheet.kt index 313408a064..76d2c41365 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/send/WcSendTransactionModalBottomSheet.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/send/WcSendTransactionModalBottomSheet.kt @@ -144,6 +144,7 @@ internal fun WcSendTransactionModalBottomSheet( enabled = state.sendEnabled, walletInteractionIcon = state.walletInteractionIcon, validationResult = state.transactionValidationResult, + isHoldToConfirmEnabled = state.isHoldToConfirmEnabled, ) }, ) diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/sign/WcSignTransactionModalBottomSheetContent.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/sign/WcSignTransactionModalBottomSheetContent.kt index d4e6d4a916..5a4d2abb63 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/sign/WcSignTransactionModalBottomSheetContent.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/ui/sign/WcSignTransactionModalBottomSheetContent.kt @@ -101,6 +101,7 @@ internal fun WcSignTransactionModalBottomSheetContent( isLoading = state.isLoading, walletInteractionIcon = state.walletInteractionIcon, validationResult = null, + isHoldToConfirmEnabled = state.isHoldToConfirmEnabled, ) }, ) diff --git a/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/model/WelcomeModel.kt b/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/model/WelcomeModel.kt index 4179ffefe8..83ab025490 100644 --- a/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/model/WelcomeModel.kt +++ b/features/welcome/impl/src/main/kotlin/com/tangem/features/welcome/impl/model/WelcomeModel.kt @@ -197,18 +197,20 @@ internal class WelcomeModel @Inject constructor( onSuccess = { scanResponse -> val userWallet = coldUserWalletBuilderFactory.create(scanResponse = scanResponse).build() ?: return@scan - saveWalletUseCase.invoke(userWallet) - .onLeft { error -> - if (error is SaveWalletError.WalletAlreadySaved) { - userWalletsListRepository.unlock( - userWallet.walletId, - unlockMethod = UserWalletsListRepository.UnlockMethod.Scan(scanResponse), - ).onRight { - userWalletsListRepository.select(userWallet.walletId) - router.replaceAll(AppRoute.Wallet) - } + saveWalletUseCase.invoke( + userWallet = userWallet, + analyticsSource = AnalyticsParam.ScreensSources.SignIn, + ).onLeft { error -> + if (error is SaveWalletError.WalletAlreadySaved) { + userWalletsListRepository.unlock( + userWallet.walletId, + unlockMethod = UserWalletsListRepository.UnlockMethod.Scan(scanResponse), + ).onRight { + userWalletsListRepository.select(userWallet.walletId) + router.replaceAll(AppRoute.Wallet) } } + } .onRight { router.replaceAll(AppRoute.Wallet) } diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/common/entity/YieldSupplyFeeUM.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/common/entity/YieldSupplyFeeUM.kt index 20e09cc6cb..5b2edf1a67 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/common/entity/YieldSupplyFeeUM.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/common/entity/YieldSupplyFeeUM.kt @@ -31,4 +31,5 @@ internal data class YieldSupplyActionUM( val yieldSupplyFeeUM: YieldSupplyFeeUM, val isPrimaryButtonEnabled: Boolean, val isTransactionSending: Boolean, + val isHoldToConfirmEnabled: Boolean, ) \ No newline at end of file diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/common/ui/YieldSupplyActionContent.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/common/ui/YieldSupplyActionContent.kt index 71645d1247..f45cde0551 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/common/ui/YieldSupplyActionContent.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/common/ui/YieldSupplyActionContent.kt @@ -156,6 +156,7 @@ private class YieldSupplyActionContentPreviewProvider : PreviewParameterProvider ), isPrimaryButtonEnabled = false, isTransactionSending = false, + isHoldToConfirmEnabled = false, ), ) } diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/approve/YieldSupplyApproveComponent.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/approve/YieldSupplyApproveComponent.kt index 9e641d0513..f56edfc633 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/approve/YieldSupplyApproveComponent.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/approve/YieldSupplyApproveComponent.kt @@ -18,7 +18,9 @@ import com.tangem.common.ui.userwallet.ext.walletInterationIcon import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.child import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.core.ui.components.HoldToConfirmButton import com.tangem.core.ui.components.PrimaryButtonIconEnd +import com.tangem.core.ui.R as CoreR import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle @@ -74,15 +76,30 @@ internal class YieldSupplyApproveComponent( ) }, footer = { - PrimaryButtonIconEnd( - text = stringResourceSafe(R.string.common_confirm), - onClick = model::onClick, - iconResId = walletInterationIcon(params.userWallet), - enabled = state.isPrimaryButtonEnabled, - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - ) + val modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + + if (state.isHoldToConfirmEnabled) { + HoldToConfirmButton( + text = stringResourceSafe( + CoreR.string.common_hold_to, + stringResourceSafe(R.string.common_confirm), + ), + onConfirm = model::onClick, + enabled = state.isPrimaryButtonEnabled, + isLoading = state.isTransactionSending, + modifier = modifier, + ) + } else { + PrimaryButtonIconEnd( + text = stringResourceSafe(R.string.common_confirm), + onClick = model::onClick, + iconResId = walletInterationIcon(params.userWallet), + enabled = state.isPrimaryButtonEnabled, + modifier = modifier, + ) + } }, content = { YieldSupplyActionContent( diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/approve/model/YieldSupplyApproveModel.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/approve/model/YieldSupplyApproveModel.kt index cc24881811..4ac6a9f1ce 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/approve/model/YieldSupplyApproveModel.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/approve/model/YieldSupplyApproveModel.kt @@ -18,6 +18,7 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.domain.models.wallet.isHotWallet import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase import com.tangem.domain.transaction.usecase.CreateApprovalTransactionUseCase import com.tangem.domain.transaction.usecase.GetFeeUseCase @@ -97,6 +98,7 @@ internal class YieldSupplyApproveModel @Inject constructor( yieldSupplyFeeUM = YieldSupplyFeeUM.Loading, isPrimaryButtonEnabled = false, isTransactionSending = false, + isHoldToConfirmEnabled = params.userWallet.isHotWallet, ), ) diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/YieldSupplyStartEarningComponent.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/YieldSupplyStartEarningComponent.kt index 1783ee53c8..c7957a5d41 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/YieldSupplyStartEarningComponent.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/YieldSupplyStartEarningComponent.kt @@ -17,6 +17,7 @@ import com.tangem.common.ui.userwallet.ext.walletInterationIcon import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.child import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.core.ui.components.HoldToConfirmButton import com.tangem.core.ui.components.PrimaryButtonIconEnd import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle import com.tangem.core.ui.components.currency.icon.CurrencyIcon @@ -25,6 +26,7 @@ import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.core.ui.R as CoreR import com.tangem.features.yield.supply.impl.R import com.tangem.features.yield.supply.impl.common.entity.YieldSupplyActionUM import com.tangem.features.yield.supply.impl.common.entity.YieldSupplyFeeUM @@ -106,22 +108,36 @@ internal class YieldSupplyStartEarningComponent( override fun Footer() { val state by model.uiState.collectAsStateWithLifecycle() - val icon = if (state.isPrimaryButtonEnabled) { - walletInterationIcon(model.userWallet) - } else { - null - } + val modifier = Modifier + .fillMaxWidth() + .padding(16.dp) - PrimaryButtonIconEnd( - text = stringResourceSafe(R.string.yield_module_start_earning), - onClick = model::onClick, - enabled = state.isPrimaryButtonEnabled, - iconResId = icon, - showProgress = state.isTransactionSending, - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - ) + if (state.isHoldToConfirmEnabled) { + HoldToConfirmButton( + text = stringResourceSafe( + CoreR.string.common_hold_to, + stringResourceSafe(R.string.yield_module_start_earning), + ), + onConfirm = model::onClick, + enabled = state.isPrimaryButtonEnabled, + isLoading = state.isTransactionSending, + modifier = modifier, + ) + } else { + val icon = if (state.isPrimaryButtonEnabled) { + walletInterationIcon(model.userWallet) + } else { + null + } + PrimaryButtonIconEnd( + text = stringResourceSafe(R.string.yield_module_start_earning), + onClick = model::onClick, + enabled = state.isPrimaryButtonEnabled, + iconResId = icon, + showProgress = state.isTransactionSending, + modifier = modifier, + ) + } } data class Params( diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/model/YieldSupplyStartEarningEntryModel.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/model/YieldSupplyStartEarningEntryModel.kt index 88108f3a9d..62a991685c 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/model/YieldSupplyStartEarningEntryModel.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/model/YieldSupplyStartEarningEntryModel.kt @@ -49,6 +49,7 @@ internal class YieldSupplyStartEarningEntryModel @Inject constructor( yieldSupplyFeeUM = YieldSupplyFeeUM.Loading, isPrimaryButtonEnabled = false, isTransactionSending = false, + isHoldToConfirmEnabled = false, ), ) diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/model/YieldSupplyStartEarningModel.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/model/YieldSupplyStartEarningModel.kt index c5fda7511e..ce6782467d 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/model/YieldSupplyStartEarningModel.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/model/YieldSupplyStartEarningModel.kt @@ -15,6 +15,7 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.models.wallet.isHotWallet import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase import com.tangem.domain.transaction.error.GetFeeError @@ -107,6 +108,7 @@ internal class YieldSupplyStartEarningModel @Inject constructor( yieldSupplyFeeUM = YieldSupplyFeeUM.Loading, isPrimaryButtonEnabled = false, isTransactionSending = false, + isHoldToConfirmEnabled = false, ), ) @@ -307,6 +309,9 @@ internal class YieldSupplyStartEarningModel @Inject constructor( getUserWalletUseCase(userWalletId).fold( ifRight = { wallet -> userWallet = wallet + uiState.update { + it.copy(isHoldToConfirmEnabled = wallet.isHotWallet) + } getCurrenciesStatusUpdates() }, ifLeft = { diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/stopearning/YieldSupplyStopEarningComponent.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/stopearning/YieldSupplyStopEarningComponent.kt index d67dc087bb..eae26ea8e1 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/stopearning/YieldSupplyStopEarningComponent.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/stopearning/YieldSupplyStopEarningComponent.kt @@ -19,7 +19,9 @@ import com.tangem.common.ui.userwallet.ext.walletInterationIcon import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.child import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.core.ui.components.HoldToConfirmButton import com.tangem.core.ui.components.PrimaryButtonIconEnd +import com.tangem.core.ui.R as CoreR import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle @@ -78,16 +80,31 @@ internal class YieldSupplyStopEarningComponent( ) }, footer = { - PrimaryButtonIconEnd( - text = stringResourceSafe(R.string.common_confirm), - onClick = model::onClick, - iconResId = walletInterationIcon(params.userWallet), - enabled = state.isPrimaryButtonEnabled, - showProgress = state.isTransactionSending, - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - ) + val modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + + if (state.isHoldToConfirmEnabled) { + HoldToConfirmButton( + text = stringResourceSafe( + CoreR.string.common_hold_to, + stringResourceSafe(R.string.common_confirm), + ), + onConfirm = model::onClick, + enabled = state.isPrimaryButtonEnabled, + isLoading = state.isTransactionSending, + modifier = modifier, + ) + } else { + PrimaryButtonIconEnd( + text = stringResourceSafe(R.string.common_confirm), + onClick = model::onClick, + iconResId = walletInterationIcon(params.userWallet), + enabled = state.isPrimaryButtonEnabled, + showProgress = state.isTransactionSending, + modifier = modifier, + ) + } }, content = { YieldSupplyActionContent( diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/stopearning/model/YieldSupplyStopEarningModel.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/stopearning/model/YieldSupplyStopEarningModel.kt index fc7f22db11..875b0b0fb4 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/stopearning/model/YieldSupplyStopEarningModel.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/stopearning/model/YieldSupplyStopEarningModel.kt @@ -15,6 +15,7 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.domain.models.wallet.isHotWallet import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase import com.tangem.domain.transaction.usecase.GetFeeUseCase import com.tangem.domain.transaction.usecase.SendTransactionUseCase @@ -99,6 +100,7 @@ internal class YieldSupplyStopEarningModel @Inject constructor( yieldSupplyFeeUM = YieldSupplyFeeUM.Loading, isPrimaryButtonEnabled = false, isTransactionSending = false, + isHoldToConfirmEnabled = params.userWallet.isHotWallet, ), ) diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index 9f554a7f91..abf365f0c3 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,13 +5,13 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "releases-5.33-1406" +tangemBlockchainSdk = "develop-1408" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "releases-5.33-576" +tangemCardSdk = "develop-577" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ tangemVico = "2.0.0-alpha.25-tangem12" #tangemVico = "0.0.1" # Keep it! - used for local builds ^ -tangemHotSdk = "develop-539" +tangemHotSdk = "develop-541" #tangemHotSdk = "0.0.1" # Keep it! - used for local builds ^ diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt deleted file mode 100644 index 9bdde82e08..0000000000 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/UserWalletManager.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.tangem.lib.crypto - -import com.tangem.lib.crypto.models.ProxyAmount - -/** - * Provider for user tokens data - */ -interface UserWalletManager { - - /** - * Returns user walletId or empty string - */ - fun getWalletId(): String - - suspend fun hideAllTokens() - - /** - * Returns wallet public address for token - * - * @param networkId for currency - * @param derivationPath if null uses default - */ - @Throws(IllegalStateException::class) - suspend fun getWalletAddress(networkId: String, derivationPath: String?): String - - @Throws(IllegalStateException::class) - suspend fun getNativeTokenBalance(networkId: String, derivationPath: String?): ProxyAmount? - - @Throws(IllegalStateException::class) - suspend fun getLastTransactionHash(networkId: String, derivationPath: String?): String? -} \ No newline at end of file diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/AnalyticsData.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/AnalyticsData.kt deleted file mode 100644 index 71a0845c8d..0000000000 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/AnalyticsData.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.tangem.lib.crypto.models - -/** - * Analytics data for send events in analytics engine - * - * @property feeType type of fee (min,max,normal) - * @property tokenSymbol symbol - * @property permissionType optional parameter used for type tx approve - */ -data class AnalyticsData( - val feeType: String, - val tokenSymbol: String, - val permissionType: String? = null, -) \ No newline at end of file diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/ApproveTxData.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/ApproveTxData.kt deleted file mode 100644 index f2bfab34e3..0000000000 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/ApproveTxData.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.tangem.lib.crypto.models - -import java.math.BigDecimal - -/** - * Tx data for create and make approve transaction - * - * @property networkId id of network - * @property feeAmount amount of fee - * @property gasLimit gasLimit for given tx - * @property destinationAddress address to send tx - * @property dataToSign data to sing with signer - */ -data class ApproveTxData( - val networkId: String, - val feeAmount: BigDecimal, - val gasLimit: Int, - val destinationAddress: String, - val dataToSign: String, -) \ No newline at end of file diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/Currency.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/Currency.kt deleted file mode 100644 index 1bdb582455..0000000000 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/Currency.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.tangem.lib.crypto.models - -/** - * Currency data class that can be native blockchain Token - * or custom Token (used in this lib to replace and divide logic Currency from app module) - */ -sealed class Currency( - open val id: String, - open val name: String, - open val symbol: String, - open val networkId: String, -) { - - data class NativeToken( - override val id: String, - override val name: String, - override val symbol: String, - override val networkId: String, - ) : Currency(id, name, symbol, networkId) - - class NonNativeToken( - override val id: String, - override val name: String, - override val symbol: String, - override val networkId: String, - val contractAddress: String, - val decimalCount: Int, - ) : Currency(id, name, symbol, networkId) -} \ No newline at end of file diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/ProxyAmount.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/ProxyAmount.kt deleted file mode 100644 index 4a877ccca3..0000000000 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/ProxyAmount.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.tangem.lib.crypto.models - -import java.math.BigDecimal - -/** - * Proxy amount is always has a Coin type - * - * @property currencySymbol - * @property value amount in [BigDecimal] - * @property decimals count for token - */ -data class ProxyAmount( - val currencySymbol: String, - var value: BigDecimal, - val decimals: Int, -) { - - companion object { - fun empty(): ProxyAmount { - return ProxyAmount("", BigDecimal.ZERO, 0) - } - } -} \ No newline at end of file diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/ProxyFiatCurrency.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/ProxyFiatCurrency.kt deleted file mode 100644 index 5a46bdef84..0000000000 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/ProxyFiatCurrency.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.tangem.lib.crypto.models - -data class ProxyFiatCurrency( - val code: String, - val name: String, - val symbol: String, -) \ No newline at end of file diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/SwapTxData.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/SwapTxData.kt deleted file mode 100644 index 4732f49fcb..0000000000 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/SwapTxData.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.tangem.lib.crypto.models - -import java.math.BigDecimal - -/** - * Tx data for create and make transaction - * - * @property networkId id of network - * @property feeAmount amount of fee - * @property gasLimit gasLimit for given tx - * @property destinationAddress address to send tx - * @property dataToSign data to sing with signer - * @property amountToSend amount of tx - * @property currencyToSend currency for tx - */ -// TODO split to cex,dex -data class SwapTxData( - val networkId: String, - val feeAmount: BigDecimal, - val gasLimit: Int, - val destinationAddress: String, - val dataToSign: String, - val amountToSend: BigDecimal, - val currencyToSend: Currency, -) \ No newline at end of file diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/transactions/SendTxResult.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/models/transactions/SendTxResult.kt deleted file mode 100644 index b3bf9c7e7b..0000000000 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/models/transactions/SendTxResult.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.tangem.lib.crypto.models.transactions - -sealed interface SendTxResult { - - object Success : SendTxResult - object UserCancelledError : SendTxResult - data class TangemSdkError(val code: Int, val cause: Throwable?) : SendTxResult - data class BlockchainSdkError(val code: Int, val cause: Throwable?) : SendTxResult - data class NetworkError(val ex: Exception? = null) : SendTxResult - data class UnknownError(val ex: Exception? = null) : SendTxResult -} \ No newline at end of file