diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index 5294908fc5..6edc1d9d21 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -17,6 +17,7 @@
+
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index ea77304740..89ca7c3734 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -22,6 +22,7 @@ dependencies {
implementation(project(":core:utils"))
implementation(project(":libs:crypto"))
implementation(project(":libs:auth"))
+ implementation(project(":data:source:preferences"))
/** Features */
implementation(project(":features:onboarding"))
diff --git a/app/src/main/assets/testnet_tokens.json b/app/src/main/assets/testnet_tokens.json
index d36455384c..b7f5b4f8e4 100644
--- a/app/src/main/assets/testnet_tokens.json
+++ b/app/src/main/assets/testnet_tokens.json
@@ -484,6 +484,17 @@
"networkId": "ravencoin/test"
}
]
+ },
+ {
+ "id": "cosmos",
+ "symbol": "ATOM",
+ "name": "Cosmos Hub",
+ "networks":
+ [
+ {
+ "networkId": "cosmos/test"
+ }
+ ]
}
]
}
diff --git a/app/src/main/java/com/tangem/tap/TapApplication.kt b/app/src/main/java/com/tangem/tap/TapApplication.kt
index f8efc046b2..ad51eea81f 100644
--- a/app/src/main/java/com/tangem/tap/TapApplication.kt
+++ b/app/src/main/java/com/tangem/tap/TapApplication.kt
@@ -20,6 +20,7 @@ import com.tangem.datasource.config.models.Config
import com.tangem.datasource.connection.NetworkConnectionManager
import com.tangem.domain.DomainLayer
import com.tangem.domain.common.LogConfig
+import com.tangem.data.source.preferences.PreferencesDataSource
import com.tangem.tap.common.IntentHandler
import com.tangem.tap.common.analytics.AnalyticsFactory
import com.tangem.tap.common.analytics.api.AnalyticsHandlerBuilder
@@ -51,9 +52,8 @@ import com.tangem.tap.domain.walletStores.repository.di.provideDefaultImplementa
import com.tangem.tap.domain.walletconnect.WalletConnectRepository
import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles
import com.tangem.tap.features.tokens.api.featuretoggles.TokensListFeatureToggles
-import com.tangem.tap.persistence.PreferencesStorage
import com.tangem.tap.proxy.AppStateHolder
-import com.tangem.tap.proxy.redux.DaggerGraphAction
+import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.wallet.BuildConfig
import dagger.hilt.android.HiltAndroidApp
import kotlinx.coroutines.launch
@@ -66,7 +66,7 @@ lateinit var store: Store
lateinit var foregroundActivityObserver: ForegroundActivityObserver
lateinit var activityResultCaller: ActivityResultCaller
-lateinit var preferencesStorage: PreferencesStorage
+lateinit var preferencesStorage: PreferencesDataSource
lateinit var walletConnectRepository: WalletConnectRepository
lateinit var shopService: TangemShopService
lateinit var userTokensRepository: UserTokensRepository
@@ -134,6 +134,9 @@ class TapApplication : Application(), ImageLoaderFactory {
@Inject
lateinit var customTokenFeatureToggles: CustomTokenFeatureToggles
+ @Inject
+ lateinit var preferencesDataSource: PreferencesDataSource
+
override fun onCreate() {
super.onCreate()
@@ -142,7 +145,14 @@ class TapApplication : Application(), ImageLoaderFactory {
appReducer(action, state, appStateHolder)
},
middleware = AppState.getMiddleware(),
- state = AppState(),
+ state = AppState(
+ daggerGraphState = DaggerGraphState(
+ assetReader = assetReader,
+ networkConnectionManager = networkConnectionManager,
+ tokensListFeatureToggles = tokensListFeatureToggles,
+ customTokenFeatureToggles = customTokenFeatureToggles,
+ ),
+ ),
)
if (BuildConfig.DEBUG) {
@@ -154,7 +164,7 @@ class TapApplication : Application(), ImageLoaderFactory {
registerActivityLifecycleCallbacks(foregroundActivityObserver.callbacks)
DomainLayer.init()
- preferencesStorage = PreferencesStorage(this)
+ preferencesStorage = preferencesDataSource
walletConnectRepository = WalletConnectRepository(this)
val configLoader = FeaturesLocalLoader(assetReader, MoshiConverter.sdkMoshi, BuildConfig.ENVIRONMENT)
@@ -178,15 +188,6 @@ class TapApplication : Application(), ImageLoaderFactory {
appStateHolder.userTokensRepository = userTokensRepository
appStateHolder.walletStoresManager = walletStoresManager
- store.dispatch(
- action = DaggerGraphAction.SetApplicationDependencies(
- assetReader = assetReader,
- networkConnectionManager = networkConnectionManager,
- tokensListFeatureToggles = tokensListFeatureToggles,
- customTokenFeatureToggles = customTokenFeatureToggles,
- ),
- )
-
scope.launch {
featureTogglesManager.init()
}
@@ -248,7 +249,7 @@ class TapApplication : Application(), ImageLoaderFactory {
private fun initFeedbackManager(
context: Context,
- preferencesStorage: PreferencesStorage,
+ preferencesStorage: PreferencesDataSource,
foregroundActivityObserver: ForegroundActivityObserver,
store: Store,
) {
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/topup/TopUpController.kt b/app/src/main/java/com/tangem/tap/common/analytics/topup/TopUpController.kt
index 30caf9f355..97edaae4c1 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/topup/TopUpController.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/topup/TopUpController.kt
@@ -3,9 +3,11 @@ package com.tangem.tap.common.analytics.topup
import com.tangem.common.extensions.guard
import com.tangem.common.extensions.isZero
import com.tangem.core.analytics.Analytics
+import com.tangem.data.source.preferences.model.DataSourceTopupInfo
+import com.tangem.data.source.preferences.storage.ToppedUpWalletStorage
import com.tangem.domain.common.CardTypesResolver
-import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.common.util.UserWalletId
+import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.common.analytics.converters.TopUpEventConverter
import com.tangem.tap.common.analytics.events.AnalyticsParam
@@ -19,7 +21,6 @@ import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.domain.walletCurrencies.WalletCurrenciesManager
import com.tangem.tap.domain.walletStores.WalletStoresManager
import com.tangem.tap.features.wallet.models.Currency
-import com.tangem.tap.persistence.ToppedUpWalletStorage
import com.tangem.tap.scope
import kotlinx.coroutines.launch
import java.math.BigDecimal
@@ -90,9 +91,9 @@ class TopUpController(
val isToppedUpInPast = findToppedUpCurrenciesInPast(walletDataModels).isNotEmpty()
if (isToppedUpInPast) {
- val newWalletInfo = ToppedUpWalletStorage.TopupInfo(
+ val newWalletInfo = DataSourceTopupInfo(
walletId = userWalletId.stringValue,
- cardBalanceState = AnalyticsParam.CardBalanceState.Full,
+ cardBalanceState = DataSourceTopupInfo.CardBalanceState.Full,
)
topupWalletStorage.save(newWalletInfo)
return@launch
@@ -109,9 +110,9 @@ class TopUpController(
fun registerEmptyWallet(scanResponse: ScanResponse) {
UserWalletIdBuilder.scanResponse(scanResponse).build()?.let {
topupWalletStorage.save(
- ToppedUpWalletStorage.TopupInfo(
+ DataSourceTopupInfo(
walletId = it.stringValue,
- cardBalanceState = AnalyticsParam.CardBalanceState.Empty,
+ cardBalanceState = DataSourceTopupInfo.CardBalanceState.Empty,
),
)
}
@@ -129,17 +130,28 @@ class TopUpController(
cardTypesResolver: CardTypesResolver,
) {
val topupInfo = topupWalletStorage.restore(userWalletId.stringValue).guard {
- val topupInfo = ToppedUpWalletStorage.TopupInfo(
+ val topupInfo = DataSourceTopupInfo(
walletId = userWalletId.stringValue,
- cardBalanceState = cardBalanceState,
+ cardBalanceState = when (cardBalanceState) {
+ AnalyticsParam.CardBalanceState.BlockchainError ->
+ DataSourceTopupInfo.CardBalanceState.BlockchainError
+ AnalyticsParam.CardBalanceState.CustomToken ->
+ DataSourceTopupInfo.CardBalanceState.CustomToken
+ AnalyticsParam.CardBalanceState.Empty ->
+ DataSourceTopupInfo.CardBalanceState.Empty
+ AnalyticsParam.CardBalanceState.Full ->
+ DataSourceTopupInfo.CardBalanceState.Full
+ },
)
topupWalletStorage.save(topupInfo)
return
}
- if (topupInfo.isToppedUp) return
- if (!topupInfo.isToppedUp && cardBalanceState.isToppedUp()) {
- topupWalletStorage.save(topupInfo.copy(cardBalanceState = AnalyticsParam.CardBalanceState.Full))
+ val isToppedUp = topupInfo.cardBalanceState == DataSourceTopupInfo.CardBalanceState.Full
+ if (isToppedUp) return
+
+ if (cardBalanceState.isToppedUp()) {
+ topupWalletStorage.save(topupInfo.copy(cardBalanceState = DataSourceTopupInfo.CardBalanceState.Full))
TopUpEventConverter().convert(cardTypesResolver)?.let {
Analytics.send(it)
}
diff --git a/app/src/main/java/com/tangem/tap/common/chat/ChatManager.kt b/app/src/main/java/com/tangem/tap/common/chat/ChatManager.kt
index bc69217fa1..182aa7350b 100644
--- a/app/src/main/java/com/tangem/tap/common/chat/ChatManager.kt
+++ b/app/src/main/java/com/tangem/tap/common/chat/ChatManager.kt
@@ -2,19 +2,19 @@ package com.tangem.tap.common.chat
import android.content.Context
import android.os.Build
-import com.tangem.tap.ForegroundActivityObserver
import com.tangem.datasource.config.models.ChatConfig
-import com.tangem.tap.common.chat.opener.ChatOpener
import com.tangem.datasource.config.models.SprinklrConfig
import com.tangem.datasource.config.models.ZendeskConfig
+import com.tangem.data.source.preferences.PreferencesDataSource
+import com.tangem.tap.ForegroundActivityObserver
+import com.tangem.tap.common.chat.opener.ChatOpener
import com.tangem.tap.common.chat.opener.implementation.SprinklrChatOpener
import com.tangem.tap.common.chat.opener.implementation.ZendeskChatOpener
import com.tangem.tap.common.redux.AppState
-import com.tangem.tap.persistence.PreferencesStorage
import org.rekotlin.Store
class ChatManager(
- private val preferencesStorage: PreferencesStorage,
+ private val preferencesStorage: PreferencesDataSource,
private val foregroundActivityObserver: ForegroundActivityObserver,
private val store: Store,
) {
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt b/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt
index 5aae0ce706..58a7fa8076 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/Blockchain.kt
@@ -40,6 +40,7 @@ fun Blockchain.getGreyedOutIconRes(): Int {
Blockchain.TON, Blockchain.TONTestnet -> R.drawable.ic_ton_no_color
Blockchain.Kava, Blockchain.KavaTestnet -> R.drawable.ic_kava_no_color
Blockchain.Ravencoin, Blockchain.RavencoinTestnet -> R.drawable.ic_ravencoin_no_color
+ Blockchain.Cosmos, Blockchain.CosmosTestnet -> R.drawable.ic_cosmos_no_color
else -> R.drawable.ic_tangem_logo
}
}
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Collections.kt b/app/src/main/java/com/tangem/tap/common/extensions/Collections.kt
index 517d8ca5ed..ee2867d25c 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/Collections.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/Collections.kt
@@ -1,15 +1,5 @@
package com.tangem.tap.common.extensions
-/**
-[REDACTED_AUTHOR]
- */
-fun List.containsAny(list: List): Boolean {
- this.forEach { mainItem ->
- list.forEach { if (it == mainItem) return true }
- }
- return false
-}
-
fun MutableList.removeBy(predicate: (T) -> Boolean): Boolean {
val toRemove = this.filter(predicate)
this.removeAll(toRemove)
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt
index efd80355aa..fbecc6daed 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/Navigation.kt
@@ -5,6 +5,7 @@ import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
+import androidx.fragment.app.FragmentTransaction
import com.tangem.feature.referral.ReferralFragment
import com.tangem.feature.swap.presentation.SwapFragment
import com.tangem.tap.common.redux.navigation.AppScreen
@@ -45,24 +46,35 @@ fun FragmentActivity.openFragment(
bundle: Bundle? = null,
fgShareTransition: FragmentShareTransition? = null,
) {
- val transaction = this.supportFragmentManager.beginTransaction()
- val fragment = fragmentFactory(screen)
- fragment.arguments = bundle
- fgShareTransition?.apply {
- fragment.sharedElementEnterTransition = enterTransitionSet
- fragment.sharedElementReturnTransition = exitTransitionSet
- transaction.setReorderingAllowed(true)
- shareElements.forEach { shareElement ->
- shareElement.wView.get()?.let { view ->
- transaction.addSharedElement(view, shareElement.elementName)
+ val transaction = supportFragmentManager.beginTransaction().apply {
+ setReorderingAllowed(true)
+ }
+
+ val fragment = fragmentFactory(screen).apply {
+ arguments = bundle
+
+ if (fgShareTransition != null) {
+ sharedElementEnterTransition = fgShareTransition.enterTransitionSet
+ sharedElementReturnTransition = fgShareTransition.exitTransitionSet
+ fgShareTransition.shareElements.forEach { shareElement ->
+ shareElement.wView.get()?.let { view ->
+ transaction.addSharedElement(view, shareElement.elementName)
+ }
}
}
}
+
if (screen.isDialogFragment) {
- (fragment as DialogFragment).show(transaction, screen.name)
- if (addToBackstack) {
- transaction.addToBackStack(screen.name)
+ val dialogFragment = requireNotNull(fragment as? DialogFragment) {
+ "If screen.isDialogFragment == true then fragment must be a DialogFragment"
}
+
+ dialogFragment.showAllowingStateLoss(
+ fragmentManager = supportFragmentManager,
+ baseTransaction = transaction,
+ tag = screen.name,
+ addToBackstack = addToBackstack,
+ )
} else {
transaction.replace(R.id.fragment_container, fragment, screen.name)
if (addToBackstack) {
@@ -72,6 +84,29 @@ fun FragmentActivity.openFragment(
}
}
+private fun DialogFragment.showAllowingStateLoss(
+ fragmentManager: FragmentManager,
+ baseTransaction: FragmentTransaction,
+ tag: String,
+ addToBackstack: Boolean,
+) {
+ runCatching {
+ if (addToBackstack) baseTransaction.addToBackStack(tag)
+ show(baseTransaction, tag)
+ }
+ .onFailure { throwable ->
+ if (throwable is IllegalStateException) {
+ val transaction = fragmentManager.beginTransaction()
+ transaction.add(this, tag)
+ if (addToBackstack) transaction.addToBackStack(tag)
+
+ transaction.commitAllowingStateLoss()
+ } else {
+ Timber.e(throwable)
+ }
+ }
+}
+
fun FragmentActivity.popBackTo(screen: AppScreen?, inclusive: Boolean = false) {
val inclusiveFlag = if (inclusive) FragmentManager.POP_BACK_STACK_INCLUSIVE else 0
try {
@@ -119,6 +154,7 @@ private fun fragmentFactory(screen: AppScreen): Fragment {
)
if (featureToggles.isRedesignedScreenEnabled) TokensListFragment() else AddTokensFragment()
}
+
AppScreen.AddCustomToken -> {
val featureToggles = store.state.daggerGraphState.get(
getDependency = DaggerGraphState::customTokenFeatureToggles,
@@ -129,6 +165,7 @@ private fun fragmentFactory(screen: AppScreen): Fragment {
AddCustomTokenFragment()
}
}
+
AppScreen.WalletDetails -> WalletDetailsFragment()
AppScreen.WalletConnectSessions -> WalletConnectFragment()
AppScreen.QrScan -> QrScanFragment()
diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt
index 39d29a451f..01d00c9a82 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalMiddleware.kt
@@ -5,11 +5,12 @@ import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.guard
import com.tangem.datasource.config.models.Config
import com.tangem.domain.common.LogConfig
-import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.common.extensions.withMainContext
+import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ProductType
import com.tangem.domain.models.scan.ScanResponse
+import com.tangem.tap.common.entities.FiatCurrency
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchOnMain
@@ -75,7 +76,9 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di
is GlobalAction.RestoreAppCurrency -> {
store.dispatch(
GlobalAction.RestoreAppCurrency.Success(
- preferencesStorage.fiatCurrenciesPrefStorage.getAppCurrency(),
+ preferencesStorage.fiatCurrenciesPrefStorage.getAppCurrency()
+ ?.run { FiatCurrency(code, name, symbol) }
+ ?: FiatCurrency.Default,
),
)
}
diff --git a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt
index 9d908f0724..b5122147ca 100644
--- a/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/configurable/warningMessage/WarningMessagesManager.kt
@@ -1,16 +1,16 @@
package com.tangem.tap.domain.configurable.warningMessage
import com.tangem.blockchain.common.Blockchain
-import com.tangem.tap.common.extensions.containsAny
import com.tangem.tap.common.extensions.removeBy
import com.tangem.wallet.R
+import java.util.concurrent.CopyOnWriteArrayList
/**
[REDACTED_AUTHOR]
*/
class WarningMessagesManager {
- private val warningsList: MutableList = mutableListOf()
+ private val warningsList = CopyOnWriteArrayList()
fun addWarning(warning: WarningMessage) {
if (findWarning(warning) == null) {
@@ -19,36 +19,27 @@ class WarningMessagesManager {
}
}
- fun getWarnings(
- location: WarningMessage.Location,
- forBlockchains: List = emptyList(),
- ): List {
- return warningsList
- .filter { !it.isHidden && it.location.contains(location) }
- .filter {
- val list = it.blockchainList
- when {
- list == null -> true
- list.containsAny(forBlockchains) -> true
- else -> false
- }
- }
+ fun getWarnings(location: WarningMessage.Location, blockchains: List): List {
+ return warningsList.filter { message ->
+ val messageBlockchains = message.blockchainList
+ val isCorrespondingMessageBlockchains = messageBlockchains == null ||
+ messageBlockchains.any(blockchains::contains)
+ val isCorrespondingMessageLocation = message.location.contains(location)
+
+ !message.isHidden && isCorrespondingMessageLocation && isCorrespondingMessageBlockchains
+ }
}
fun hideWarning(warning: WarningMessage): Boolean {
- val foundWarning = findWarning(warning)
- return when {
- foundWarning == null -> false
- foundWarning.type == WarningMessage.Type.Temporary ||
- foundWarning.type == WarningMessage.Type.AppRating -> {
- if (foundWarning.isHidden) {
- false
- } else {
- foundWarning.isHidden = true
- true
- }
- }
- else -> false
+ val foundWarning = findWarning(warning) ?: return false
+ val isCorrectType = foundWarning.type == WarningMessage.Type.Temporary ||
+ foundWarning.type == WarningMessage.Type.AppRating
+
+ return if (!foundWarning.isHidden && isCorrectType) {
+ foundWarning.isHidden = true
+ true
+ } else {
+ false
}
}
@@ -72,31 +63,31 @@ class WarningMessagesManager {
companion object {
const val REMAINING_SIGNATURES_WARNING = 10
- fun devCardWarning(): WarningMessage = WarningMessage(
- "",
- "",
+ val devCardWarning = WarningMessage(
+ title = "",
+ message = "",
type = WarningMessage.Type.Permanent,
priority = WarningMessage.Priority.Critical,
- listOf(WarningMessage.Location.MainScreen),
- null,
- R.string.common_warning,
- R.string.alert_developer_card,
- WarningMessage.Origin.Local,
+ location = listOf(WarningMessage.Location.MainScreen),
+ blockchains = null,
+ titleResId = R.string.common_warning,
+ messageResId = R.string.alert_developer_card,
+ origin = WarningMessage.Origin.Local,
)
- fun alreadySignedHashesWarning(): WarningMessage = WarningMessage(
- "",
- "",
+ val alreadySignedHashesWarning = WarningMessage(
+ title = "",
+ message = "",
type = WarningMessage.Type.Temporary,
priority = WarningMessage.Priority.Info,
- listOf(WarningMessage.Location.MainScreen),
- null,
- R.string.common_warning,
- R.string.alert_card_signed_transactions,
- WarningMessage.Origin.Local,
+ location = listOf(WarningMessage.Location.MainScreen),
+ blockchains = null,
+ titleResId = R.string.common_warning,
+ messageResId = R.string.alert_card_signed_transactions,
+ origin = WarningMessage.Origin.Local,
)
- fun signedHashesMultiWalletWarning(): WarningMessage = WarningMessage(
+ val signedHashesMultiWalletWarning = WarningMessage(
title = "",
message = "",
type = WarningMessage.Type.Temporary,
@@ -110,69 +101,71 @@ class WarningMessagesManager {
titleFormatArg = "\u26A0",
)
- fun appRatingWarning(): WarningMessage = WarningMessage(
- "",
- "",
- WarningMessage.Type.AppRating,
- WarningMessage.Priority.Info,
- listOf(WarningMessage.Location.MainScreen),
- null,
- R.string.warning_rate_app_title,
- R.string.warning_rate_app_message,
- WarningMessage.Origin.Local,
+ val appRatingWarning = WarningMessage(
+ title = "",
+ message = "",
+ type = WarningMessage.Type.AppRating,
+ priority = WarningMessage.Priority.Info,
+ location = listOf(WarningMessage.Location.MainScreen),
+ blockchains = null,
+ titleResId = R.string.warning_rate_app_title,
+ messageResId = R.string.warning_rate_app_message,
+ origin = WarningMessage.Origin.Local,
)
- fun isAlreadySignedHashesWarning(warning: WarningMessage): Boolean {
- return warning.messageResId == R.string.alert_card_signed_transactions
- }
-
- fun onlineVerificationFailed(): WarningMessage = WarningMessage(
- "",
- "",
- type = WarningMessage.Type.Permanent,
- priority = WarningMessage.Priority.Critical,
- listOf(WarningMessage.Location.MainScreen),
- null,
- R.string.warning_failed_to_verify_card_title,
- R.string.warning_failed_to_verify_card_message,
- WarningMessage.Origin.Local,
- )
-
- fun remainingSignaturesNotEnough(remainingSignatures: Int): WarningMessage = WarningMessage(
+ val onlineVerificationFailed = WarningMessage(
title = "",
message = "",
type = WarningMessage.Type.Permanent,
priority = WarningMessage.Priority.Critical,
- listOf(WarningMessage.Location.MainScreen),
+ location = listOf(WarningMessage.Location.MainScreen),
blockchains = null,
- titleResId = R.string.common_warning,
- messageResId = R.string.warning_low_signatures_format,
+ titleResId = R.string.warning_failed_to_verify_card_title,
+ messageResId = R.string.warning_failed_to_verify_card_message,
origin = WarningMessage.Origin.Local,
- messageFormatArg = remainingSignatures.toString(),
)
- fun testCardWarning(): WarningMessage = WarningMessage(
- "",
- "",
+ val testCardWarning = WarningMessage(
+ title = "",
+ message = "",
type = WarningMessage.Type.TestCard,
priority = WarningMessage.Priority.Critical,
- listOf(WarningMessage.Location.MainScreen, WarningMessage.Location.SendScreen),
- null,
- R.string.common_warning,
- R.string.warning_testnet_card_message,
- WarningMessage.Origin.Local,
+ location = listOf(WarningMessage.Location.MainScreen, WarningMessage.Location.SendScreen),
+ blockchains = null,
+ titleResId = R.string.common_warning,
+ messageResId = R.string.warning_testnet_card_message,
+ origin = WarningMessage.Origin.Local,
)
- fun demoCardWarning(): WarningMessage = WarningMessage(
- "",
- "",
+ val demoCardWarning = WarningMessage(
+ title = "",
+ message = "",
type = WarningMessage.Type.Permanent,
priority = WarningMessage.Priority.Critical,
- listOf(WarningMessage.Location.MainScreen),
- null,
- R.string.common_warning,
- R.string.alert_demo_message,
- WarningMessage.Origin.Local,
+ location = listOf(WarningMessage.Location.MainScreen),
+ blockchains = null,
+ titleResId = R.string.common_warning,
+ messageResId = R.string.alert_demo_message,
+ origin = WarningMessage.Origin.Local,
)
+
+ fun remainingSignaturesNotEnough(remainingSignatures: Int): WarningMessage {
+ return WarningMessage(
+ title = "",
+ message = "",
+ type = WarningMessage.Type.Permanent,
+ priority = WarningMessage.Priority.Critical,
+ location = listOf(WarningMessage.Location.MainScreen),
+ blockchains = null,
+ titleResId = R.string.common_warning,
+ messageResId = R.string.warning_low_signatures_format,
+ origin = WarningMessage.Origin.Local,
+ messageFormatArg = remainingSignatures.toString(),
+ )
+ }
+
+ fun isAlreadySignedHashesWarning(warning: WarningMessage): Boolean {
+ return warning.messageResId == R.string.alert_card_signed_transactions
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt
index 75ea69067b..4a7518e9f9 100644
--- a/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt
+++ b/app/src/main/java/com/tangem/tap/domain/tasks/product/CreateProductWalletTask.kt
@@ -10,12 +10,12 @@ import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.ByteArrayKey
import com.tangem.common.extensions.guard
import com.tangem.common.extensions.toMapKey
-import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.common.map
-import com.tangem.domain.models.scan.CardDTO
+import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.common.CardTypesResolver
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.domain.common.TapWorkarounds.isTestCard
+import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.KeyWalletPublicKey
import com.tangem.operations.CommandResponse
import com.tangem.operations.backup.PrimaryCard
@@ -233,17 +233,38 @@ private class CreateWalletTangemWallet : ProductCommandProcessor) -> Unit,
) {
val map = mutableMapOf>()
+ var isBlockchainsForCurvesExist = false
createWalletResponse.forEach { response ->
val blockchainsForCurve = getBlockchains(response.cardId, card).filter {
it.getSupportedCurves().contains(response.wallet.curve)
}
- val derivationPaths = blockchainsForCurve.mapNotNull { it.derivationPath(card.derivationStyle) }
+ val derivationPaths = blockchainsForCurve.mapNotNull {
+ isBlockchainsForCurvesExist = true
+ it.derivationPath(card.derivationStyle)
+ }
if (derivationPaths.isNotEmpty()) {
map[response.wallet.publicKey.toMapKey()] = derivationPaths
}
}
+ val cardEnv = session.environment.card
+ if (cardEnv == null) {
+ callback(CompletionResult.Failure(TangemSdkError.CardError()))
+ return
+ }
if (map.isEmpty()) {
- callback(CompletionResult.Failure(TangemSdkError.UnknownError()))
+ if (isBlockchainsForCurvesExist) {
+ callback(CompletionResult.Failure(TangemSdkError.UnknownError()))
+ } else {
+ // if there is no blockchains to derive, just return success response with empty derivedKeys
+ callback(
+ CompletionResult.Success(
+ CreateProductWalletTaskResponse(
+ card = cardEnv,
+ primaryCard = primaryCard,
+ ),
+ ),
+ )
+ }
return
}
@@ -254,7 +275,7 @@ private class CreateWalletTangemWallet : ProductCommandProcessor {
id = value.id,
name = value.name,
symbol = value.symbol,
+ isActive = value.active,
network = value.networks.firstOrNull()?.let { network ->
FoundToken.Network(
id = network.networkId,
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/models/FoundToken.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/models/FoundToken.kt
index 25c3cb5836..6ca52f5eb5 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/models/FoundToken.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/models/FoundToken.kt
@@ -3,14 +3,21 @@ package com.tangem.tap.features.customtoken.impl.domain.models
/**
* Found token model
*
- * @property id id
- * @property name name
- * @property symbol symbol
- * @property network network
+ * @property id id
+ * @property name name
+ * @property symbol symbol
+ * @property isActive flag that determines status of token
+ * @property network network
*
[REDACTED_AUTHOR]
*/
-data class FoundToken(val id: String, val name: String, val symbol: String, val network: Network) {
+data class FoundToken(
+ val id: String,
+ val name: String,
+ val symbol: String,
+ val isActive: Boolean,
+ val network: Network,
+) {
/**
* Found token network
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/AddCustomTokenFragment.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/AddCustomTokenFragment.kt
index ee63fc97e0..f4478fc4fc 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/AddCustomTokenFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/AddCustomTokenFragment.kt
@@ -6,6 +6,7 @@ import android.view.View
import android.view.ViewGroup
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.core.view.WindowCompat
import androidx.fragment.app.Fragment
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.transition.TransitionInflater
@@ -24,6 +25,8 @@ import dagger.hilt.android.AndroidEntryPoint
internal class AddCustomTokenFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
+ activity?.window?.let { WindowCompat.setDecorFitsSystemWindows(it, true) }
+
with(TransitionInflater.from(requireContext())) {
enterTransition = inflateTransition(R.transition.fade)
exitTransition = inflateTransition(R.transition.fade)
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/models/AddCustomTokenViewsModels.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/models/AddCustomTokenViewsModels.kt
index 90dcb8a583..a43c5a84c4 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/models/AddCustomTokenViewsModels.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/models/AddCustomTokenViewsModels.kt
@@ -1,8 +1,6 @@
package com.tangem.tap.features.customtoken.impl.presentation.models
import androidx.compose.foundation.text.KeyboardOptions
-import androidx.compose.ui.text.input.ImeAction
-import androidx.compose.ui.text.input.KeyboardType
import com.tangem.blockchain.common.Blockchain
import com.tangem.tap.features.details.ui.cardsettings.TextReference
import com.tangem.wallet.R
@@ -94,106 +92,94 @@ internal sealed interface AddCustomTokenInputField {
/** Label */
val label: TextReference
- /** Input availability */
- val isEnabled: Boolean
-
- /** Flag that determine if current value has error */
- val isError: Boolean
-
/** Placeholder (hint) */
val placeholder: TextReference
- /** Flag that determine the processing of current value */
- val isLoading: Boolean
-
/**
* Input field model to enter the contract address
*
- * @property value current value
- * @property onValueChange lambda be invoked when value is been changed
- * @property isError flag that determine if current value has error
- * @property isLoading flag that determine the processing of current value
+ * @property value current value
+ * @property onValueChange lambda be invoked when value is been changed
+ * @property keyboardOptions keyboard options
+ * @property label label
+ * @property placeholder placeholder (hint)
+ * @property isLoading flag that determine the processing of current value
+ * @property isError flag that determine if current value has error
+ * @property error error description
*/
data class ContactAddress(
override val value: String,
override val onValueChange: (String) -> Unit,
- override val isError: Boolean,
- override val isLoading: Boolean,
- ) : AddCustomTokenInputField {
- override val isEnabled = true
- override val keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next)
- override val label = TextReference.Res(R.string.custom_token_contract_address_input_title)
- override val placeholder = TextReference.Str(value = "0x0000000000000000000000000000000000000000")
- }
+ override val keyboardOptions: KeyboardOptions,
+ override val label: TextReference,
+ override val placeholder: TextReference,
+ val isLoading: Boolean,
+ val isError: Boolean,
+ val error: TextReference? = null,
+ ) : AddCustomTokenInputField
/**
* Input field model to enter the token name
*
- * @property value current value
- * @property onValueChange lambda be invoked when value is been changed
- * @property isEnabled input availability
- * @property isError flag that determine if current value has error
+ * @property value current value
+ * @property onValueChange lambda be invoked when value is been changed
+ * @property keyboardOptions keyboard options
+ * @property label label
+ * @property placeholder placeholder (hint)
+ * @property isEnabled input availability
*/
data class TokenName(
override val value: String,
override val onValueChange: (String) -> Unit,
- override val isEnabled: Boolean,
- override val isError: Boolean,
- ) : AddCustomTokenInputField {
- override val keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next)
- override val label = TextReference.Res(R.string.custom_token_name_input_title)
- override val placeholder = TextReference.Res(id = R.string.custom_token_name_input_placeholder)
- override val isLoading = false
- }
+ override val keyboardOptions: KeyboardOptions,
+ override val label: TextReference,
+ override val placeholder: TextReference,
+ val isEnabled: Boolean,
+ ) : AddCustomTokenInputField
/**
* Input field model to enter the token symbol
*
- * @property value current value
- * @property onValueChange lambda be invoked when value is been changed
- * @property isEnabled input availability
- * @property isError flag that determine if current value has error
+ * @property value current value
+ * @property onValueChange lambda be invoked when value is been changed
+ * @property keyboardOptions keyboard options
+ * @property label label
+ * @property placeholder placeholder (hint)
+ * @property isEnabled input availability
*/
data class TokenSymbol(
override val value: String,
override val onValueChange: (String) -> Unit,
- override val isEnabled: Boolean,
- override val isError: Boolean,
- ) : AddCustomTokenInputField {
- override val keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next)
- override val label = TextReference.Res(R.string.custom_token_token_symbol_input_title)
- override val placeholder = TextReference.Res(id = R.string.custom_token_token_symbol_input_placeholder)
- override val isLoading = false
- }
+ override val keyboardOptions: KeyboardOptions,
+ override val label: TextReference,
+ override val placeholder: TextReference,
+ val isEnabled: Boolean,
+ ) : AddCustomTokenInputField
/**
* Input field model to enter the token decimals
*
- * @property value current value
- * @property onValueChange lambda be invoked when value is been changed
- * @property isEnabled input availability
- * @property isError flag that determine if current value has error
+ * @property value current value
+ * @property onValueChange lambda be invoked when value is been changed
+ * @property keyboardOptions keyboard options
+ * @property label label
+ * @property placeholder placeholder (hint)
+ * @property isEnabled input availability
*/
data class Decimals(
override val value: String,
override val onValueChange: (String) -> Unit,
- override val isEnabled: Boolean,
- override val isError: Boolean,
- ) : AddCustomTokenInputField {
- override val keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Next)
- override val label = TextReference.Res(R.string.custom_token_decimals_input_title)
- override val placeholder = TextReference.Str(value = "8")
- override val isLoading = false
- }
+ override val keyboardOptions: KeyboardOptions,
+ override val label: TextReference,
+ override val placeholder: TextReference,
+ val isEnabled: Boolean,
+ ) : AddCustomTokenInputField
}
/** Base selector field model of add custom token screen */
internal sealed interface AddCustomTokenSelectorField {
- /** Selection availability */
- val isEnabled: Boolean
-
- /** Label string resource id */
+ /** Label */
val label: TextReference
/** Selected menu item */
@@ -208,35 +194,34 @@ internal sealed interface AddCustomTokenSelectorField {
/**
* Network selector model
*
+ * @property label label
* @property selectedItem selected menu item
* @property items menu items
* @property onMenuItemClick lambda be invoked when menu item is been selected
*/
data class Network(
+ override val label: TextReference,
override val selectedItem: SelectorItem.Title,
override val items: List,
override val onMenuItemClick: (Int) -> Unit,
- ) : AddCustomTokenSelectorField {
- override val isEnabled = true
- override val label = TextReference.Res(R.string.custom_token_network_input_title)
- }
+ ) : AddCustomTokenSelectorField
/**
* Derivation path selector model
*
- * @property isEnabled selection availability
+ * @property label label
* @property selectedItem selected menu item
* @property items menu items
* @property onMenuItemClick lambda be invoked when menu item is been selected
+ * @property isEnabled selection availability
*/
data class DerivationPath(
- override val isEnabled: Boolean,
+ override val label: TextReference,
override val selectedItem: SelectorItem.TitleWithSubtitle,
override val items: List,
override val onMenuItemClick: (Int) -> Unit,
- ) : AddCustomTokenSelectorField {
- override val label = TextReference.Res(R.string.custom_token_derivation_path_input_title)
- }
+ val isEnabled: Boolean,
+ ) : AddCustomTokenSelectorField
/** Base menu item model */
sealed interface SelectorItem {
@@ -259,17 +244,40 @@ internal sealed interface AddCustomTokenSelectorField {
* Menu item with title ans subtitle
*
* @property title title text
- * @property subtitle subtitle text
* @property blockchain blockchain
+ * @property subtitle subtitle text
*/
data class TitleWithSubtitle(
override val title: TextReference,
- val subtitle: TextReference,
override val blockchain: Blockchain,
+ val subtitle: TextReference,
) : SelectorItem
}
}
+/**
+ * Warning model of add custom token screen
+ *
+ * @property description warning description
+ */
+internal sealed class AddCustomTokenWarning(val description: TextReference) {
+
+ /** Potential scam warning */
+ object PotentialScamToken : AddCustomTokenWarning(
+ description = TextReference.Res(R.string.custom_token_validation_error_not_found),
+ )
+
+ /** Token already added warning */
+ object TokenAlreadyAdded : AddCustomTokenWarning(
+ description = TextReference.Res(R.string.custom_token_validation_error_already_added),
+ )
+
+ /** Unsupported Solana token warning */
+ object UnsupportedSolanaToken : AddCustomTokenWarning(
+ description = TextReference.Res(R.string.alert_manage_tokens_unsupported_message),
+ )
+}
+
/**
* Floating button of add custom token screen
*
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/models/CustomTokenType.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/models/CustomTokenType.kt
new file mode 100644
index 0000000000..b36fe58ce3
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/models/CustomTokenType.kt
@@ -0,0 +1,4 @@
+package com.tangem.tap.features.customtoken.impl.presentation.models
+
+/** Custom token type */
+enum class CustomTokenType { TOKEN, BLOCKCHAIN }
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/routers/CustomTokenRouter.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/routers/CustomTokenRouter.kt
index 933bda4bc7..bcd32893fe 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/routers/CustomTokenRouter.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/routers/CustomTokenRouter.kt
@@ -9,4 +9,7 @@ internal interface CustomTokenRouter {
/** Return to last screen */
fun popBackStack()
+
+ /** Open wallet (main) screen */
+ fun openWalletScreen()
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/routers/DefaultCustomTokenRouter.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/routers/DefaultCustomTokenRouter.kt
index 9460879f92..25eb163e9b 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/routers/DefaultCustomTokenRouter.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/routers/DefaultCustomTokenRouter.kt
@@ -1,5 +1,6 @@
package com.tangem.tap.features.customtoken.impl.presentation.routers
+import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.store
@@ -9,4 +10,8 @@ internal class DefaultCustomTokenRouter : CustomTokenRouter {
override fun popBackStack() {
store.dispatch(NavigationAction.PopBackTo())
}
+
+ override fun openWalletScreen() {
+ store.dispatch(NavigationAction.PopBackTo(screen = AppScreen.Wallet))
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/states/AddCustomTokenStateHolder.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/states/AddCustomTokenStateHolder.kt
index 17b0f92c9c..32eba291ef 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/states/AddCustomTokenStateHolder.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/states/AddCustomTokenStateHolder.kt
@@ -4,8 +4,8 @@ import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTok
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenFloatingButton
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenForm
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenTestBlock
+import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenWarning
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokensToolbar
-import com.tangem.tap.features.details.ui.cardsettings.TextReference
/**
* State holder of add custom token screen
@@ -24,7 +24,7 @@ internal sealed interface AddCustomTokenStateHolder {
val form: AddCustomTokenForm
/** Warnings */
- val warnings: List
+ val warnings: Set
/** Floating button model */
val floatingButton: AddCustomTokenFloatingButton
@@ -42,7 +42,7 @@ internal sealed interface AddCustomTokenStateHolder {
onBackButtonClick: () -> Unit = this.onBackButtonClick,
toolbar: AddCustomTokensToolbar = this.toolbar,
form: AddCustomTokenForm = this.form,
- warnings: List = this.warnings,
+ warnings: Set = this.warnings,
floatingButton: AddCustomTokenFloatingButton = this.floatingButton,
): AddCustomTokenStateHolder {
return when (this) {
@@ -64,7 +64,7 @@ internal sealed interface AddCustomTokenStateHolder {
override val onBackButtonClick: () -> Unit,
override val toolbar: AddCustomTokensToolbar,
override val form: AddCustomTokenForm,
- override val warnings: List,
+ override val warnings: Set,
override val floatingButton: AddCustomTokenFloatingButton,
) : AddCustomTokenStateHolder
@@ -83,7 +83,7 @@ internal sealed interface AddCustomTokenStateHolder {
override val onBackButtonClick: () -> Unit,
override val toolbar: AddCustomTokensToolbar,
override val form: AddCustomTokenForm,
- override val warnings: List,
+ override val warnings: Set,
override val floatingButton: AddCustomTokenFloatingButton,
val testBlock: AddCustomTokenTestBlock,
val bottomSheet: AddCustomTokenChooseTokenBottomSheet,
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenContent.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenContent.kt
index 7b746c3ec4..e57d82cc15 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenContent.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenContent.kt
@@ -9,22 +9,21 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material.FabPosition
import androidx.compose.material.Scaffold
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.key
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.tooling.preview.Preview
-import com.tangem.blockchain.common.Blockchain
+import androidx.compose.ui.unit.dp
import com.tangem.core.ui.res.TangemTheme
-import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenFloatingButton
-import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenInputField
-import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenSelectorField
-import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokensToolbar
import com.tangem.tap.features.customtoken.impl.presentation.states.AddCustomTokenStateHolder
import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenFloatingButton
import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenForm
import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenToolbar
-import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenWarning
-import com.tangem.tap.features.details.ui.cardsettings.TextReference
-import com.tangem.wallet.R
+import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenWarnings
/**
* Add custom token content
@@ -37,6 +36,7 @@ import com.tangem.wallet.R
internal fun AddCustomTokenContent(state: AddCustomTokenStateHolder.Content) {
BackHandler(onBack = state.onBackButtonClick)
+ var floatingButtonHeight by remember { mutableStateOf(0.dp) }
Scaffold(
topBar = {
AddCustomTokenToolbar(
@@ -44,78 +44,36 @@ internal fun AddCustomTokenContent(state: AddCustomTokenStateHolder.Content) {
onBackButtonClick = state.toolbar.onBackButtonClick,
)
},
- floatingActionButton = { AddCustomTokenFloatingButton(model = state.floatingButton) },
+ floatingActionButton = {
+ val density = LocalDensity.current
+ val verticalPadding = TangemTheme.dimens.spacing32
+ AddCustomTokenFloatingButton(
+ model = state.floatingButton,
+ modifier = Modifier.onSizeChanged {
+ floatingButtonHeight = with(density) { it.height.toDp() + verticalPadding }
+ },
+ )
+ },
floatingActionButtonPosition = FabPosition.Center,
) {
Column(
modifier = Modifier
+ .verticalScroll(rememberScrollState())
.padding(paddingValues = it)
- .fillMaxSize()
- .verticalScroll(rememberScrollState()),
+ .padding(bottom = floatingButtonHeight)
+ .fillMaxSize(),
) {
AddCustomTokenForm(model = state.form)
- state.warnings.forEach { description ->
- key(description) {
- AddCustomTokenWarning(description)
- }
- }
+ AddCustomTokenWarnings(warnings = state.warnings)
}
}
}
-@Preview(showSystemUi = true)
+@Preview
@Composable
private fun Preview_AddCustomTokenContent() {
TangemTheme {
- AddCustomTokenContent(
- state = AddCustomTokenStateHolder.Content(
- onBackButtonClick = {},
- toolbar = AddCustomTokensToolbar(
- title = TextReference.Res(R.string.add_custom_token_title),
- onBackButtonClick = {},
- ),
- form = com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenForm(
- contractAddressInputField = AddCustomTokenInputField.ContactAddress(
- value = "",
- onValueChange = {},
- isError = false,
- isLoading = false,
- ),
- networkSelectorField = AddCustomTokenSelectorField.Network(
- selectedItem = AddCustomTokenSelectorField.SelectorItem.Title(
- title = TextReference.Str("Avalanche"),
- blockchain = Blockchain.Avalanche,
- ),
- items = listOf(),
- onMenuItemClick = {},
- ),
- tokenNameInputField = AddCustomTokenInputField.TokenName(
- value = "",
- onValueChange = {},
- isEnabled = false,
- isError = false,
- ),
- tokenSymbolInputField = AddCustomTokenInputField.TokenSymbol(
- value = "",
- onValueChange = {},
- isEnabled = false,
- isError = false,
- ),
- decimalsInputField = AddCustomTokenInputField.Decimals(
- value = "",
- onValueChange = {},
- isEnabled = false,
- isError = false,
- ),
- derivationPathSelectorField = null,
- ),
- warnings = listOf(),
- floatingButton = AddCustomTokenFloatingButton(
- isEnabled = false,
- onClick = {},
- ),
- ),
- )
+ AddCustomTokenContent(state = AddCustomTokenPreviewData.createContent())
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenPreviewData.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenPreviewData.kt
new file mode 100644
index 0000000000..e463c90ae9
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenPreviewData.kt
@@ -0,0 +1,124 @@
+package com.tangem.tap.features.customtoken.impl.presentation.ui
+
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.KeyboardType
+import com.tangem.blockchain.common.Blockchain
+import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenChooseTokenBottomSheet
+import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenFloatingButton
+import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenForm
+import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenInputField
+import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenSelectorField
+import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenTestBlock
+import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenWarning
+import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokensToolbar
+import com.tangem.tap.features.customtoken.impl.presentation.states.AddCustomTokenStateHolder
+import com.tangem.tap.features.details.ui.cardsettings.TextReference
+import com.tangem.wallet.R
+
+/**
+[REDACTED_AUTHOR]
+ */
+internal object AddCustomTokenPreviewData {
+
+ fun createWarnings(): Set {
+ return setOf(
+ AddCustomTokenWarning.PotentialScamToken,
+ AddCustomTokenWarning.TokenAlreadyAdded,
+ AddCustomTokenWarning.UnsupportedSolanaToken,
+ )
+ }
+
+ fun createDefaultForm(): AddCustomTokenForm {
+ return AddCustomTokenForm(
+ contractAddressInputField = AddCustomTokenInputField.ContactAddress(
+ value = "",
+ onValueChange = {},
+ keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
+ label = TextReference.Res(R.string.custom_token_contract_address_input_title),
+ placeholder = TextReference.Str(value = "0x0000000000000000000000000000000000000000"),
+ isLoading = false,
+ isError = false,
+ error = null,
+ ),
+ networkSelectorField = AddCustomTokenSelectorField.Network(
+ label = TextReference.Res(R.string.custom_token_network_input_title),
+ selectedItem = AddCustomTokenSelectorField.SelectorItem.Title(
+ title = TextReference.Res(R.string.custom_token_network_input_not_selected),
+ blockchain = Blockchain.Unknown,
+ ),
+ items = emptyList(),
+ onMenuItemClick = {},
+ ),
+ tokenNameInputField = AddCustomTokenInputField.TokenName(
+ value = "",
+ onValueChange = {},
+ keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
+ label = TextReference.Res(R.string.custom_token_name_input_title),
+ placeholder = TextReference.Res(id = R.string.custom_token_name_input_placeholder),
+ isEnabled = false,
+ ),
+ tokenSymbolInputField = AddCustomTokenInputField.TokenSymbol(
+ value = "",
+ onValueChange = {},
+ keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
+ label = TextReference.Res(R.string.custom_token_token_symbol_input_title),
+ placeholder = TextReference.Res(id = R.string.custom_token_token_symbol_input_placeholder),
+ isEnabled = false,
+ ),
+ decimalsInputField = AddCustomTokenInputField.Decimals(
+ value = "",
+ onValueChange = {},
+ keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Next),
+ label = TextReference.Res(R.string.custom_token_decimals_input_title),
+ placeholder = TextReference.Str(value = "8"),
+ isEnabled = false,
+ ),
+ derivationPathSelectorField = AddCustomTokenSelectorField.DerivationPath(
+ label = TextReference.Res(R.string.custom_token_derivation_path_input_title),
+ selectedItem = AddCustomTokenSelectorField.SelectorItem.TitleWithSubtitle(
+ title = TextReference.Res(R.string.custom_token_derivation_path_default),
+ subtitle = TextReference.Res(R.string.custom_token_derivation_path_default),
+ blockchain = Blockchain.Unknown,
+ ),
+ items = emptyList(),
+ onMenuItemClick = {},
+ isEnabled = true,
+ ),
+ )
+ }
+
+ fun createTestContent(): AddCustomTokenStateHolder.TestContent {
+ return AddCustomTokenStateHolder.TestContent(
+ onBackButtonClick = {},
+ toolbar = AddCustomTokensToolbar(
+ title = TextReference.Res(R.string.add_custom_token_title),
+ onBackButtonClick = {},
+ ),
+ form = createDefaultForm(),
+ warnings = createWarnings(),
+ floatingButton = AddCustomTokenFloatingButton(isEnabled = false, onClick = {}),
+ testBlock = AddCustomTokenTestBlock(
+ chooseTokenButtonText = "Choose token",
+ clearButtonText = "Clear address",
+ resetButtonText = "Reset",
+ onClearAddressButtonClick = {},
+ onResetButtonClick = {},
+ ),
+ bottomSheet = AddCustomTokenChooseTokenBottomSheet(categoriesBlocks = emptyList(), onTestTokenClick = {}),
+ )
+ }
+
+ fun createContent(): AddCustomTokenStateHolder.Content {
+ return AddCustomTokenStateHolder.Content(
+ onBackButtonClick = {},
+ toolbar = AddCustomTokensToolbar(
+ title = TextReference.Res(R.string.add_custom_token_title),
+ onBackButtonClick = {},
+ ),
+ form = createDefaultForm(),
+ warnings = createWarnings(),
+ floatingButton = AddCustomTokenFloatingButton(isEnabled = false, onClick = {}),
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenScreen.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenScreen.kt
index 82f284fb98..71d079e899 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenScreen.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenScreen.kt
@@ -1,6 +1,10 @@
package com.tangem.tap.features.customtoken.impl.presentation.ui
import androidx.compose.runtime.Composable
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.tooling.preview.PreviewParameter
+import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
+import com.tangem.core.ui.res.TangemTheme
import com.tangem.tap.features.customtoken.impl.presentation.states.AddCustomTokenStateHolder
/**
@@ -10,11 +14,27 @@ import com.tangem.tap.features.customtoken.impl.presentation.states.AddCustomTok
*
[REDACTED_AUTHOR]
*/
-@Suppress("UnusedPrivateMember")
@Composable
internal fun AddCustomTokenScreen(stateHolder: AddCustomTokenStateHolder) {
when (stateHolder) {
is AddCustomTokenStateHolder.Content -> AddCustomTokenContent(state = stateHolder)
is AddCustomTokenStateHolder.TestContent -> AddCustomTokenTestContent(state = stateHolder)
}
-}
\ No newline at end of file
+}
+
+@Preview(showSystemUi = true)
+@Composable
+private fun Preview_AddCustomTokenScreen(
+ @PreviewParameter(AddCustomTokenScreenProvider::class) stateHolder: AddCustomTokenStateHolder,
+) {
+ TangemTheme {
+ AddCustomTokenScreen(stateHolder)
+ }
+}
+
+private class AddCustomTokenScreenProvider : CollectionPreviewParameterProvider(
+ collection = listOf(
+ AddCustomTokenPreviewData.createContent(),
+ AddCustomTokenPreviewData.createTestContent(),
+ ),
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenTestContent.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenTestContent.kt
index dce924a655..c23fa86835 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenTestContent.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/AddCustomTokenTestContent.kt
@@ -4,6 +4,7 @@ import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
@@ -19,33 +20,33 @@ import androidx.compose.material.FabPosition
import androidx.compose.material.Text
import androidx.compose.material.rememberBottomSheetScaffoldState
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import com.tangem.blockchain.common.Blockchain
import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.SpacerH8
import com.tangem.core.ui.components.atoms.Hand
import com.tangem.core.ui.res.TangemTheme
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenChooseTokenBottomSheet
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenChooseTokenBottomSheet.TestTokenItem
-import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenFloatingButton
-import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenInputField
-import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenSelectorField
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenTestBlock
-import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokensToolbar
import com.tangem.tap.features.customtoken.impl.presentation.states.AddCustomTokenStateHolder
import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenFloatingButton
import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenForm
import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenToolbar
-import com.tangem.tap.features.details.ui.cardsettings.TextReference
-import com.tangem.wallet.R
+import com.tangem.tap.features.customtoken.impl.presentation.ui.components.AddCustomTokenWarnings
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@@ -74,6 +75,7 @@ internal fun AddCustomTokenTestContent(state: AddCustomTokenStateHolder.TestCont
},
)
+ var floatingButtonHeight by remember { mutableStateOf(0.dp) }
BottomSheetScaffold(
sheetContent = {
SheetContent(
@@ -95,7 +97,16 @@ internal fun AddCustomTokenTestContent(state: AddCustomTokenStateHolder.TestCont
},
)
},
- floatingActionButton = { AddCustomTokenFloatingButton(model = state.floatingButton) },
+ floatingActionButton = {
+ val density = LocalDensity.current
+ val verticalPadding = TangemTheme.dimens.spacing32
+ AddCustomTokenFloatingButton(
+ model = state.floatingButton,
+ modifier = Modifier.onSizeChanged {
+ floatingButtonHeight = with(density) { it.height.toDp() + verticalPadding }
+ },
+ )
+ },
floatingActionButtonPosition = FabPosition.Center,
sheetBackgroundColor = TangemTheme.colors.background.secondary,
sheetPeekHeight = TangemTheme.dimens.size0,
@@ -104,15 +115,19 @@ internal fun AddCustomTokenTestContent(state: AddCustomTokenStateHolder.TestCont
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
- .padding(it),
+ .padding(paddingValues = it)
+ .padding(bottom = floatingButtonHeight)
+ .fillMaxSize(),
) {
TestBlock(
- model = state.testBlock,
+ state.testBlock,
coroutineScope,
bottomSheetScaffoldState,
)
AddCustomTokenForm(model = state.form)
+
+ AddCustomTokenWarnings(warnings = state.warnings)
}
}
}
@@ -243,69 +258,10 @@ private fun TestBlock(
}
}
-@Preview(showSystemUi = true)
+@Preview
@Composable
private fun Preview_AddCustomTokenTestContent() {
TangemTheme {
- AddCustomTokenTestContent(
- state = AddCustomTokenStateHolder.TestContent(
- onBackButtonClick = {},
- toolbar = AddCustomTokensToolbar(
- title = TextReference.Res(R.string.add_custom_token_title),
- onBackButtonClick = {},
- ),
- form = com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenForm(
- contractAddressInputField = AddCustomTokenInputField.ContactAddress(
- value = "",
- onValueChange = {},
- isError = false,
- isLoading = false,
- ),
- networkSelectorField = AddCustomTokenSelectorField.Network(
- selectedItem = AddCustomTokenSelectorField.SelectorItem.Title(
- title = TextReference.Str(value = "Avalanche"),
- blockchain = Blockchain.Avalanche,
- ),
- items = listOf(),
- onMenuItemClick = {},
- ),
- tokenNameInputField = AddCustomTokenInputField.TokenName(
- value = "",
- onValueChange = {},
- isEnabled = false,
- isError = false,
- ),
- tokenSymbolInputField = AddCustomTokenInputField.TokenSymbol(
- value = "",
- onValueChange = {},
- isEnabled = false,
- isError = false,
- ),
- decimalsInputField = AddCustomTokenInputField.Decimals(
- value = "",
- onValueChange = {},
- isEnabled = false,
- isError = false,
- ),
- derivationPathSelectorField = null,
- ),
- warnings = listOf(),
- floatingButton = AddCustomTokenFloatingButton(
- isEnabled = false,
- onClick = {},
- ),
- testBlock = AddCustomTokenTestBlock(
- chooseTokenButtonText = "Choose token",
- clearButtonText = "Clear address",
- resetButtonText = "Reset",
- onClearAddressButtonClick = {},
- onResetButtonClick = {},
- ),
- bottomSheet = AddCustomTokenChooseTokenBottomSheet(
- categoriesBlocks = listOf(),
- onTestTokenClick = {},
- ),
- ),
- )
+ AddCustomTokenTestContent(state = AddCustomTokenPreviewData.createTestContent())
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenFloatingButton.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenFloatingButton.kt
index 082bfe4939..618a46fd70 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenFloatingButton.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenFloatingButton.kt
@@ -8,6 +8,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.tooling.preview.PreviewParameter
+import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.components.PrimaryButtonIconLeft
import com.tangem.core.ui.res.TangemTheme
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenFloatingButton
@@ -16,14 +18,15 @@ import com.tangem.wallet.R
/**
* Add custom token floating button. Attached above the keyboard.
*
- * @param model button model
+ * @param model button model
+ * @param modifier modifier
*
[REDACTED_AUTHOR]
*/
@Composable
-internal fun AddCustomTokenFloatingButton(model: AddCustomTokenFloatingButton) {
+internal fun AddCustomTokenFloatingButton(model: AddCustomTokenFloatingButton, modifier: Modifier = Modifier) {
PrimaryButtonIconLeft(
- modifier = Modifier
+ modifier = modifier
.imePadding()
.padding(horizontal = TangemTheme.dimens.spacing16)
.fillMaxWidth(),
@@ -36,16 +39,17 @@ internal fun AddCustomTokenFloatingButton(model: AddCustomTokenFloatingButton) {
@Preview
@Composable
-private fun Preview_AddCustomTokenFloatingButton_Enabled() {
+private fun Preview_AddCustomTokenFloatingButton(
+ @PreviewParameter(AddCustomTokenFloatingButtonProvider::class) model: AddCustomTokenFloatingButton,
+) {
TangemTheme {
- AddCustomTokenFloatingButton(model = AddCustomTokenFloatingButton(isEnabled = true, onClick = {}))
+ AddCustomTokenFloatingButton(model)
}
}
-@Preview
-@Composable
-private fun Preview_AddCustomTokenFloatingButton_Disabled() {
- TangemTheme {
- AddCustomTokenFloatingButton(model = AddCustomTokenFloatingButton(isEnabled = false, onClick = {}))
- }
-}
\ No newline at end of file
+private class AddCustomTokenFloatingButtonProvider : CollectionPreviewParameterProvider(
+ listOf(
+ AddCustomTokenFloatingButton(isEnabled = true, onClick = {}),
+ AddCustomTokenFloatingButton(isEnabled = false, onClick = {}),
+ ),
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenForm.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenForm.kt
index 44904ac319..6b2e38d732 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenForm.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenForm.kt
@@ -1,6 +1,10 @@
package com.tangem.tap.features.customtoken.impl.presentation.ui.components
import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.slideInVertically
+import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -14,6 +18,7 @@ import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.ExposedDropdownMenuBox
import androidx.compose.material.ExposedDropdownMenuDefaults
import androidx.compose.material.LinearProgressIndicator
+import androidx.compose.material.MaterialTheme
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
@@ -26,13 +31,14 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
-import com.tangem.blockchain.common.Blockchain
+import androidx.compose.ui.tooling.preview.PreviewParameter
+import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.res.TangemTheme
import com.tangem.tap.common.compose.TangemTextFieldsDefault
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenForm
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenInputField
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenSelectorField
-import com.tangem.tap.features.details.ui.cardsettings.TextReference
+import com.tangem.tap.features.customtoken.impl.presentation.ui.AddCustomTokenPreviewData
import com.tangem.tap.features.details.ui.cardsettings.resolveReference
/**
@@ -68,7 +74,37 @@ internal fun AddCustomTokenForm(model: AddCustomTokenForm) {
@Composable
private fun InputField(model: AddCustomTokenInputField) {
+ Column {
+ val isError = (model as? AddCustomTokenInputField.ContactAddress)?.isError ?: false
+
+ TextField(model, isError)
+
+ (model as? AddCustomTokenInputField.ContactAddress)?.error?.resolveReference()?.let {
+ AnimatedVisibility(
+ visible = isError,
+ enter = fadeIn() + slideInVertically(),
+ exit = slideOutVertically() + fadeOut(),
+ ) {
+ Text(
+ text = it,
+ color = MaterialTheme.colors.error,
+ style = TangemTheme.typography.body2,
+ )
+ }
+ }
+ }
+}
+
+@Composable
+private fun TextField(model: AddCustomTokenInputField, isError: Boolean) {
Box {
+ val isEnabled = when (model) {
+ is AddCustomTokenInputField.ContactAddress -> true
+ is AddCustomTokenInputField.Decimals -> model.isEnabled
+ is AddCustomTokenInputField.TokenName -> model.isEnabled
+ is AddCustomTokenInputField.TokenSymbol -> model.isEnabled
+ }
+
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = model.value,
@@ -79,8 +115,8 @@ private fun InputField(model: AddCustomTokenInputField) {
text = model.label.resolveReference(),
style = TangemTheme.typography.caption,
color = TangemTextFieldsDefault.defaultTextFieldColors.labelColor(
- enabled = model.isEnabled,
- error = model.isError,
+ enabled = isEnabled,
+ error = isError,
interactionSource = remember { MutableInteractionSource() },
).value,
)
@@ -90,20 +126,20 @@ private fun InputField(model: AddCustomTokenInputField) {
text = model.placeholder.resolveReference(),
style = TangemTheme.typography.body1,
color = TangemTextFieldsDefault.defaultTextFieldColors
- .placeholderColor(enabled = model.isEnabled)
+ .placeholderColor(enabled = isEnabled)
.value,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
},
singleLine = true,
- enabled = model.isEnabled,
- isError = model.isError,
+ enabled = isEnabled,
+ isError = isError,
colors = TangemTextFieldsDefault.defaultTextFieldColors,
)
AnimatedVisibility(
- visible = model.isLoading,
+ visible = (model as? AddCustomTokenInputField.ContactAddress)?.isLoading ?: false,
modifier = Modifier
.fillMaxWidth()
.align(Alignment.BottomCenter)
@@ -124,6 +160,7 @@ private fun SelectorField(model: AddCustomTokenSelectorField) {
expanded = isExpanded,
onExpandedChange = { isExpanded = !isExpanded },
) {
+ val isEnabled = (model as? AddCustomTokenSelectorField.DerivationPath)?.isEnabled ?: true
OutlinedTextField(
value = when (val item = model.selectedItem) {
is AddCustomTokenSelectorField.SelectorItem.Title -> item.title
@@ -132,14 +169,14 @@ private fun SelectorField(model: AddCustomTokenSelectorField) {
modifier = Modifier.fillMaxWidth(),
onValueChange = {},
readOnly = true,
- enabled = model.isEnabled,
+ enabled = isEnabled,
label = { Text(text = model.label.resolveReference()) },
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = isExpanded) },
colors = TangemTextFieldsDefault.defaultTextFieldColors,
)
ExposedDropdownMenu(
- expanded = isExpanded && model.isEnabled,
+ expanded = isExpanded && isEnabled,
onDismissRequest = { isExpanded = false },
) {
FocusRequester
@@ -173,44 +210,18 @@ private fun SelectorField(model: AddCustomTokenSelectorField) {
@Preview
@Composable
-private fun Preview_AddCustomTokenForm() {
+private fun Preview_AddCustomTokenForm(@PreviewParameter(AddCustomTokenFormProvider::class) model: AddCustomTokenForm) {
TangemTheme {
- AddCustomTokenForm(
- AddCustomTokenForm(
- contractAddressInputField = AddCustomTokenInputField.ContactAddress(
- value = "",
- onValueChange = {},
- isError = false,
- isLoading = false,
- ),
- networkSelectorField = AddCustomTokenSelectorField.Network(
- selectedItem = AddCustomTokenSelectorField.SelectorItem.Title(
- title = TextReference.Str(value = "Avalanche"),
- blockchain = Blockchain.Avalanche,
- ),
- items = listOf(),
- onMenuItemClick = {},
- ),
- tokenNameInputField = AddCustomTokenInputField.TokenName(
- value = "",
- onValueChange = {},
- isEnabled = false,
- isError = false,
- ),
- tokenSymbolInputField = AddCustomTokenInputField.TokenSymbol(
- value = "",
- onValueChange = {},
- isEnabled = false,
- isError = false,
- ),
- decimalsInputField = AddCustomTokenInputField.Decimals(
- value = "",
- onValueChange = {},
- isEnabled = false,
- isError = false,
- ),
- derivationPathSelectorField = null,
- ),
- )
+ AddCustomTokenForm(model)
}
-}
\ No newline at end of file
+}
+
+private class AddCustomTokenFormProvider : CollectionPreviewParameterProvider(
+ collection = listOf(
+ AddCustomTokenPreviewData.createDefaultForm(),
+ AddCustomTokenPreviewData.createDefaultForm().copy(derivationPathSelectorField = null),
+ AddCustomTokenPreviewData.createDefaultForm().let { form ->
+ form.copy(contractAddressInputField = form.contractAddressInputField.copy(isLoading = true))
+ },
+ ),
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenWarning.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenWarnings.kt
similarity index 51%
rename from app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenWarning.kt
rename to app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenWarnings.kt
index 5eb76c6e66..7627e3c6af 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenWarning.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/ui/components/AddCustomTokenWarnings.kt
@@ -2,39 +2,56 @@ package com.tangem.tap.features.customtoken.impl.presentation.ui.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Card
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.key
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.sp
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.core.ui.res.TangemTheme
-import com.tangem.tap.features.details.ui.cardsettings.TextReference
+import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenWarning
+import com.tangem.tap.features.customtoken.impl.presentation.ui.AddCustomTokenPreviewData
import com.tangem.tap.features.details.ui.cardsettings.resolveReference
import com.tangem.wallet.R
/**
- * Add custom token warning component
- * FIXME("Incorrect typography. Replace with typography from design system")
+ * Add custom token warnings
*
- * @param description warning description
- * @param modifier modifier
+ * @param warnings warnings descriptions set
*
[REDACTED_AUTHOR]
*/
@Composable
-internal fun AddCustomTokenWarning(description: TextReference, modifier: Modifier = Modifier) {
+internal fun AddCustomTokenWarnings(warnings: Set) {
+ Column(
+ modifier = Modifier
+ .padding(horizontal = TangemTheme.dimens.spacing16)
+ .fillMaxWidth(),
+ verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
+ ) {
+ warnings.forEach { warning ->
+ key(warning) { AddCustomTokenWarning(warning) }
+ }
+ }
+}
+
+@Composable
+private fun AddCustomTokenWarning(warning: AddCustomTokenWarning) {
Card(
- modifier = modifier,
- shape = RoundedCornerShape(TangemTheme.dimens.radius4),
+ modifier = Modifier.fillMaxSize(),
+ shape = TangemTheme.shapes.roundedCornersSmall2,
backgroundColor = TangemColorPalette.Tangerine,
contentColor = TangemColorPalette.White,
elevation = TangemTheme.dimens.elevation4,
) {
+ // FIXME("Incorrect typography. Replace with typography from design system")
Column(
modifier = Modifier.padding(all = TangemTheme.dimens.spacing16),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
@@ -45,10 +62,18 @@ internal fun AddCustomTokenWarning(description: TextReference, modifier: Modifie
style = TangemTheme.typography.body2.copy(fontWeight = FontWeight.Bold),
)
Text(
- text = description.resolveReference(),
+ text = warning.description.resolveReference(),
fontSize = 13.sp,
lineHeight = 18.sp,
)
}
}
+}
+
+@Preview
+@Composable
+private fun Preview_AddCustomTokenWarnings() {
+ TangemTheme {
+ AddCustomTokenWarnings(warnings = AddCustomTokenPreviewData.createWarnings())
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenAnalyticsSender.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenAnalyticsSender.kt
new file mode 100644
index 0000000000..e9641c119f
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenAnalyticsSender.kt
@@ -0,0 +1,35 @@
+package com.tangem.tap.features.customtoken.impl.presentation.viewmodels
+
+import com.tangem.core.analytics.api.AnalyticsEventHandler
+import com.tangem.tap.common.analytics.events.ManageTokens
+import com.tangem.tap.features.wallet.models.Currency
+
+/** Analytics sender for tokens list screen */
+class AddCustomTokenAnalyticsSender(private val analyticsEventHandler: AnalyticsEventHandler) {
+
+ fun sendWhenScreenOpened() {
+ analyticsEventHandler.send(ManageTokens.CustomToken.ScreenOpened)
+ }
+
+ fun sendWhenAddTokenButtonClicked(currency: Currency, address: String) {
+ analyticsEventHandler.send(
+ when (currency) {
+ is Currency.Blockchain -> {
+ ManageTokens.CustomToken.TokenWasAdded.Blockchain(
+ derivationPath = currency.derivationPath,
+ blockchain = currency.blockchain,
+ )
+ }
+
+ is Currency.Token -> {
+ ManageTokens.CustomToken.TokenWasAdded.Token(
+ symbol = currency.currencySymbol,
+ derivationPath = currency.derivationPath,
+ blockchain = currency.blockchain,
+ contractAddress = address,
+ )
+ }
+ },
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt
index 22b13b1216..5e2020d8ef 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt
@@ -1,8 +1,11 @@
package com.tangem.tap.features.customtoken.impl.presentation.viewmodels
+import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.KeyboardType
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ViewModel
@@ -14,12 +17,14 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.AddCustomTokenError
import com.tangem.domain.common.TapWorkarounds.derivationStyle
-import com.tangem.domain.common.TapWorkarounds.isTestCard
+import com.tangem.domain.common.extensions.canHandleToken
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.domain.common.extensions.isSupportedInApp
import com.tangem.domain.common.extensions.supportedBlockchains
-import com.tangem.tap.common.analytics.events.ManageTokens
+import com.tangem.domain.common.extensions.toNetworkId
+import com.tangem.tap.domain.model.WalletDataModel
import com.tangem.tap.features.customtoken.impl.domain.CustomTokenInteractor
+import com.tangem.tap.features.customtoken.impl.domain.models.FoundToken
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenChooseTokenBottomSheet
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenChooseTokenBottomSheet.TestTokenItem
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenChooseTokenBottomSheet.TokensCategoryBlock
@@ -27,8 +32,11 @@ import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTok
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenForm
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenInputField
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenSelectorField
+import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenSelectorField.SelectorItem
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenTestBlock
+import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokenWarning
import com.tangem.tap.features.customtoken.impl.presentation.models.AddCustomTokensToolbar
+import com.tangem.tap.features.customtoken.impl.presentation.models.CustomTokenType
import com.tangem.tap.features.customtoken.impl.presentation.routers.CustomTokenRouter
import com.tangem.tap.features.customtoken.impl.presentation.states.AddCustomTokenStateHolder
import com.tangem.tap.features.customtoken.impl.presentation.validators.ContactAddressValidator
@@ -36,6 +44,7 @@ import com.tangem.tap.features.customtoken.impl.presentation.validators.Contract
import com.tangem.tap.features.details.ui.cardsettings.TextReference
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.proxy.AppStateHolder
+import com.tangem.tap.store
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
import com.tangem.utils.coroutines.runCatching
import com.tangem.wallet.BuildConfig
@@ -49,34 +58,37 @@ import javax.inject.Inject
/**
* ViewModel for add custom token screen
*
- * @param featureRouter feature router
- * @property featureInteractor feature interactor
- * @property dispatchers coroutine dispatchers provider
- * @property reduxStateHolder redux state holder
- * @property analyticsEventHandler analytics event handler
+ * @param analyticsEventHandler analytics event handler
+ * @param featureRouter feature router
+ * @property featureInteractor feature interactor
+ * @property dispatchers coroutine dispatchers provider
+ * @property reduxStateHolder redux state holder
*
[REDACTED_AUTHOR]
*/
+@Suppress("LargeClass")
@HiltViewModel
internal class AddCustomTokenViewModel @Inject constructor(
+ analyticsEventHandler: AnalyticsEventHandler,
featureRouter: CustomTokenRouter,
private val featureInteractor: CustomTokenInteractor,
private val dispatchers: AppCoroutineDispatcherProvider,
private val reduxStateHolder: AppStateHolder,
- private val analyticsEventHandler: AnalyticsEventHandler,
) : ViewModel(), DefaultLifecycleObserver {
+ private val analyticsSender = AddCustomTokenAnalyticsSender(analyticsEventHandler)
private val actionsHandler = ActionsHandler(featureRouter)
private val testActionsHandler = TestActionsHandler()
+ private val formStateBuilder = FormStateBuilder()
/** Screen state */
var uiState by mutableStateOf(getInitialUiState())
private set
- private var foundTokenId: String? = null
+ private var foundToken: FoundToken? = null
override fun onCreate(owner: LifecycleOwner) {
- analyticsEventHandler.send(ManageTokens.CustomToken.ScreenOpened)
+ analyticsSender.sendWhenScreenOpened()
}
private fun getInitialUiState(): AddCustomTokenStateHolder {
@@ -84,8 +96,8 @@ internal class AddCustomTokenViewModel @Inject constructor(
AddCustomTokenStateHolder.TestContent(
onBackButtonClick = actionsHandler::onBackButtonClick,
toolbar = createToolbar(),
- form = createForm(),
- warnings = listOf(),
+ form = formStateBuilder.createForm(),
+ warnings = emptySet(),
floatingButton = createFloatingButton(),
testBlock = AddCustomTokenTestBlock(
chooseTokenButtonText = "Choose token",
@@ -106,8 +118,8 @@ internal class AddCustomTokenViewModel @Inject constructor(
AddCustomTokenStateHolder.Content(
onBackButtonClick = actionsHandler::onBackButtonClick,
toolbar = createToolbar(),
- form = createForm(),
- warnings = listOf(),
+ form = formStateBuilder.createForm(),
+ warnings = emptySet(),
floatingButton = createFloatingButton(),
)
}
@@ -120,155 +132,32 @@ internal class AddCustomTokenViewModel @Inject constructor(
)
}
- private fun createForm(): AddCustomTokenForm {
- return AddCustomTokenForm(
- contractAddressInputField = createContractAddressInputField(),
- networkSelectorField = createNetworkSelectorField(),
- tokenNameInputField = createTokenNameInputField(),
- tokenSymbolInputField = createTokenSymbolInputField(),
- decimalsInputField = createDecimalsInputField(),
- derivationPathSelectorField = createDerivationPathsSelectorField(),
- )
+ private fun createFloatingButton(): AddCustomTokenFloatingButton {
+ return AddCustomTokenFloatingButton(isEnabled = false, onClick = actionsHandler::onAddCustomTokenClick)
}
- private fun createContractAddressInputField(): AddCustomTokenInputField.ContactAddress {
- return AddCustomTokenInputField.ContactAddress(
- value = "",
- onValueChange = actionsHandler::onContactAddressValueChange,
- isError = false,
- isLoading = false,
- )
- }
+ private inner class FormStateBuilder {
- private fun createNetworkSelectorField(): AddCustomTokenSelectorField.Network {
- val selectorItems = getNetworkSelectorItems()
- return AddCustomTokenSelectorField.Network(
- selectedItem = requireNotNull(selectorItems.firstOrNull()),
- items = selectorItems,
- onMenuItemClick = {
- actionsHandler.onNetworkSelectorItemClick(
- selectedItem = requireNotNull(selectorItems.getOrNull(it)),
- )
- },
- )
- }
-
- private fun getNetworkSelectorItems(): List {
- val card = reduxStateHolder.scanResponse?.card
- val evmBlockchains = Blockchain.values().filter { card?.isTestCard == it.isTestnet() && it.isEvm() }
-
- val additionalBlockchains = listOf(
- Blockchain.Binance,
- Blockchain.BinanceTestnet,
- Blockchain.Solana,
- Blockchain.SolanaTestnet,
- Blockchain.Tron,
- Blockchain.TronTestnet,
- )
-
- return (evmBlockchains + additionalBlockchains)
- .filter { card?.supportedBlockchains()?.contains(it) == true }
- .map(::createNetworkSelectorItem)
- .toMutableList()
- .apply {
- add(index = 0, element = createNetworkSelectorItem(blockchain = Blockchain.Unknown))
- }
- }
-
- private fun createNetworkSelectorItem(blockchain: Blockchain): AddCustomTokenSelectorField.SelectorItem.Title {
- return when (blockchain) {
- Blockchain.Unknown -> {
- AddCustomTokenSelectorField.SelectorItem.Title(
- title = TextReference.Res(R.string.custom_token_network_input_not_selected),
- blockchain = Blockchain.Unknown,
- )
- }
-
- else -> {
- AddCustomTokenSelectorField.SelectorItem.Title(
- title = TextReference.Str(blockchain.fullName),
- blockchain = blockchain,
- )
- }
- }
- }
-
- private fun createTokenNameInputField(): AddCustomTokenInputField.TokenName {
- return AddCustomTokenInputField.TokenName(
- value = "",
- onValueChange = actionsHandler::onTokenNameValueChange,
- isEnabled = false,
- isError = false,
- )
- }
-
- private fun createTokenSymbolInputField(): AddCustomTokenInputField.TokenSymbol {
- return AddCustomTokenInputField.TokenSymbol(
- value = "",
- onValueChange = actionsHandler::onTokenSymbolValueChange,
- isEnabled = false,
- isError = false,
- )
- }
-
- private fun createDecimalsInputField(): AddCustomTokenInputField.Decimals {
- return AddCustomTokenInputField.Decimals(
- value = "",
- onValueChange = actionsHandler::onDecimalsValueChange,
- isEnabled = false,
- isError = false,
- )
- }
-
- private fun createDerivationPathsSelectorField(): AddCustomTokenSelectorField.DerivationPath? {
- if (reduxStateHolder.scanResponse?.card?.settings?.isHDWalletAllowed == false) return null
-
- val selectorItems = getDerivationPathsSelectorItems()
- return AddCustomTokenSelectorField.DerivationPath(
- isEnabled = true,
- selectedItem = requireNotNull(selectorItems.firstOrNull()),
- items = selectorItems,
- onMenuItemClick = {
- val field = requireNotNull(uiState.form.derivationPathSelectorField)
- uiState = uiState.copySealed(
- form = uiState.form.copy(
- derivationPathSelectorField = field.copy(
- selectedItem = requireNotNull(selectorItems.getOrNull(it)),
- ),
- ),
- )
- },
- )
- }
-
- private fun getDerivationPathsSelectorItems(): List {
- val evmBlockchains = Blockchain.values().filter {
- reduxStateHolder.scanResponse?.card?.isTestCard == it.isTestnet() && it.isEvm() && it.isSupportedInApp()
+ fun createForm(): AddCustomTokenForm {
+ return AddCustomTokenForm(
+ contractAddressInputField = createContractAddressInputField(),
+ networkSelectorField = createNetworkSelectorField(),
+ tokenNameInputField = createTokenNameInputField(),
+ tokenSymbolInputField = createTokenSymbolInputField(),
+ decimalsInputField = createDecimalsInputField(),
+ derivationPathSelectorField = createDerivationPathsSelectorField(),
+ )
}
- return evmBlockchains
- .sortedBy(Blockchain::fullName)
- .map(::createDerivationPathSelectorItem)
- .toMutableList()
- .apply {
- add(index = 0, element = createDerivationPathSelectorItem(Blockchain.Unknown))
- }
- }
-
- private fun createDerivationPathSelectorItem(
- blockchain: Blockchain,
- ): AddCustomTokenSelectorField.SelectorItem.TitleWithSubtitle {
- return when (blockchain) {
- Blockchain.Unknown -> {
- AddCustomTokenSelectorField.SelectorItem.TitleWithSubtitle(
+ fun createDerivationPathSelectorItem(blockchain: Blockchain): SelectorItem.TitleWithSubtitle {
+ return if (blockchain == Blockchain.Unknown) {
+ SelectorItem.TitleWithSubtitle(
title = TextReference.Res(R.string.custom_token_derivation_path_default),
subtitle = TextReference.Res(R.string.custom_token_derivation_path_default),
blockchain = Blockchain.Unknown,
)
- }
-
- else -> {
- AddCustomTokenSelectorField.SelectorItem.TitleWithSubtitle(
+ } else {
+ SelectorItem.TitleWithSubtitle(
title = blockchain.derivationPath(DerivationStyle.LEGACY)?.rawPath?.let(TextReference::Str)
?: TextReference.Res(R.string.custom_token_derivation_path_default),
subtitle = TextReference.Str(blockchain.fullName),
@@ -276,10 +165,396 @@ internal class AddCustomTokenViewModel @Inject constructor(
)
}
}
+
+ fun createNetworkSelectorItem(blockchain: Blockchain): SelectorItem.Title {
+ return if (blockchain == Blockchain.Unknown) {
+ SelectorItem.Title(
+ title = TextReference.Res(R.string.custom_token_network_input_not_selected),
+ blockchain = Blockchain.Unknown,
+ )
+ } else {
+ SelectorItem.Title(
+ title = TextReference.Str(blockchain.fullName),
+ blockchain = blockchain,
+ )
+ }
+ }
+
+ private fun createContractAddressInputField(): AddCustomTokenInputField.ContactAddress {
+ return AddCustomTokenInputField.ContactAddress(
+ value = "",
+ onValueChange = actionsHandler::onContactAddressValueChange,
+ keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
+ label = TextReference.Res(R.string.custom_token_contract_address_input_title),
+ placeholder = TextReference.Str(value = "0x0000000000000000000000000000000000000000"),
+ isLoading = false,
+ isError = false,
+ error = null,
+ )
+ }
+
+ private fun createNetworkSelectorField(): AddCustomTokenSelectorField.Network {
+ val selectorItems = getNetworkSelectorItems()
+ return AddCustomTokenSelectorField.Network(
+ label = TextReference.Res(R.string.custom_token_network_input_title),
+ selectedItem = requireNotNull(selectorItems.firstOrNull()),
+ items = selectorItems,
+ onMenuItemClick = actionsHandler::onNetworkSelectorItemClick,
+ )
+ }
+
+ private fun getNetworkSelectorItems(): List {
+ val defaultNetwork = createNetworkSelectorItem(blockchain = Blockchain.Unknown)
+ return listOf(defaultNetwork) + Blockchain.values()
+ .filter { blockchain ->
+ (blockchain.isEvm() || blockchain.canHandleTokens()) &&
+ reduxStateHolder.scanResponse?.card?.supportedBlockchains()?.contains(blockchain) == true
+ }
+ .map(::createNetworkSelectorItem)
+ }
+
+ private fun createTokenNameInputField(): AddCustomTokenInputField.TokenName {
+ return AddCustomTokenInputField.TokenName(
+ value = "",
+ onValueChange = actionsHandler::onTokenNameValueChange,
+ keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
+ label = TextReference.Res(R.string.custom_token_name_input_title),
+ placeholder = TextReference.Res(id = R.string.custom_token_name_input_placeholder),
+ isEnabled = false,
+ )
+ }
+
+ private fun createTokenSymbolInputField(): AddCustomTokenInputField.TokenSymbol {
+ return AddCustomTokenInputField.TokenSymbol(
+ value = "",
+ onValueChange = actionsHandler::onTokenSymbolValueChange,
+ keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
+ label = TextReference.Res(R.string.custom_token_token_symbol_input_title),
+ placeholder = TextReference.Res(id = R.string.custom_token_token_symbol_input_placeholder),
+ isEnabled = false,
+ )
+ }
+
+ private fun createDecimalsInputField(): AddCustomTokenInputField.Decimals {
+ return AddCustomTokenInputField.Decimals(
+ value = "",
+ onValueChange = actionsHandler::onDecimalsValueChange,
+ keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Next),
+ label = TextReference.Res(R.string.custom_token_decimals_input_title),
+ placeholder = TextReference.Str(value = "8"),
+ isEnabled = false,
+ )
+ }
+
+ private fun createDerivationPathsSelectorField(): AddCustomTokenSelectorField.DerivationPath? {
+ if (reduxStateHolder.scanResponse?.card?.settings?.isHDWalletAllowed == false) return null
+
+ val selectorItems = getDerivationPathsSelectorItems()
+ return AddCustomTokenSelectorField.DerivationPath(
+ label = TextReference.Res(R.string.custom_token_derivation_path_input_title),
+ selectedItem = requireNotNull(selectorItems.firstOrNull()),
+ items = selectorItems,
+ onMenuItemClick = actionsHandler::onDerivationPathSelectorItemClick,
+ isEnabled = true,
+ )
+ }
+
+ private fun getDerivationPathsSelectorItems(): List {
+ val defaultDerivationPath = createDerivationPathSelectorItem(Blockchain.Unknown)
+ return listOf(defaultDerivationPath) + Blockchain.values()
+ .filter { blockchain -> blockchain.isEvm() && blockchain.isSupportedInApp() }
+ .sortedBy(Blockchain::fullName)
+ .map(::createDerivationPathSelectorItem)
+ }
}
- private fun createFloatingButton(): AddCustomTokenFloatingButton {
- return AddCustomTokenFloatingButton(isEnabled = false, onClick = actionsHandler::onAddCustomTokenClick)
+ private fun updateForm(address: String, selectedNetwork: Blockchain) {
+ viewModelScope.launch(dispatchers.main) {
+ runCatching(dispatchers.io) {
+ featureInteractor.findToken(address = address, blockchain = selectedNetwork)
+ }
+ .onSuccess { token ->
+ foundToken = token
+ uiState = uiState.copySealed(
+ form = uiState.form.copy(
+ contractAddressInputField = uiState.form.contractAddressInputField.copy(
+ isLoading = false,
+ ),
+ networkSelectorField = uiState.form.networkSelectorField.copy(
+ selectedItem = formStateBuilder.createNetworkSelectorItem(
+ blockchain = Blockchain.fromNetworkId(token.network.id)
+ ?: Blockchain.Unknown,
+ ),
+ ),
+ tokenNameInputField = uiState.form.tokenNameInputField.copy(
+ value = token.name,
+ isEnabled = false,
+ ),
+ tokenSymbolInputField = uiState.form.tokenSymbolInputField.copy(
+ value = token.symbol,
+ isEnabled = false,
+ ),
+ decimalsInputField = uiState.form.decimalsInputField.copy(
+ value = token.network.decimalCount,
+ isEnabled = false,
+ ),
+ ),
+ )
+ }
+ .onFailure {
+ foundToken = null
+ uiState = uiState.copySealed(
+ form = uiState.form.copy(
+ contractAddressInputField = uiState.form.contractAddressInputField.copy(
+ isLoading = false,
+ ),
+ tokenNameInputField = uiState.form.tokenNameInputField.copy(isEnabled = true),
+ tokenSymbolInputField = uiState.form.tokenSymbolInputField.copy(isEnabled = true),
+ decimalsInputField = uiState.form.decimalsInputField.copy(isEnabled = true),
+ ),
+ )
+ Timber.e(it)
+ }
+
+ updateDerivationPathSelector()
+ updateWarnings()
+ updateFloatingButton()
+ }
+ }
+
+ private fun updateDerivationPathSelector() {
+ val derivationPathSelectorField = uiState.form.derivationPathSelectorField ?: return
+ val selectedValue = derivationPathSelectorField.selectedItem.blockchain
+ val isSupported = selectedValue.isEvm() || !isDerivationPathSelected()
+
+ uiState = uiState.copySealed(
+ form = uiState.form.copy(
+ derivationPathSelectorField = uiState.form.derivationPathSelectorField?.copy(
+ isEnabled = if (derivationPathSelectorField.isEnabled != isSupported) {
+ isSupported
+ } else {
+ derivationPathSelectorField.isEnabled
+ },
+ selectedItem = if (isDerivationPathSelected() && !isSupported) {
+ formStateBuilder.createDerivationPathSelectorItem(Blockchain.Unknown)
+ } else {
+ derivationPathSelectorField.selectedItem
+ },
+ ),
+ ),
+ )
+ }
+
+ private fun isDerivationPathSelected(): Boolean {
+ return uiState.form.derivationPathSelectorField?.selectedItem?.blockchain != Blockchain.Unknown
+ }
+
+ private fun updateWarnings() {
+ uiState = uiState.copySealed(
+ warnings = buildSet {
+ when (getCustomTokenType()) {
+ CustomTokenType.TOKEN -> {
+ addAll(getTokenWarningSet())
+ }
+
+ CustomTokenType.BLOCKCHAIN -> {
+ if (isCustomTokenAlreadyAdded()) add(AddCustomTokenWarning.TokenAlreadyAdded)
+ if (isDerivationPathSelected()) add(AddCustomTokenWarning.PotentialScamToken)
+ }
+ }
+ },
+ )
+ }
+
+ private fun getCustomTokenType(): CustomTokenType {
+ return if (isAnyTokenFieldsFilled() || isAllTokenFieldsFilled()) {
+ CustomTokenType.TOKEN
+ } else {
+ CustomTokenType.BLOCKCHAIN
+ }
+ }
+
+ private fun isAnyTokenFieldsFilled(): Boolean {
+ return with(uiState.form) {
+ contractAddressInputField.value.isNotEmpty() || tokenNameInputField.value.isNotEmpty() ||
+ tokenSymbolInputField.value.isNotEmpty() || decimalsInputField.value.isNotEmpty()
+ }
+ }
+
+ private fun isAllTokenFieldsFilled(): Boolean {
+ return with(uiState.form) {
+ contractAddressInputField.value.isNotEmpty() && tokenNameInputField.value.isNotEmpty() &&
+ tokenSymbolInputField.value.isNotEmpty() && decimalsInputField.value.isNotEmpty()
+ }
+ }
+
+ private fun getTokenWarningSet(): Set {
+ val networkSelectorValue = uiState.form.networkSelectorField.selectedItem.blockchain
+
+ val isContractAddressFieldEmpty = ContactAddressValidator.validate(
+ address = uiState.form.contractAddressInputField.value,
+ blockchain = networkSelectorValue,
+ ).let {
+ it is ContractAddressValidatorResult.Error && it.type == AddCustomTokenError.FieldIsEmpty
+ }
+
+ val isSupportedToken = if (!isNetworkSelected()) {
+ true
+ } else {
+ reduxStateHolder.scanResponse?.card?.canHandleToken(networkSelectorValue) ?: false
+ }
+
+ return buildSet {
+ if (!isSupportedToken && !isContractAddressFieldEmpty) add(AddCustomTokenWarning.UnsupportedSolanaToken)
+ if (isCustomTokenAlreadyAdded()) add(AddCustomTokenWarning.TokenAlreadyAdded)
+ if (foundToken == null && isAnyTokenFieldsFilled() || foundToken?.isActive == false) {
+ add(AddCustomTokenWarning.PotentialScamToken)
+ }
+ }
+ }
+
+ private fun isNetworkSelected(): Boolean {
+ return uiState.form.networkSelectorField.selectedItem.blockchain != Blockchain.Unknown
+ }
+
+ private fun updateFloatingButton() {
+ if (isCustomTokenAlreadyAdded()) {
+ uiState = uiState.copySealed(
+ warnings = uiState.warnings + AddCustomTokenWarning.TokenAlreadyAdded,
+ floatingButton = uiState.floatingButton.copy(isEnabled = false),
+ )
+ return
+ }
+
+ val state = when {
+ isAllTokenFieldsFilled() && isNetworkSelected() -> {
+ val networkSelectorValue = uiState.form.networkSelectorField.selectedItem.blockchain
+ val error = ContactAddressValidator.validate(
+ address = uiState.form.contractAddressInputField.value,
+ blockchain = networkSelectorValue,
+ )
+ val isSupportedToken = reduxStateHolder.scanResponse?.card
+ ?.canHandleToken(networkSelectorValue)
+ ?: false
+
+ uiState.copySealed(
+ floatingButton = uiState.floatingButton.copy(
+ isEnabled = error is ContractAddressValidatorResult.Success && isSupportedToken,
+ ),
+ )
+ }
+
+ isAnyTokenFieldsFilled() -> {
+ uiState.copySealed(floatingButton = uiState.floatingButton.copy(isEnabled = false))
+ }
+
+ else -> {
+ uiState.copySealed(
+ floatingButton = uiState.floatingButton.copy(
+ isEnabled = if (isNetworkSelected()) !isBlockchainAlreadyAdded() else false,
+ ),
+ )
+ }
+ }
+
+ uiState = state.copySealed(
+ warnings = uiState.warnings - AddCustomTokenWarning.TokenAlreadyAdded,
+ )
+ }
+
+ private fun isCustomTokenAlreadyAdded(): Boolean {
+ return when (getCustomTokenType()) {
+ CustomTokenType.TOKEN -> isTokenAlreadyAdded()
+ CustomTokenType.BLOCKCHAIN -> isBlockchainAlreadyAdded()
+ }
+ }
+
+ private fun isTokenAlreadyAdded(): Boolean {
+ return store.state.walletState.walletsStores
+ .map { walletStore -> walletStore.walletsData.map(WalletDataModel::currency) }
+ .flatten()
+ .filterIsInstance()
+ .any { wrappedCurrency ->
+ val contractAddress = uiState.form.contractAddressInputField.value
+ val networkSelectorValue = uiState.form.networkSelectorField.selectedItem.blockchain
+ val sameId = foundToken?.id == wrappedCurrency.token.id
+ val sameAddress = contractAddress == wrappedCurrency.token.contractAddress
+ val sameBlockchain =
+ Blockchain.fromNetworkId(networkSelectorValue.toNetworkId()) == wrappedCurrency.blockchain
+ val isSameDerivationPath = getDerivationPath()?.rawPath == wrappedCurrency.derivationPath
+ sameId && sameAddress && sameBlockchain && isSameDerivationPath
+ }
+ }
+
+ private fun isBlockchainAlreadyAdded(): Boolean {
+ return store.state.walletState.walletsStores
+ .map { walletStore -> walletStore.walletsData.map(WalletDataModel::currency) }
+ .flatten()
+ .filterIsInstance()
+ .any {
+ val networkSelectorValue = uiState.form.networkSelectorField.selectedItem.blockchain
+ networkSelectorValue == it.blockchain && getDerivationPath()?.rawPath == it.derivationPath
+ }
+ }
+
+ private fun handleContractAddressErrorValidation(type: AddCustomTokenError) {
+ when {
+ isNetworkSelected() && type == AddCustomTokenError.InvalidContractAddress -> {
+ val isAnotherTokenFieldsFilled = isAnyTokenFieldsFilled()
+ uiState = uiState.copySealed(
+ form = uiState.form.copy(
+ contractAddressInputField = uiState.form.contractAddressInputField.copy(
+ isError = true,
+ error = TextReference.Res(
+ id = R.string.custom_token_creation_error_invalid_contract_address,
+ ),
+ ),
+ tokenNameInputField = uiState.form.tokenNameInputField.copy(
+ isEnabled = isAnotherTokenFieldsFilled,
+ ),
+ tokenSymbolInputField = uiState.form.tokenSymbolInputField.copy(
+ isEnabled = isAnotherTokenFieldsFilled,
+ ),
+ decimalsInputField = uiState.form.decimalsInputField.copy(
+ isEnabled = isAnotherTokenFieldsFilled,
+ ),
+ ),
+ )
+ }
+
+ !isNetworkSelected() || type == AddCustomTokenError.FieldIsEmpty -> {
+ uiState = uiState.copySealed(
+ form = uiState.form.copy(
+ contractAddressInputField = uiState.form.contractAddressInputField.copy(isError = false),
+ tokenNameInputField = uiState.form.tokenNameInputField.copy(value = "", isEnabled = false),
+ tokenSymbolInputField = uiState.form.tokenSymbolInputField.copy(
+ value = "",
+ isEnabled = false,
+ ),
+ decimalsInputField = uiState.form.decimalsInputField.copy(value = "", isEnabled = false),
+ ),
+ )
+ }
+
+ else -> Unit
+ }
+ }
+
+ private fun getDerivationPath(): DerivationPath? {
+ val isNotDerivationPathSelected = !isDerivationPathSelected()
+ val network = if (isNotDerivationPathSelected) {
+ uiState.form.networkSelectorField.selectedItem.blockchain
+ } else {
+ uiState.form.derivationPathSelectorField?.selectedItem?.blockchain
+ }
+
+ return network?.derivationPath(
+ style = if (isNotDerivationPathSelected) {
+ reduxStateHolder.scanResponse?.card?.derivationStyle
+ } else {
+ DerivationStyle.LEGACY
+ },
+ )
}
private inner class ActionsHandler(private val featureRouter: CustomTokenRouter) {
@@ -288,85 +563,47 @@ internal class AddCustomTokenViewModel @Inject constructor(
featureRouter.popBackStack()
}
- fun onAddCustomTokenClick() {
- if (uiState.form.networkSelectorField.selectedItem.blockchain != Blockchain.Unknown) {
- val selectedNetwork = uiState.form.networkSelectorField.selectedItem.blockchain
-
- val currency = if (isAnyTokenFieldsFilled() || isAllTokenFieldsFilled()) {
- Currency.Token(
- token = Token(
- name = uiState.form.tokenNameInputField.value,
- symbol = uiState.form.tokenSymbolInputField.value,
- contractAddress = uiState.form.contractAddressInputField.value,
- decimals = uiState.form.decimalsInputField.value.toInt(),
- id = foundTokenId,
- ),
- blockchain = selectedNetwork,
- derivationPath = getDerivationPath(
- mainNetwork = selectedNetwork,
- derivationNetwork = uiState.form.derivationPathSelectorField?.selectedItem?.blockchain,
- derivationStyle = reduxStateHolder.scanResponse?.card?.derivationStyle,
- )?.rawPath,
- )
- } else {
- Currency.Blockchain(
- blockchain = selectedNetwork,
- derivationPath = getDerivationPath(
- mainNetwork = selectedNetwork,
- derivationNetwork = uiState.form.derivationPathSelectorField?.selectedItem?.blockchain,
- derivationStyle = reduxStateHolder.scanResponse?.card?.derivationStyle,
- )?.rawPath,
- )
- }
-
- sendOnAddTokenButtonClick(currency = currency, address = uiState.form.contractAddressInputField.value)
-
- viewModelScope.launch(dispatchers.io) {
- featureInteractor.saveToken(
- currency = currency,
- address = uiState.form.contractAddressInputField.value,
- )
- }
- }
- }
-
fun onContactAddressValueChange(enteredValue: String) {
- with(uiState.form) {
- val selectedNetwork = networkSelectorField.selectedItem.blockchain
- val isValid = ContactAddressValidator.validate(
- address = enteredValue,
- blockchain = selectedNetwork,
- )
-
- when (isValid) {
- is ContractAddressValidatorResult.Success -> {
- uiState = uiState.copySealed(
- form = uiState.form.copy(
- contractAddressInputField = contractAddressInputField.copy(
- isError = false,
- isLoading = true,
- ),
- ),
- )
- updateForm(address = enteredValue, selectedNetwork = selectedNetwork)
- }
-
- is ContractAddressValidatorResult.Error -> {
- handleContractAddressErrorValidation(type = isValid.type)
- }
- }
-
- updateDerivationPathSelector()
-
- // TODO("[REDACTED_TASK_KEY] Update warnings")
- // TODO("[REDACTED_TASK_KEY] Update floating button")
- }
- }
-
- fun onNetworkSelectorItemClick(selectedItem: AddCustomTokenSelectorField.SelectorItem.Title) {
uiState = uiState.copySealed(
form = uiState.form.copy(
- networkSelectorField = uiState.form.networkSelectorField.copy(selectedItem = selectedItem),
+ contractAddressInputField = uiState.form.contractAddressInputField.copy(value = enteredValue),
+ ),
+ )
+
+ val selectedNetwork = uiState.form.networkSelectorField.selectedItem.blockchain
+ val validatorResult = ContactAddressValidator.validate(
+ address = enteredValue,
+ blockchain = selectedNetwork,
+ )
+
+ when (validatorResult) {
+ is ContractAddressValidatorResult.Success -> {
+ uiState = uiState.copySealed(
+ form = uiState.form.copy(
+ contractAddressInputField = uiState.form.contractAddressInputField.copy(
+ isError = false,
+ isLoading = true,
+ ),
+ ),
+ )
+ updateForm(address = enteredValue, selectedNetwork = selectedNetwork)
+ }
+
+ is ContractAddressValidatorResult.Error -> {
+ handleContractAddressErrorValidation(type = validatorResult.type)
+ updateDerivationPathSelector()
+ updateWarnings()
+ updateFloatingButton()
+ }
+ }
+ }
+
+ fun onNetworkSelectorItemClick(index: Int) {
+ uiState = uiState.copySealed(
+ form = uiState.form.copy(
+ networkSelectorField = uiState.form.networkSelectorField.copy(
+ selectedItem = requireNotNull(uiState.form.networkSelectorField.items.getOrNull(index)),
+ ),
),
)
onContactAddressValueChange(uiState.form.contractAddressInputField.value)
@@ -378,7 +615,7 @@ internal class AddCustomTokenViewModel @Inject constructor(
tokenNameInputField = uiState.form.tokenNameInputField.copy(value = enteredValue),
),
)
- // TODO("[REDACTED_TASK_KEY] Update floating button")
+ updateFloatingButton()
}
fun onTokenSymbolValueChange(enteredValue: String) {
@@ -387,7 +624,7 @@ internal class AddCustomTokenViewModel @Inject constructor(
tokenSymbolInputField = uiState.form.tokenSymbolInputField.copy(value = enteredValue),
),
)
- // TODO("[REDACTED_TASK_KEY] Update floating button")
+ updateFloatingButton()
}
fun onDecimalsValueChange(enteredValue: String) {
@@ -396,156 +633,53 @@ internal class AddCustomTokenViewModel @Inject constructor(
decimalsInputField = uiState.form.decimalsInputField.copy(value = enteredValue),
),
)
- // TODO("[REDACTED_TASK_KEY] Update floating button")
+ updateFloatingButton()
}
- private fun getDerivationPath(
- mainNetwork: Blockchain,
- derivationNetwork: Blockchain?,
- derivationStyle: DerivationStyle?,
- ): DerivationPath? {
- val network = if (derivationNetwork == Blockchain.Unknown) mainNetwork else derivationNetwork
-
- return network?.derivationPath(
- style = if (derivationNetwork == Blockchain.Unknown) derivationStyle else DerivationStyle.LEGACY,
+ fun onDerivationPathSelectorItemClick(index: Int) {
+ val field = requireNotNull(uiState.form.derivationPathSelectorField)
+ uiState = uiState.copySealed(
+ form = uiState.form.copy(
+ derivationPathSelectorField = field.copy(
+ selectedItem = requireNotNull(field.items.getOrNull(index)),
+ ),
+ ),
)
}
- private fun sendOnAddTokenButtonClick(currency: Currency, address: String) {
- when (currency) {
- is Currency.Blockchain -> {
- analyticsEventHandler.send(
- ManageTokens.CustomToken.TokenWasAdded.Blockchain(
- derivationPath = currency.derivationPath,
- blockchain = currency.blockchain,
- ),
- )
- }
+ fun onAddCustomTokenClick() {
+ if (!isNetworkSelected()) return
- is Currency.Token -> {
- analyticsEventHandler.send(
- ManageTokens.CustomToken.TokenWasAdded.Token(
- symbol = currency.currencySymbol,
- derivationPath = currency.derivationPath,
- blockchain = currency.blockchain,
- contractAddress = address,
- ),
- )
- }
- }
- }
-
- private fun isAnyTokenFieldsFilled(): Boolean {
- return with(uiState.form) {
- contractAddressInputField.value.isNotEmpty() || tokenNameInputField.value.isNotEmpty() ||
- tokenSymbolInputField.value.isNotEmpty() || decimalsInputField.value.isNotEmpty()
- }
- }
-
- private fun isAllTokenFieldsFilled(): Boolean {
- return with(uiState.form) {
- contractAddressInputField.value.isNotEmpty() && tokenNameInputField.value.isNotEmpty() &&
- tokenSymbolInputField.value.isNotEmpty() && decimalsInputField.value.isNotEmpty()
- }
- }
-
- private fun updateForm(address: String, selectedNetwork: Blockchain) {
viewModelScope.launch(dispatchers.main) {
- runCatching(dispatchers.io) {
- featureInteractor.findToken(address = address, blockchain = selectedNetwork)
- }
- .onSuccess { token ->
- with(uiState.form) {
- uiState = uiState.copySealed(
- form = copy(
- contractAddressInputField = contractAddressInputField.copy(isLoading = false),
- networkSelectorField = networkSelectorField.copy(
- selectedItem = createNetworkSelectorItem(
- blockchain = Blockchain.fromNetworkId(token.network.id)
- ?: Blockchain.Unknown,
- ),
- ),
- tokenNameInputField = tokenNameInputField.copy(
- value = token.name,
- isEnabled = false,
- ),
- tokenSymbolInputField = tokenSymbolInputField.copy(
- value = token.symbol,
- isEnabled = false,
- ),
- decimalsInputField = decimalsInputField.copy(
- value = token.network.decimalCount,
- isEnabled = false,
- ),
- ),
- )
- }
- }
- .onFailure {
- foundTokenId = null
- Timber.e(it)
- }
- }
- }
-
- private fun handleContractAddressErrorValidation(type: AddCustomTokenError) {
- with(uiState.form) {
- val isNetworkSelectorFilled = networkSelectorField.selectedItem.blockchain != Blockchain.Unknown
- val isAnotherTokenFieldsFilled = isAnyTokenFieldsFilled()
-
- when {
- isNetworkSelectorFilled && type == AddCustomTokenError.InvalidContractAddress -> {
- // TODO("[REDACTED_TASK_KEY] Add error")
-
- uiState = uiState.copySealed(
- form = uiState.form.copy(
- tokenNameInputField = tokenNameInputField.copy(isEnabled = isAnotherTokenFieldsFilled),
- tokenSymbolInputField = tokenSymbolInputField.copy(
- isEnabled = isAnotherTokenFieldsFilled,
- ),
- decimalsInputField = decimalsInputField.copy(isEnabled = isAnotherTokenFieldsFilled),
+ val address = uiState.form.contractAddressInputField.value
+ val currency = when (getCustomTokenType()) {
+ CustomTokenType.TOKEN -> {
+ Currency.Token(
+ token = Token(
+ name = uiState.form.tokenNameInputField.value,
+ symbol = uiState.form.tokenSymbolInputField.value,
+ contractAddress = address,
+ decimals = requireNotNull(uiState.form.decimalsInputField.value.toIntOrNull()),
+ id = foundToken?.id,
),
+ blockchain = uiState.form.networkSelectorField.selectedItem.blockchain,
+ derivationPath = getDerivationPath()?.rawPath,
)
}
- !isNetworkSelectorFilled || type == AddCustomTokenError.FieldIsEmpty -> {
- uiState = uiState.copySealed(
- form = uiState.form.copy(
- contractAddressInputField = contractAddressInputField.copy(isError = false),
- tokenNameInputField = tokenNameInputField.copy(value = "", isEnabled = false),
- tokenSymbolInputField = tokenSymbolInputField.copy(value = "", isEnabled = false),
- decimalsInputField = decimalsInputField.copy(value = "", isEnabled = false),
- ),
+ CustomTokenType.BLOCKCHAIN -> {
+ Currency.Blockchain(
+ blockchain = uiState.form.networkSelectorField.selectedItem.blockchain,
+ derivationPath = getDerivationPath()?.rawPath,
)
}
-
- else -> Unit
}
- }
- }
- private fun updateDerivationPathSelector() {
- val selectedValue = uiState.form.derivationPathSelectorField?.selectedItem?.blockchain ?: return
- val isSupported = selectedValue.isEvm() || selectedValue == Blockchain.Unknown
+ analyticsSender.sendWhenAddTokenButtonClicked(currency, address)
- if (selectedValue != Blockchain.Unknown && !isSupported) {
- uiState = uiState.copySealed(
- form = uiState.form.copy(
- derivationPathSelectorField = uiState.form.derivationPathSelectorField?.copy(
- selectedItem = createDerivationPathSelectorItem(Blockchain.Unknown),
- ),
- ),
- )
- }
-
- if (uiState.form.derivationPathSelectorField?.isEnabled != isSupported) {
- uiState = uiState.copySealed(
- form = uiState.form.copy(
- derivationPathSelectorField = uiState.form.derivationPathSelectorField?.copy(
- isEnabled = isSupported,
- ),
- ),
- )
+ runCatching(dispatchers.io) { featureInteractor.saveToken(currency, address) }
+ .onSuccess { featureRouter.openWalletScreen() }
+ .onFailure(Timber::e)
}
}
}
@@ -553,31 +687,37 @@ internal class AddCustomTokenViewModel @Inject constructor(
private inner class TestActionsHandler {
fun onClearAddressButtonClick() {
- with(uiState.form) {
- uiState = uiState.copySealed(
- form = copy(
- contractAddressInputField = contractAddressInputField.copy(value = ""),
- tokenNameInputField = tokenNameInputField.copy(value = "", isEnabled = false),
- tokenSymbolInputField = tokenSymbolInputField.copy(value = "", isEnabled = false),
- decimalsInputField = decimalsInputField.copy(value = "", isEnabled = false),
+ uiState = uiState.copySealed(
+ form = uiState.form.copy(
+ contractAddressInputField = uiState.form.contractAddressInputField.copy(
+ value = "",
+ isLoading = false,
+ isError = false,
+ error = null,
),
- )
- }
+ ),
+ )
}
fun onResetButtonClick() {
with(uiState.form) {
uiState = uiState.copySealed(
- form = copy(
- contractAddressInputField = contractAddressInputField.copy(value = ""),
+ form = uiState.form.copy(
+ contractAddressInputField = contractAddressInputField.copy(
+ value = "",
+ isLoading = false,
+ isError = false,
+ error = null,
+ ),
networkSelectorField = networkSelectorField.copy(
- selectedItem = requireNotNull(networkSelectorField.items.firstOrNull()),
+ selectedItem = formStateBuilder.createNetworkSelectorItem(blockchain = Blockchain.Unknown),
),
tokenNameInputField = tokenNameInputField.copy(value = "", isEnabled = false),
tokenSymbolInputField = tokenSymbolInputField.copy(value = "", isEnabled = false),
decimalsInputField = decimalsInputField.copy(value = "", isEnabled = false),
derivationPathSelectorField = derivationPathSelectorField?.copy(
- selectedItem = requireNotNull(derivationPathSelectorField.items.firstOrNull()),
+ isEnabled = true,
+ selectedItem = formStateBuilder.createDerivationPathSelectorItem(Blockchain.Unknown),
),
),
)
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/legacy/compose/AddCustomTokenScreen.kt b/app/src/main/java/com/tangem/tap/features/customtoken/legacy/compose/AddCustomTokenScreen.kt
index 88ba8be37d..3ac2828354 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/legacy/compose/AddCustomTokenScreen.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/legacy/compose/AddCustomTokenScreen.kt
@@ -174,12 +174,14 @@ fun Warnings(warnings: List) {
Column {
warnings.forEachIndexed { index, item ->
val modifier = when (index) {
- 0 -> Modifier.padding(16.dp, 0.dp, 16.dp, 0.dp)
- warnings.lastIndex -> Modifier.padding(16.dp, 8.dp, 16.dp, 16.dp)
- else -> Modifier.padding(16.dp, 8.dp, 16.dp, 0.dp)
+ 0 -> Modifier.padding(vertical = 0.dp)
+ warnings.lastIndex -> Modifier.padding(top = 8.dp, bottom = 16.dp)
+ else -> Modifier.padding(top = 8.dp, bottom = 0.dp)
}
AddCustomTokenWarning(
- modifier = modifier.fillMaxWidth(),
+ modifier = modifier
+ .padding(horizontal = TangemTheme.dimens.spacing16)
+ .fillMaxWidth(),
warning = item,
converter = warningConverter,
)
diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/DisclaimerDataProvider.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/DisclaimerDataProvider.kt
index 0097f34142..9d3b4aeda6 100644
--- a/app/src/main/java/com/tangem/tap/features/disclaimer/DisclaimerDataProvider.kt
+++ b/app/src/main/java/com/tangem/tap/features/disclaimer/DisclaimerDataProvider.kt
@@ -1,6 +1,6 @@
package com.tangem.tap.features.disclaimer
-import com.tangem.tap.persistence.DisclaimerPrefStorage
+import com.tangem.data.source.preferences.storage.DisclaimerPrefStorage
/**
[REDACTED_AUTHOR]
diff --git a/app/src/main/java/com/tangem/tap/features/disclaimer/DisclaimerType.kt b/app/src/main/java/com/tangem/tap/features/disclaimer/DisclaimerType.kt
index 30b3f43d1c..56519de4ff 100644
--- a/app/src/main/java/com/tangem/tap/features/disclaimer/DisclaimerType.kt
+++ b/app/src/main/java/com/tangem/tap/features/disclaimer/DisclaimerType.kt
@@ -1,11 +1,11 @@
package com.tangem.tap.features.disclaimer
-import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.common.TapWorkarounds.isSaltPay
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
-import com.tangem.tap.persistence.DisclaimerPrefStorage
+import com.tangem.domain.models.scan.CardDTO
+import com.tangem.data.source.preferences.storage.DisclaimerPrefStorage
import com.tangem.tap.preferencesStorage
-import java.util.*
+import java.util.Locale
/**
[REDACTED_AUTHOR]
diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt
index e35aa85d7a..bf80a5b458 100644
--- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt
+++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingManager.kt
@@ -15,7 +15,7 @@ import com.tangem.tap.domain.extensions.getOrLoadCardArtworkUrl
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.models.hasPendingTransactions
import com.tangem.tap.features.wallet.redux.ProgressState
-import com.tangem.tap.persistence.UsedCardsPrefStorage
+import com.tangem.data.source.preferences.storage.UsedCardsPrefStorage
import timber.log.Timber
import java.math.BigDecimal
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/di/TokensListRouterModule.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/di/TokensListRouterModule.kt
index 2a7da5cca4..07a7a3412d 100644
--- a/app/src/main/java/com/tangem/tap/features/tokens/impl/di/TokensListRouterModule.kt
+++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/di/TokensListRouterModule.kt
@@ -1,5 +1,6 @@
package com.tangem.tap.features.tokens.impl.di
+import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles
import com.tangem.tap.features.tokens.impl.presentation.router.DefaultTokensListRouter
import com.tangem.tap.features.tokens.impl.presentation.router.TokensListRouter
import dagger.Module
@@ -17,5 +18,7 @@ internal object TokensListRouterModule {
@Provides
@ViewModelScoped
- fun provideTokensListRouter(): TokensListRouter = DefaultTokensListRouter()
+ fun provideTokensListRouter(customTokenFeatureToggles: CustomTokenFeatureToggles): TokensListRouter {
+ return DefaultTokensListRouter(customTokenFeatureToggles)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/router/DefaultTokensListRouter.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/router/DefaultTokensListRouter.kt
index 73e09a433e..cbfe64e505 100644
--- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/router/DefaultTokensListRouter.kt
+++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/router/DefaultTokensListRouter.kt
@@ -3,7 +3,9 @@ package com.tangem.tap.features.tokens.impl.presentation.router
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchNotification
import com.tangem.tap.common.redux.AppDialog
+import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
+import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles
import com.tangem.tap.features.tokens.legacy.redux.TokensAction
import com.tangem.tap.features.wallet.redux.models.WalletDialog
import com.tangem.tap.store
@@ -15,15 +17,20 @@ import com.tangem.wallet.R
*
[REDACTED_AUTHOR]
*/
-internal class DefaultTokensListRouter : TokensListRouter {
+internal class DefaultTokensListRouter(
+ private val customTokenFeatureToggles: CustomTokenFeatureToggles,
+) : TokensListRouter {
override fun popBackStack() {
store.dispatch(NavigationAction.PopBackTo())
- store.dispatch(TokensAction.ResetState)
}
override fun openAddCustomTokenScreen() {
- store.dispatch(TokensAction.PrepareAndNavigateToAddCustomToken)
+ if (customTokenFeatureToggles.isRedesignedScreenEnabled) {
+ store.dispatch(NavigationAction.NavigateTo(AppScreen.AddCustomToken))
+ } else {
+ store.dispatch(TokensAction.PrepareAndNavigateToAddCustomToken)
+ }
}
override fun showAddressCopiedNotification() {
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokenItem.kt b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokenItem.kt
index 9f3c695bb2..b3ee9856f3 100644
--- a/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokenItem.kt
+++ b/app/src/main/java/com/tangem/tap/features/tokens/impl/presentation/ui/TokenItem.kt
@@ -139,6 +139,7 @@ private fun Icon(name: String, iconUrl: String, modifier: Modifier = Modifier) {
)
}
+// TODO(use CurrencyPlaceholderIcon here?)
@Composable
private fun PlaceholderIcon(name: String, modifier: Modifier = Modifier) {
Box(
diff --git a/app/src/main/java/com/tangem/tap/features/tokens/legacy/redux/TokensMiddleware.kt b/app/src/main/java/com/tangem/tap/features/tokens/legacy/redux/TokensMiddleware.kt
index be311deb0a..cab038d65e 100644
--- a/app/src/main/java/com/tangem/tap/features/tokens/legacy/redux/TokensMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/tokens/legacy/redux/TokensMiddleware.kt
@@ -31,6 +31,7 @@ import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.domain.TapError
+import com.tangem.tap.domain.model.WalletDataModel
import com.tangem.tap.domain.tokens.LoadAvailableCoinsService
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.proxy.redux.DaggerGraphState
@@ -316,21 +317,23 @@ class TokensMiddleware {
}
}
- val addedCurrencies = store.state.walletState.walletsStores.map { walletStore ->
- walletStore.walletsData.map { walletData -> walletData.currency }
- }.flatten().map {
- when (it) {
- is Currency.Blockchain -> DomainWrapped.Currency.Blockchain(
- it.blockchain,
- it.derivationPath,
- )
- is Currency.Token -> DomainWrapped.Currency.Token(
- it.token,
- it.blockchain,
- it.derivationPath,
- )
+ val addedCurrencies = store.state.walletState.walletsStores
+ .map { walletStore -> walletStore.walletsData.map(WalletDataModel::currency) }
+ .flatten()
+ .map { currency ->
+ when (currency) {
+ is Currency.Blockchain -> DomainWrapped.Currency.Blockchain(
+ currency.blockchain,
+ currency.derivationPath,
+ )
+
+ is Currency.Token -> DomainWrapped.Currency.Token(
+ currency.token,
+ currency.blockchain,
+ currency.derivationPath,
+ )
+ }
}
- }
domainStore.dispatch(AddCustomTokenAction.Init.SetAddedCurrencies(addedCurrencies))
domainStore.dispatch(AddCustomTokenAction.Init.SetOnAddTokenCallback(onAddCustomToken))
store.dispatch(NavigationAction.NavigateTo(AppScreen.AddCustomToken))
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt
index cabb464853..bc5e960649 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/AppCurrencyMiddleware.kt
@@ -2,7 +2,9 @@ package com.tangem.tap.features.wallet.redux.middlewares
import com.tangem.common.extensions.guard
import com.tangem.core.analytics.Analytics
-import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse
+import com.tangem.data.source.preferences.model.DataSourceCurrency
+import com.tangem.data.source.preferences.model.DataSourceFiatCurrency
+import com.tangem.data.source.preferences.storage.FiatCurrenciesPrefStorage
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.MainScreen
import com.tangem.tap.common.entities.FiatCurrency
@@ -14,7 +16,6 @@ import com.tangem.tap.features.wallet.domain.WalletRepository
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.models.WalletDialog
import com.tangem.tap.features.walletSelector.redux.WalletSelectorAction
-import com.tangem.tap.persistence.FiatCurrenciesPrefStorage
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.userWalletsListManager
@@ -47,8 +48,10 @@ class AppCurrencyMiddleware(
scope.launch {
runCatching { walletRepository.getCurrencyList() }
- .onSuccess {
- val currenciesList = it.currencies
+ .onSuccess { response ->
+ val currenciesList = response.currencies
+ .map { with(it) { DataSourceCurrency(id, code, name, rateBTC, unit, type) } }
+
if (currenciesList.isNotEmpty() && currenciesList.toSet() != storedFiatCurrencies.toSet()) {
fiatCurrenciesPrefStorage.save(currenciesList)
store.dispatchDialogShow(
@@ -64,7 +67,9 @@ class AppCurrencyMiddleware(
private fun selectCurrency(action: WalletAction.AppCurrencyAction.SelectAppCurrency) {
Analytics.send(MainScreen.MainCurrencyChanged(AnalyticsParam.CurrencyType.FiatCurrency(action.fiatCurrency)))
- fiatCurrenciesPrefStorage.saveAppCurrency(action.fiatCurrency)
+ fiatCurrenciesPrefStorage.saveAppCurrency(
+ with(action.fiatCurrency) { DataSourceFiatCurrency(code, name, symbol) },
+ )
store.dispatch(GlobalAction.ChangeAppCurrency(action.fiatCurrency))
store.dispatch(DetailsAction.ChangeAppCurrency(action.fiatCurrency))
store.dispatch(WalletSelectorAction.ChangeAppCurrency(action.fiatCurrency))
@@ -77,7 +82,7 @@ class AppCurrencyMiddleware(
}
}
- private fun List.mapToUiModel(): List {
+ private fun List.mapToUiModel(): List {
return this.map {
FiatCurrency(
code = it.code,
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt
index 3ade28d8db..fa6759eb26 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/WarningsMiddleware.kt
@@ -31,7 +31,7 @@ class WarningsMiddleware {
is WalletAction.Warnings.CheckIfNeeded -> {
showCardWarningsIfNeeded(globalState)
val readyToShow = preferencesStorage.appRatingLaunchObserver.isReadyToShow()
- if (readyToShow) addWarningMessage(WarningMessagesManager.appRatingWarning(), true)
+ if (readyToShow) addWarningMessage(warning = WarningMessagesManager.appRatingWarning, autoUpdate = true)
}
is WalletAction.Warnings.CheckHashesCount.CheckHashesCountOnline -> checkHashesCountOnline()
is WalletAction.Warnings.CheckHashesCount.SaveCardId -> {
@@ -69,7 +69,7 @@ class WarningsMiddleware {
preferencesStorage.appRatingLaunchObserver.foundWalletWithFunds()
}
if (preferencesStorage.appRatingLaunchObserver.isReadyToShow()) {
- addWarningMessage(WarningMessagesManager.appRatingWarning(), true)
+ addWarningMessage(WarningMessagesManager.appRatingWarning, true)
}
}
@@ -78,21 +78,21 @@ class WarningsMiddleware {
val card = scanResponse.card
globalState.warningManager?.removeWarnings(WarningMessage.Origin.Local)
if (card.isTestCard) {
- addWarningMessage(WarningMessagesManager.testCardWarning(), autoUpdate = true)
+ addWarningMessage(WarningMessagesManager.testCardWarning, autoUpdate = true)
return@let
}
showWarningLowRemainingSignaturesIfNeeded(card)
if (card.firmwareVersion.type != FirmwareVersion.FirmwareType.Release) {
- addWarningMessage(WarningMessagesManager.devCardWarning())
+ addWarningMessage(WarningMessagesManager.devCardWarning)
} else if (!preferencesStorage.usedCardsPrefStorage.wasScanned(card.cardId)) {
checkIfWarningNeeded(scanResponse)?.let { warning -> addWarningMessage(warning) }
}
if (card.firmwareVersion.type == FirmwareVersion.FirmwareType.Release && !globalState.cardVerifiedOnline) {
- addWarningMessage(WarningMessagesManager.onlineVerificationFailed())
+ addWarningMessage(WarningMessagesManager.onlineVerificationFailed)
}
if (scanResponse.isDemoCard()) {
- addWarningMessage(WarningMessagesManager.demoCardWarning())
+ addWarningMessage(WarningMessagesManager.demoCardWarning)
}
setWarningMessages()
}
@@ -113,7 +113,7 @@ class WarningsMiddleware {
if (scanResponse.cardTypesResolver.isMultiwalletAllowed()) {
val isBackupForbidden = with(scanResponse.card.settings) { !(isBackupAllowed || isHDWalletAllowed) }
return if (scanResponse.card.hasSignedHashes() && isBackupForbidden) {
- WarningMessagesManager.signedHashesMultiWalletWarning()
+ WarningMessagesManager.signedHashesMultiWalletWarning
} else {
store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId)
null
@@ -123,7 +123,7 @@ class WarningsMiddleware {
val validator = store.state.walletState.walletManagers.firstOrNull() as? SignatureCountValidator
return if (validator == null) {
if (scanResponse.card.hasSignedHashes()) {
- WarningMessagesManager.alreadySignedHashesWarning()
+ WarningMessagesManager.alreadySignedHashesWarning
} else {
store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId)
null
@@ -161,9 +161,9 @@ class WarningsMiddleware {
}
is SimpleResult.Failure ->
if (result.error is BlockchainSdkError.SignatureCountNotMatched) {
- addWarningMessage(WarningMessagesManager.alreadySignedHashesWarning(), true)
+ addWarningMessage(WarningMessagesManager.alreadySignedHashesWarning, true)
} else if (signedHashes > 0) {
- addWarningMessage(WarningMessagesManager.alreadySignedHashesWarning(), true)
+ addWarningMessage(WarningMessagesManager.alreadySignedHashesWarning, true)
}
null -> Unit
}
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletViewModel.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletViewModel.kt
index ce2e4ea085..ded1be23f3 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletViewModel.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/WalletViewModel.kt
@@ -105,14 +105,12 @@ internal class WalletViewModel @Inject constructor(
private fun bootstrapSelectedWalletStoresChanges(manager: UserWalletsListManager) {
observeWalletStoresUpdatesJob = manager.selectedUserWallet
.map { it.walletId }
- .flatMapLatest { selectedUserWalletId ->
- walletStoresManager.get(selectedUserWalletId)
- }
+ .flatMapLatest(walletStoresManager::get)
.debounce { walletStores ->
if (walletStores.isNotEmpty()) WALLET_STORES_DEBOUNCE_TIMEOUT else 0
}
.onEach { walletStores ->
- store.dispatch(WalletAction.WalletStoresChanged(walletStores))
+ store.dispatchOnMain(WalletAction.WalletStoresChanged(walletStores))
}
.launchIn(viewModelScope)
}
diff --git a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SignedHashesWarningDialog.kt b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SignedHashesWarningDialog.kt
index b9996b1c18..c44801d9c8 100644
--- a/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SignedHashesWarningDialog.kt
+++ b/app/src/main/java/com/tangem/tap/features/wallet/ui/dialogs/SignedHashesWarningDialog.kt
@@ -16,9 +16,7 @@ object SignedHashesWarningDialog {
setPositiveButton(R.string.common_understand) { _, _ ->
store.dispatch(WalletAction.Warnings.CheckHashesCount.SaveCardId)
store.dispatch(
- GlobalAction.HideWarningMessage(
- WarningMessagesManager.signedHashesMultiWalletWarning(),
- ),
+ GlobalAction.HideWarningMessage(WarningMessagesManager.signedHashesMultiWalletWarning),
)
}
setNegativeButton(R.string.common_cancel) { _, _ -> }
diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt
index b41635abb0..e997860d4e 100644
--- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt
+++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorAction.kt
@@ -57,4 +57,6 @@ internal sealed interface WalletSelectorAction : Action {
) : WalletSelectorAction
object CloseError : WalletSelectorAction
+
+ object ClearUserWallets : WalletSelectorAction
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt
index 675ca89cdf..53cd284846 100644
--- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorMiddleware.kt
@@ -7,8 +7,8 @@ import com.tangem.common.doOnSuccess
import com.tangem.common.flatMap
import com.tangem.common.map
import com.tangem.core.analytics.Analytics
-import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.common.util.UserWalletId
+import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Basic
import com.tangem.tap.common.analytics.events.MyWallets
@@ -81,6 +81,12 @@ internal class WalletSelectorMiddleware {
is WalletSelectorAction.ChangeAppCurrency -> {
refreshUserWalletsAmounts()
}
+ is WalletSelectorAction.ClearUserWallets -> scope.launch {
+ clearUserWallets()
+ .doOnFailure { e ->
+ Timber.e(e, "Unable to clear user wallets")
+ }
+ }
is WalletSelectorAction.AddWallet.Success,
is WalletSelectorAction.AddWallet.Error,
is WalletSelectorAction.SelectedWalletChanged,
diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt
index 2d06b37937..2a8ace7ad3 100644
--- a/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt
+++ b/app/src/main/java/com/tangem/tap/features/walletSelector/redux/WalletSelectorReducer.kt
@@ -59,6 +59,7 @@ internal object WalletSelectorReducer {
is WalletSelectorAction.SelectWallet,
is WalletSelectorAction.RemoveWallets,
is WalletSelectorAction.RenameWallet,
+ is WalletSelectorAction.ClearUserWallets,
-> state
}
}
diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt
index 4f24d56193..ecb5291d26 100644
--- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt
+++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorBottomSheetFragment.kt
@@ -15,7 +15,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Alignment
-import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
@@ -46,11 +45,8 @@ internal class WalletSelectorBottomSheetFragment : ComposeBottomSheetFragment {
- return viewModel.state.collectAsState()
- }
+ override fun provideState(): State = viewModel.state.collectAsState()
- @OptIn(ExperimentalComposeUiApi::class)
@Composable
override fun ScreenContent(state: WalletSelectorScreenState, modifier: Modifier) {
val snackbarHostState = remember { SnackbarHostState() }
diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt
index 99024dad35..5a947b1cbd 100644
--- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt
+++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/WalletSelectorViewModel.kt
@@ -200,13 +200,17 @@ internal class WalletSelectorViewModel : ViewModel(), StoreSubscriber WarningModel.BiometricsDisabledWarning(
- onConfirm = { /* [REDACTED_TODO_COMMENT] */ },
+ onConfirm = this::clearUserWallets,
onDismiss = this::dismissWarningDialog,
)
else -> currentDialog
}
}
+ private fun clearUserWallets() {
+ store.dispatch(WalletSelectorAction.ClearUserWallets)
+ }
+
private fun dismissWarningDialog() {
stateInternal.update { prevState ->
prevState.copy(
diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeAction.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeAction.kt
index 0d248ec7af..a0484fc798 100644
--- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeAction.kt
+++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeAction.kt
@@ -18,4 +18,6 @@ internal sealed interface WelcomeAction : Action {
data class HandleIntentIfNeeded(val intent: Intent?) : WelcomeAction
object CloseError : WelcomeAction
+
+ object ClearUserWallets : WelcomeAction
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt
index 6e7da12221..5e242d9cec 100644
--- a/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/features/welcome/redux/WelcomeMiddleware.kt
@@ -3,11 +3,14 @@ package com.tangem.tap.features.welcome.redux
import android.content.Intent
import com.tangem.common.core.TangemSdkError
import com.tangem.common.doOnFailure
+import com.tangem.common.doOnResult
import com.tangem.common.doOnSuccess
+import com.tangem.common.flatMap
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Basic
import com.tangem.tap.common.extensions.dispatchOnMain
+import com.tangem.tap.common.extensions.dispatchWithMain
import com.tangem.tap.common.extensions.onUserWalletSelected
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.navigation.AppScreen
@@ -51,6 +54,9 @@ internal class WelcomeMiddleware {
is WelcomeAction.HandleIntentIfNeeded -> {
handleInitialIntent(action.intent)
}
+ is WelcomeAction.ClearUserWallets -> {
+ clearUserWallets()
+ }
is WelcomeAction.ProceedWithBiometrics.Error,
is WelcomeAction.ProceedWithCard.Error,
is WelcomeAction.ProceedWithBiometrics.Success,
@@ -60,22 +66,31 @@ internal class WelcomeMiddleware {
}
}
- private fun proceedWithBiometrics(state: WelcomeState) {
- scope.launch {
- userWalletsListManager.unlockIfLockable()
- .doOnFailure { error ->
- Timber.e(error, "Unable to unlock user wallets with biometrics")
- store.dispatchOnMain(WelcomeAction.ProceedWithBiometrics.Error(error))
- }
- .doOnSuccess { selectedUserWallet ->
- store.dispatchOnMain(SignInAction.SetSignInType(Basic.SignedIn.SignInType.Biometric))
- store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet))
- store.dispatchOnMain(WelcomeAction.ProceedWithBiometrics.Success)
- store.onUserWalletSelected(userWallet = selectedUserWallet)
+ private fun clearUserWallets() = scope.launch {
+ userWalletsListManager.clear()
+ .flatMap { tangemSdkManager.clearSavedUserCodes() }
+ .doOnFailure { e ->
+ Timber.e(e, "Unable to clear user wallets")
+ }
+ .doOnResult {
+ store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Home))
+ }
+ }
- intentHandler.handleWalletConnectLink(state.intent)
- }
- }
+ private fun proceedWithBiometrics(state: WelcomeState) = scope.launch {
+ userWalletsListManager.unlockIfLockable()
+ .doOnFailure { error ->
+ Timber.e(error, "Unable to unlock user wallets with biometrics")
+ store.dispatchOnMain(WelcomeAction.ProceedWithBiometrics.Error(error))
+ }
+ .doOnSuccess { selectedUserWallet ->
+ store.dispatchOnMain(SignInAction.SetSignInType(Basic.SignedIn.SignInType.Biometric))
+ store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet))
+ store.dispatchOnMain(WelcomeAction.ProceedWithBiometrics.Success)
+ store.onUserWalletSelected(userWallet = selectedUserWallet)
+
+ intentHandler.handleWalletConnectLink(state.intent)
+ }
}
private fun proceedWithCard(state: WelcomeState) = scope.launch {
diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt
index e19891caa2..ad39cbe5ae 100644
--- a/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt
+++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/WelcomeViewModel.kt
@@ -72,7 +72,7 @@ internal class WelcomeViewModel : ViewModel(), StoreSubscriber {
onDismiss = this::dismissWarning,
)
is UserWalletsListError.BiometricsAuthenticationDisabled -> WarningModel.BiometricsDisabledWarning(
- onConfirm = { /* [REDACTED_TODO_COMMENT] */ },
+ onConfirm = this::clearUserWallets,
onDismiss = this::dismissWarning,
)
else -> null
@@ -88,6 +88,10 @@ internal class WelcomeViewModel : ViewModel(), StoreSubscriber {
closeError()
}
+ private fun clearUserWallets() {
+ store.dispatch(WelcomeAction.ClearUserWallets)
+ }
+
private fun subscribeToStoreChanges() {
store.subscribe(this) { appState ->
appState.skip { old, new -> old.welcomeState == new.welcomeState }
diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/BuyExchangeService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/BuyExchangeService.kt
index fec559a528..180f96317b 100644
--- a/app/src/main/java/com/tangem/tap/network/exchangeServices/BuyExchangeService.kt
+++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/BuyExchangeService.kt
@@ -13,7 +13,7 @@ import kotlinx.coroutines.launch
* Temporary wrapper for the buy services. Service switches based on selected product type.
* Just now - UtorgService used only for the SaltPay cards
*/
-class BuyExchangeService(
+internal class BuyExchangeService(
private val productTypeProvider: () -> ProductType?,
private val mercuryoService: MercuryoService,
private val utorgService: UtorgExchangeService,
diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt
index 8f76434576..f7725ec89a 100644
--- a/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt
+++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/mercuryo/MercuryoService.kt
@@ -11,57 +11,21 @@ import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.network.exchangeServices.CurrencyExchangeManager
import com.tangem.tap.network.exchangeServices.ExchangeService
import com.tangem.tap.network.exchangeServices.ExchangeUrlBuilder
+import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.CopyOnWriteArrayList
/**
[REDACTED_AUTHOR]
*/
-class MercuryoService(
- private val environment: MercuryoEnvironment,
-) : ExchangeService {
+internal class MercuryoService(private val environment: MercuryoEnvironment) : ExchangeService {
private val api: MercuryoApi = environment.mercuryoApi
- private val blockchainsAvailableToBuy = mutableListOf()
- private val tokensAvailableToBy = mutableMapOf>()
+ private val blockchainsAvailableToBuy = CopyOnWriteArrayList()
+ private val tokensAvailableToBuy = ConcurrentHashMap>()
override fun featureIsSwitchedOn(): Boolean = true
- @Suppress("NestedBlockDepth")
- override suspend fun update() {
- when (val result = performRequest { api.currencies(environment.apiVersion) }) {
- is Result.Success -> {
- val response = result.data
- if (response.status == RESPONSE_SUCCESS_STATUS_CODE) {
- // all currencies which can be bought
- val currenciesAvailableToBy = response.data.crypto
- // tokens which can be bought only from specific blockchain network
- val supportedTokensWithNetwork = response.data.config.base
-
- currenciesAvailableToBy.forEach { currencyName ->
- val blockchain = blockchainFromCurrencyName(currencyName)
- if (blockchain == null) {
- // suppose its a token
- supportedTokensWithNetwork[currencyName]?.let {
- blockchainFromCurrencyName(it)
- }?.let { blockchainNetwork ->
- val supportedInBlockchainsNetwork = tokensAvailableToBy[currencyName]
- ?: mutableListOf()
- supportedInBlockchainsNetwork.add(blockchainNetwork)
- tokensAvailableToBy[currencyName] = supportedInBlockchainsNetwork
- }
- } else {
- blockchainsAvailableToBuy.add(blockchain)
- }
- }
- }
- }
- is Result.Failure -> {
- blockchainsAvailableToBuy.clear()
- tokensAvailableToBy.clear()
- }
- }
- }
-
override fun isBuyAllowed(): Boolean = true
override fun isSellAllowed(): Boolean = false
@@ -83,20 +47,32 @@ class MercuryoService(
when {
blockchain.isTestnet() -> blockchain.getTestnetTopUpUrl() != null
unsupportedBlockchains.contains(blockchain) -> false
- else -> {
- blockchainsAvailableToBuy.contains(currency.blockchain)
- }
+ else -> blockchainsAvailableToBuy.contains(blockchain)
}
}
+
is Currency.Token -> {
- val supportedInBlockchains = tokensAvailableToBy[currency.currencySymbol] ?: return false
- supportedInBlockchains.contains(currency.blockchain)
+ val supportedInBlockchains = tokensAvailableToBuy[currency.currencySymbol] ?: return false
+ supportedInBlockchains.contains(blockchain)
}
}
}
override fun availableForSell(currency: Currency): Boolean = false
+ override suspend fun update() {
+ val result = performRequest { api.currencies(environment.apiVersion) }
+ when {
+ result is Result.Success && result.data.status == RESPONSE_SUCCESS_STATUS_CODE -> {
+ handleSuccessfullyUpdatedData(data = result.data.data)
+ }
+ result is Result.Failure -> {
+ blockchainsAvailableToBuy.clear()
+ tokensAvailableToBuy.clear()
+ }
+ }
+ }
+
override fun getUrl(
action: CurrencyExchangeManager.Action,
blockchain: Blockchain,
@@ -117,24 +93,40 @@ class MercuryoService(
.appendQueryParameter("fix_currency", "true")
.appendQueryParameter("return_url", ExchangeUrlBuilder.SUCCESS_URL)
- val url = builder.build().toString()
- return url
- }
-
- private fun signature(address: String): String {
- return (address + environment.secret).calculateSha512().toHexString().lowercase()
+ return builder.build().toString()
}
override fun getSellCryptoReceiptUrl(action: CurrencyExchangeManager.Action, transactionId: String): String? = null
- private fun blockchainFromCurrencyName(currencyName: String): Blockchain? = when (currencyName) {
- "BNB" -> Blockchain.BSC
- "ETH" -> Blockchain.Ethereum
- "ADA" -> Blockchain.CardanoShelley
- else -> Blockchain.values().find { it.currency.lowercase() == currencyName.lowercase() }
+ private fun handleSuccessfullyUpdatedData(data: MercuryoCurrenciesResponse.Data) {
+ data.crypto.forEach { currencyName ->
+ val blockchain = blockchainFromCurrencyName(currencyName)
+ if (blockchain == null) {
+ val specificBlockchain = data.config.base[currencyName]?.let(::blockchainFromCurrencyName)
+ if (specificBlockchain != null) {
+ tokensAvailableToBuy.set(
+ key = currencyName,
+ value = tokensAvailableToBuy[currencyName].orEmpty() + specificBlockchain,
+ )
+ }
+ } else {
+ blockchainsAvailableToBuy.add(blockchain)
+ }
+ }
}
- companion object {
- private const val RESPONSE_SUCCESS_STATUS_CODE = 200
+ private fun blockchainFromCurrencyName(currencyName: String): Blockchain? {
+ return when (currencyName) {
+ "BNB" -> Blockchain.BSC
+ "ETH" -> Blockchain.Ethereum
+ "ADA" -> Blockchain.CardanoShelley
+ else -> Blockchain.values().find { it.currency.lowercase() == currencyName.lowercase() }
+ }
+ }
+
+ private fun signature(address: String) = (address + environment.secret).calculateSha512().toHexString().lowercase()
+
+ private companion object {
+ const val RESPONSE_SUCCESS_STATUS_CODE = 200
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/persistence/CardBalanceStateAdapter.kt b/app/src/main/java/com/tangem/tap/persistence/CardBalanceStateAdapter.kt
deleted file mode 100644
index 0385071ff3..0000000000
--- a/app/src/main/java/com/tangem/tap/persistence/CardBalanceStateAdapter.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.tangem.tap.persistence
-
-import com.squareup.moshi.FromJson
-import com.squareup.moshi.ToJson
-import com.tangem.tap.common.analytics.events.AnalyticsParam
-
-class CardBalanceStateAdapter {
-
- @ToJson
- fun toJson(src: AnalyticsParam.CardBalanceState): String = src.value
-
- @FromJson
- fun fromJson(json: String): AnalyticsParam.CardBalanceState {
- return when (json) {
- AnalyticsParam.CardBalanceState.Empty.value -> AnalyticsParam.CardBalanceState.Empty
- AnalyticsParam.CardBalanceState.Full.value -> AnalyticsParam.CardBalanceState.Full
- else -> error("CardBalanceState not found")
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt b/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt
index 72645564d0..c673ac8577 100644
--- a/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt
+++ b/app/src/main/java/com/tangem/tap/proxy/DerivationManagerImpl.kt
@@ -107,8 +107,8 @@ class DerivationManagerImpl(
val selectedUserWallet = appStateHolder.userWalletsListManager?.selectedUserWalletSync
val result = appStateHolder.tangemSdkManager?.derivePublicKeys(
- scanResponse.card.cardId,
- derivations,
+ cardId = null, // always ignore cardId in derive task
+ derivations = derivations,
)
when (result) {
is CompletionResult.Success -> {
diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt
index 65a4cdb3e9..2952ed0257 100644
--- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt
+++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphAction.kt
@@ -1,20 +1,9 @@
package com.tangem.tap.proxy.redux
-import com.tangem.datasource.asset.AssetReader
-import com.tangem.datasource.connection.NetworkConnectionManager
import com.tangem.features.tester.api.TesterRouter
-import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles
-import com.tangem.tap.features.tokens.api.featuretoggles.TokensListFeatureToggles
import org.rekotlin.Action
sealed interface DaggerGraphAction : Action {
- data class SetApplicationDependencies(
- val assetReader: AssetReader,
- val networkConnectionManager: NetworkConnectionManager,
- val tokensListFeatureToggles: TokensListFeatureToggles,
- val customTokenFeatureToggles: CustomTokenFeatureToggles,
- ) : DaggerGraphAction
-
data class SetActivityDependencies(val testerRouter: TesterRouter) : DaggerGraphAction
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt
index 3bb0e65284..43cb94af5c 100644
--- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt
+++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphReducer.kt
@@ -12,12 +12,6 @@ object DaggerGraphReducer {
private fun internalReduce(action: DaggerGraphAction, state: AppState): DaggerGraphState {
return when (action) {
- is DaggerGraphAction.SetApplicationDependencies -> state.daggerGraphState.copy(
- assetReader = action.assetReader,
- networkConnectionManager = action.networkConnectionManager,
- tokensListFeatureToggles = action.tokensListFeatureToggles,
- customTokenFeatureToggles = action.customTokenFeatureToggles,
- )
is DaggerGraphAction.SetActivityDependencies -> state.daggerGraphState.copy(
testerRouter = action.testerRouter,
)
diff --git a/app/src/main/res/drawable/ic_cosmos_no_color.xml b/app/src/main/res/drawable/ic_cosmos_no_color.xml
new file mode 100644
index 0000000000..2b45162689
--- /dev/null
+++ b/app/src/main/res/drawable/ic_cosmos_no_color.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_currency_wallet_content.xml b/app/src/main/res/layout/item_currency_wallet_content.xml
index 5eb26a5bbb..4bb15bb54b 100644
--- a/app/src/main/res/layout/item_currency_wallet_content.xml
+++ b/app/src/main/res/layout/item_currency_wallet_content.xml
@@ -19,8 +19,8 @@
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:ellipsize="end"
- android:gravity="start"
android:maxLines="1"
+ android:textAlignment="viewStart"
android:textColor="@color/text_primary_1"
android:textSize="16sp"
android:textStyle="bold"
@@ -67,8 +67,8 @@
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:ellipsize="end"
- android:gravity="start"
android:maxLines="1"
+ android:textAlignment="viewStart"
android:textColor="@color/text_tertiary"
android:textSize="12sp"
android:visibility="gone"
diff --git a/app/src/main/res/layout/layout_balance_wallet_details.xml b/app/src/main/res/layout/layout_balance_wallet_details.xml
index 0ae3a9e463..9c4656d92e 100644
--- a/app/src/main/res/layout/layout_balance_wallet_details.xml
+++ b/app/src/main/res/layout/layout_balance_wallet_details.xml
@@ -15,6 +15,7 @@
android:paddingStart="12dp"
android:paddingTop="16dp"
android:paddingEnd="16dp"
+ android:textAlignment="viewStart"
android:textColor="@color/darkGray6"
android:textSize="20sp"
android:textStyle="bold"
@@ -31,6 +32,7 @@
android:paddingStart="16dp"
android:paddingTop="4dp"
android:paddingEnd="16dp"
+ android:textAlignment="viewStart"
android:textColor="@color/darkGray1"
android:textSize="14sp"
app:layout_constraintEnd_toStartOf="@id/iv_currency"
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Shimmers.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Shimmers.kt
new file mode 100644
index 0000000000..3b6227abbc
--- /dev/null
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/Shimmers.kt
@@ -0,0 +1,80 @@
+package com.tangem.core.ui.components
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import com.tangem.core.ui.res.TangemTheme
+import com.valentinilk.shimmer.shimmer
+
+/**
+ * Rectangle shimmer item with rounded shape from DS
+ */
+@Composable
+fun ShimmerRectangle(modifier: Modifier = Modifier) {
+ Box(
+ modifier = modifier
+ .shimmer()
+ .background(
+ color = TangemTheme.colors.button.secondary,
+ shape = RoundedCornerShape(TangemTheme.dimens.radius6),
+ ),
+ )
+}
+
+/**
+ * Circle shimmer item
+ * Size should be set in modifier
+ */
+@Composable
+fun CircleShimmer(modifier: Modifier = Modifier) {
+ Box(modifier = modifier.shimmer()) {
+ Box(
+ modifier = Modifier
+ .matchParentSize()
+ .background(
+ color = TangemTheme.colors.button.secondary,
+ shape = CircleShape,
+ ),
+ )
+ }
+}
+
+// region preview
+
+@Composable
+private fun ShimmersPreview() {
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing18),
+ ) {
+ ShimmerRectangle(
+ modifier = Modifier.size(
+ width = TangemTheme.dimens.size72,
+ height = TangemTheme.dimens.size12,
+ ),
+ )
+ CircleShimmer(modifier = Modifier.size(size = TangemTheme.dimens.size42))
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun Shimmers_InLightTheme() {
+ TangemTheme(isDark = false) {
+ ShimmersPreview()
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun Shimmers_InDarkTheme() {
+ TangemTheme(isDark = true) {
+ ShimmersPreview()
+ }
+}
+
+// endregion preview
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt
index d760b3a96b..430fcb853f 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt
@@ -36,6 +36,7 @@ fun getActiveIconRes(blockchainId: String): Int {
"The-Open-Network", "The-Open-Network/test" -> R.drawable.img_ton_22
"KAVA", "KAVA/test" -> R.drawable.img_kava_22
"ravencoin", "ravencoin/test" -> R.drawable.img_ravencoin_22
+ "cosmos", "cosmos/test" -> R.drawable.img_cosmos_22
else -> R.drawable.ic_alert_24
}
}
diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt
index d5d14244ae..a3b0fc8c7e 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt
@@ -58,6 +58,7 @@ data class TangemDimens internal constructor(
val size50: Dp = 50.dp,
val size56: Dp = 56.dp,
val size62: Dp = 62.dp,
+ val size68: Dp = 68.dp,
val size72: Dp = 72.dp,
val size80: Dp = 80.dp,
val size84: Dp = 84.dp,
@@ -65,7 +66,9 @@ data class TangemDimens internal constructor(
val size93: Dp = 93.dp,
val size96: Dp = 96.dp,
val size102: Dp = 102.dp,
+ val size108: Dp = 108.dp,
val size116: Dp = 116.dp,
+ val size120: Dp = 120.dp,
val size142: Dp = 142.dp,
val size164: Dp = 164.dp,
val size200: Dp = 200.dp,
@@ -95,5 +98,6 @@ data class TangemDimens internal constructor(
val spacing54: Dp = 54.dp,
val spacing56: Dp = 56.dp,
val spacing92: Dp = 92.dp,
+ val spacing154: Dp = 154.dp,
// endregion Spacing
)
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemShapes.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemShapes.kt
index 57bd37658f..b452e38004 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemShapes.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemShapes.kt
@@ -9,6 +9,7 @@ data class TangemShapes internal constructor(
val roundedCornersSmall: Shape,
val roundedCornersSmall2: Shape,
val roundedCornersMedium: Shape,
+ val roundedCornersXMedium: Shape,
val roundedCornersLarge: Shape,
val bottomSheet: Shape,
) {
@@ -16,6 +17,7 @@ data class TangemShapes internal constructor(
roundedCornersSmall = RoundedCornerShape(size = dimens.radius2),
roundedCornersSmall2 = RoundedCornerShape(size = dimens.radius4),
roundedCornersMedium = RoundedCornerShape(size = dimens.radius12),
+ roundedCornersXMedium = RoundedCornerShape(size = dimens.radius16),
roundedCornersLarge = RoundedCornerShape(size = dimens.radius28),
bottomSheet = RoundedCornerShape(
topStart = dimens.radius16,
diff --git a/core/ui/src/main/res/drawable/ic_drag_24.xml b/core/ui/src/main/res/drawable/ic_drag_24.xml
new file mode 100644
index 0000000000..9246315ec7
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_drag_24.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/ic_eye_off_24.xml b/core/ui/src/main/res/drawable/ic_eye_off_24.xml
new file mode 100644
index 0000000000..7454717d84
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_eye_off_24.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/img_arrow_down_8.xml b/core/ui/src/main/res/drawable/img_arrow_down_8.xml
new file mode 100644
index 0000000000..355357c8c3
--- /dev/null
+++ b/core/ui/src/main/res/drawable/img_arrow_down_8.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/img_arrow_up_8.xml b/core/ui/src/main/res/drawable/img_arrow_up_8.xml
new file mode 100644
index 0000000000..0d99c9d789
--- /dev/null
+++ b/core/ui/src/main/res/drawable/img_arrow_up_8.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/core/ui/src/main/res/drawable/img_cosmos_22.xml b/core/ui/src/main/res/drawable/img_cosmos_22.xml
new file mode 100644
index 0000000000..14ea7757da
--- /dev/null
+++ b/core/ui/src/main/res/drawable/img_cosmos_22.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
diff --git a/core/ui/src/main/res/drawable/img_loader_15.xml b/core/ui/src/main/res/drawable/img_loader_15.xml
new file mode 100644
index 0000000000..3798701a08
--- /dev/null
+++ b/core/ui/src/main/res/drawable/img_loader_15.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
diff --git a/data/source/preferences/.gitignore b/data/source/preferences/.gitignore
new file mode 100644
index 0000000000..796b96d1c4
--- /dev/null
+++ b/data/source/preferences/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/data/source/preferences/build.gradle.kts b/data/source/preferences/build.gradle.kts
new file mode 100644
index 0000000000..7cd9e54515
--- /dev/null
+++ b/data/source/preferences/build.gradle.kts
@@ -0,0 +1,17 @@
+plugins {
+ alias(deps.plugins.kotlin.android)
+ alias(deps.plugins.kotlin.kapt)
+ alias(deps.plugins.android.library)
+ id("configuration")
+}
+
+dependencies {
+ implementation(deps.androidx.core.ktx)
+ implementation(deps.moshi)
+ implementation(deps.moshi.kotlin)
+ implementation(deps.hilt.android)
+ kapt(deps.hilt.kapt)
+
+ // For MoshiJsonConverter
+ implementation(deps.tangem.card.core)
+}
\ No newline at end of file
diff --git a/data/source/preferences/src/main/AndroidManifest.xml b/data/source/preferences/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..67568f1b9e
--- /dev/null
+++ b/data/source/preferences/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/AppRatingLaunchObserver.kt b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/AppRatingLaunchObserver.kt
new file mode 100644
index 0000000000..d65eb3813b
--- /dev/null
+++ b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/AppRatingLaunchObserver.kt
@@ -0,0 +1,73 @@
+package com.tangem.data.source.preferences
+
+import android.content.SharedPreferences
+import androidx.core.content.edit
+import java.util.*
+
+@Deprecated("Create repository instead")
+class AppRatingLaunchObserver internal constructor(
+ private val preferences: SharedPreferences,
+ private val launchCounts: Int,
+) {
+
+ private val deferShowing = 20
+ private val firstShowing = 3
+ private var fundsFoundDate: Calendar? = null
+
+ init {
+ val msWhenFundsWasFound = preferences.getLong(K_FUNDS_FOUND_DATE, FUNDS_FOUND_DATE_UNDEFINED)
+ if (msWhenFundsWasFound != FUNDS_FOUND_DATE_UNDEFINED) {
+ fundsFoundDate = Calendar.getInstance().apply { timeInMillis = msWhenFundsWasFound }
+ }
+ }
+
+ fun foundWalletWithFunds() {
+ if (fundsFoundDate != null) return
+
+ fundsFoundDate = Calendar.getInstance()
+ preferences.edit(true) {
+ putLong(K_FUNDS_FOUND_DATE, fundsFoundDate!!.timeInMillis).apply()
+ putInt(K_SHOW_RATING_AT_LAUNCH_COUNT, launchCounts + firstShowing)
+ }
+ }
+
+ fun isReadyToShow(): Boolean {
+ val fundsDate = fundsFoundDate ?: return false
+
+ if (!userWasInteractWithRating()) {
+ val diff = Calendar.getInstance().timeInMillis - fundsDate.timeInMillis
+ val diffInDays = diff / DAY_MILLIS
+ return launchCounts >= getCounterOfNextShowing() && diffInDays >= firstShowing
+ }
+
+ val nextShowing = getCounterOfNextShowing()
+ return launchCounts >= nextShowing
+ }
+
+ fun applyDelayedShowing() {
+ updateNextShowing(launchCounts + deferShowing)
+ }
+
+ fun setNeverToShow() {
+ updateNextShowing(Int.MAX_VALUE)
+ }
+
+ private fun updateNextShowing(at: Int) {
+ val editor = preferences.edit()
+ editor.putInt(K_SHOW_RATING_AT_LAUNCH_COUNT, at)
+ editor.putBoolean(K_USER_WAS_INTERACT_WITH_RATING, true)
+ editor.apply()
+ }
+
+ private fun userWasInteractWithRating(): Boolean = preferences.getBoolean(K_USER_WAS_INTERACT_WITH_RATING, false)
+
+ private fun getCounterOfNextShowing(): Int = preferences.getInt(K_SHOW_RATING_AT_LAUNCH_COUNT, firstShowing)
+
+ companion object {
+ private const val K_SHOW_RATING_AT_LAUNCH_COUNT = "showRatingDialogAtLaunchCount"
+ private const val K_FUNDS_FOUND_DATE = "fundsFoundDate"
+ private const val K_USER_WAS_INTERACT_WITH_RATING = "userWasInteractWithRating"
+ private const val FUNDS_FOUND_DATE_UNDEFINED = -1L
+ private const val DAY_MILLIS = 1000 * 60 * 60 * 24
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/PreferencesDataSource.kt
similarity index 60%
rename from app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt
rename to data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/PreferencesDataSource.kt
index 5a2fddd325..ec8b43876b 100644
--- a/app/src/main/java/com/tangem/tap/persistence/PreferencesStorage.kt
+++ b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/PreferencesDataSource.kt
@@ -1,14 +1,21 @@
-package com.tangem.tap.persistence
+package com.tangem.data.source.preferences
-import android.app.Application
import android.content.Context
import android.content.SharedPreferences
import androidx.core.content.edit
import com.tangem.common.json.MoshiJsonConverter
-import com.tangem.datasource.api.common.BigDecimalAdapter
-import java.util.*
+import com.tangem.data.source.preferences.adapters.BigDecimalAdapter
+import com.tangem.data.source.preferences.adapters.CardBalanceStateAdapter
+import com.tangem.data.source.preferences.storage.DisclaimerPrefStorage
+import com.tangem.data.source.preferences.storage.FiatCurrenciesPrefStorage
+import com.tangem.data.source.preferences.storage.ToppedUpWalletStorage
+import com.tangem.data.source.preferences.storage.UsedCardsPrefStorage
+import javax.inject.Inject
-class PreferencesStorage(applicationContext: Application) {
+// 🔥FIXME: Only logic to work with preferences must be here, must be separated to repositories
+// TODO: Replace shared preferences with DataStore
+@Deprecated("Create repository instead")
+class PreferencesDataSource @Inject internal constructor(applicationContext: Context) {
val appRatingLaunchObserver: AppRatingLaunchObserver
val usedCardsPrefStorage: UsedCardsPrefStorage
@@ -73,8 +80,6 @@ class PreferencesStorage(applicationContext: Application) {
putBoolean(OPEN_WELCOME_ON_RESUME_KEY, value)
}
- fun getCountOfLaunches(): Int = preferences.getInt(APP_LAUNCH_COUNT_KEY, 1)
-
fun saveTwinsOnboardingShown() {
preferences.edit { putBoolean(TWINS_ONBOARDING_SHOWN_KEY, true) }
}
@@ -83,6 +88,8 @@ class PreferencesStorage(applicationContext: Application) {
return preferences.getBoolean(TWINS_ONBOARDING_SHOWN_KEY, false)
}
+ private fun getCountOfLaunches(): Int = preferences.getInt(APP_LAUNCH_COUNT_KEY, 1)
+
private fun incrementLaunchCounter() {
var count = preferences.getInt(APP_LAUNCH_COUNT_KEY, 0)
preferences.edit { putInt(APP_LAUNCH_COUNT_KEY, ++count) }
@@ -100,72 +107,4 @@ class PreferencesStorage(applicationContext: Application) {
private const val APPLICATION_STOPPED_KEY = "applicationStopped"
private const val OPEN_WELCOME_ON_RESUME_KEY = "openWelcomeOnResume"
}
-}
-
-class AppRatingLaunchObserver(
- private val preferences: SharedPreferences,
- private val launchCounts: Int,
-) {
-
- private val deferShowing = 20
- private val firstShowing = 3
- private var fundsFoundDate: Calendar? = null
-
- init {
- val msWhenFundsWasFound = preferences.getLong(K_FUNDS_FOUND_DATE, FUNDS_FOUND_DATE_UNDEFINED)
- if (msWhenFundsWasFound != FUNDS_FOUND_DATE_UNDEFINED) {
- fundsFoundDate = Calendar.getInstance().apply { timeInMillis = msWhenFundsWasFound }
- }
- }
-
- fun foundWalletWithFunds() {
- if (fundsFoundDate != null) return
-
- fundsFoundDate = Calendar.getInstance()
- preferences.edit(true) {
- putLong(K_FUNDS_FOUND_DATE, fundsFoundDate!!.timeInMillis).apply()
- putInt(K_SHOW_RATING_AT_LAUNCH_COUNT, launchCounts + firstShowing)
- }
- }
-
- @Suppress("MagicNumber")
- fun isReadyToShow(): Boolean {
- val fundsDate = fundsFoundDate ?: return false
-
- if (!userWasInteractWithRating()) {
- val diff = Calendar.getInstance().timeInMillis - fundsDate.timeInMillis
- val diffInDays = diff / (1000 * 60 * 60 * 24)
- return launchCounts >= getCounterOfNextShowing() && diffInDays >= firstShowing
- }
-
- val nextShowing = getCounterOfNextShowing()
- return launchCounts >= nextShowing
- }
-
- fun applyDelayedShowing() {
- updateNextShowing(launchCounts + deferShowing)
- }
-
- @Suppress("MagicNumber")
- fun setNeverToShow() {
- updateNextShowing(999999999)
- }
-
- private fun updateNextShowing(at: Int) {
- val editor = preferences.edit()
- editor.putInt(K_SHOW_RATING_AT_LAUNCH_COUNT, at)
- editor.putBoolean(K_USER_WAS_INTERACT_WITH_RATING, true)
- editor.apply()
- }
-
- private fun userWasInteractWithRating(): Boolean = preferences.getBoolean(K_USER_WAS_INTERACT_WITH_RATING, false)
-
- private fun getCounterOfNextShowing(): Int = preferences.getInt(K_SHOW_RATING_AT_LAUNCH_COUNT, firstShowing)
-
- companion object {
- private const val K_SHOW_RATING_AT_LAUNCH_COUNT = "showRatingDialogAtLaunchCount"
- private const val K_FUNDS_FOUND_DATE = "fundsFoundDate"
- private const val K_USER_WAS_INTERACT_WITH_RATING = "userWasInteractWithRating"
- private const val FUNDS_FOUND_DATE_UNDEFINED = -1L
- }
}
\ No newline at end of file
diff --git a/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/adapters/BigDecimalAdapter.kt b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/adapters/BigDecimalAdapter.kt
new file mode 100644
index 0000000000..d2adc38e1d
--- /dev/null
+++ b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/adapters/BigDecimalAdapter.kt
@@ -0,0 +1,13 @@
+package com.tangem.data.source.preferences.adapters
+
+import com.squareup.moshi.FromJson
+import com.squareup.moshi.ToJson
+import java.math.BigDecimal
+
+class BigDecimalAdapter {
+ @FromJson
+ fun fromJson(value: String) = BigDecimal(value)
+
+ @ToJson
+ fun toJson(value: BigDecimal) = value.toString()
+}
\ No newline at end of file
diff --git a/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/adapters/CardBalanceStateAdapter.kt b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/adapters/CardBalanceStateAdapter.kt
new file mode 100644
index 0000000000..6f9ca6f810
--- /dev/null
+++ b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/adapters/CardBalanceStateAdapter.kt
@@ -0,0 +1,20 @@
+package com.tangem.data.source.preferences.adapters
+
+import com.squareup.moshi.FromJson
+import com.squareup.moshi.ToJson
+import com.tangem.data.source.preferences.model.DataSourceTopupInfo
+
+class CardBalanceStateAdapter {
+
+ @ToJson
+ fun toJson(src: DataSourceTopupInfo.CardBalanceState): String = src.serializedName
+
+ @FromJson
+ fun fromJson(json: String): DataSourceTopupInfo.CardBalanceState {
+ return when (json) {
+ DataSourceTopupInfo.CardBalanceState.Empty.serializedName -> DataSourceTopupInfo.CardBalanceState.Empty
+ DataSourceTopupInfo.CardBalanceState.Full.serializedName -> DataSourceTopupInfo.CardBalanceState.Full
+ else -> error("CardBalanceState not found")
+ }
+ }
+}
\ No newline at end of file
diff --git a/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/di/PreferencesStoreModule.kt b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/di/PreferencesStoreModule.kt
new file mode 100644
index 0000000000..5961efde84
--- /dev/null
+++ b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/di/PreferencesStoreModule.kt
@@ -0,0 +1,19 @@
+package com.tangem.data.source.preferences.di
+
+import android.content.Context
+import com.tangem.data.source.preferences.PreferencesDataSource
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+internal object PreferencesStoreModule {
+
+ @Provides
+ @Singleton
+ fun providePreferencesStore(@ApplicationContext context: Context) = PreferencesDataSource(context)
+}
\ No newline at end of file
diff --git a/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/model/DataSourceCurrency.kt b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/model/DataSourceCurrency.kt
new file mode 100644
index 0000000000..3a2efbcd50
--- /dev/null
+++ b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/model/DataSourceCurrency.kt
@@ -0,0 +1,10 @@
+package com.tangem.data.source.preferences.model
+
+data class DataSourceCurrency(
+ val id: String,
+ val code: String,
+ val name: String,
+ val rateBTC: String,
+ val unit: String,
+ val type: String,
+)
\ No newline at end of file
diff --git a/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/model/DataSourceFiatCurrency.kt b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/model/DataSourceFiatCurrency.kt
new file mode 100644
index 0000000000..9497c85988
--- /dev/null
+++ b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/model/DataSourceFiatCurrency.kt
@@ -0,0 +1,7 @@
+package com.tangem.data.source.preferences.model
+
+data class DataSourceFiatCurrency(
+ val code: String,
+ val name: String,
+ val symbol: String,
+)
\ No newline at end of file
diff --git a/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/model/DataSourceTopupInfo.kt b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/model/DataSourceTopupInfo.kt
new file mode 100644
index 0000000000..0088d25b3e
--- /dev/null
+++ b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/model/DataSourceTopupInfo.kt
@@ -0,0 +1,13 @@
+package com.tangem.data.source.preferences.model
+
+data class DataSourceTopupInfo(
+ val walletId: String,
+ val cardBalanceState: CardBalanceState,
+) {
+ enum class CardBalanceState(val serializedName: String) {
+ Empty(serializedName = "Empty"),
+ Full(serializedName = "Full"),
+ CustomToken(serializedName = "Custom token"),
+ BlockchainError(serializedName = "Blockchain error"),
+ }
+}
\ No newline at end of file
diff --git a/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/model/DataSourceUsedCardInfo.kt b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/model/DataSourceUsedCardInfo.kt
new file mode 100644
index 0000000000..d05eadc7ca
--- /dev/null
+++ b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/model/DataSourceUsedCardInfo.kt
@@ -0,0 +1,8 @@
+package com.tangem.data.source.preferences.model
+
+internal data class DataSourceUsedCardInfo(
+ val cardId: String,
+ val isScanned: Boolean = false,
+ val isActivationStarted: Boolean = false,
+ val isActivationFinished: Boolean = false,
+)
\ No newline at end of file
diff --git a/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/model/DataSourceUsedCardInfoOld.kt b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/model/DataSourceUsedCardInfoOld.kt
new file mode 100644
index 0000000000..ee01e6752a
--- /dev/null
+++ b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/model/DataSourceUsedCardInfoOld.kt
@@ -0,0 +1,7 @@
+package com.tangem.data.source.preferences.model
+
+internal data class DataSourceUsedCardInfoOld(
+ val cardId: String,
+ val isScanned: Boolean = false,
+ val isActivationStarted: Boolean = false,
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/persistence/DisclaimerPrefStorage.kt b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/storage/DisclaimerPrefStorage.kt
similarity index 73%
rename from app/src/main/java/com/tangem/tap/persistence/DisclaimerPrefStorage.kt
rename to data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/storage/DisclaimerPrefStorage.kt
index 34dbf530a3..e91fb9e27c 100644
--- a/app/src/main/java/com/tangem/tap/persistence/DisclaimerPrefStorage.kt
+++ b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/storage/DisclaimerPrefStorage.kt
@@ -1,4 +1,4 @@
-package com.tangem.tap.persistence
+package com.tangem.data.source.preferences.storage
import android.content.SharedPreferences
import androidx.core.content.edit
@@ -6,7 +6,8 @@ import androidx.core.content.edit
/**
[REDACTED_AUTHOR]
*/
-class DisclaimerPrefStorage(
+@Deprecated("Create repository instead")
+class DisclaimerPrefStorage internal constructor(
private val preferences: SharedPreferences,
) {
diff --git a/app/src/main/java/com/tangem/tap/persistence/FiatCurrenciesPrefStorage.kt b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/storage/FiatCurrenciesPrefStorage.kt
similarity index 65%
rename from app/src/main/java/com/tangem/tap/persistence/FiatCurrenciesPrefStorage.kt
rename to data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/storage/FiatCurrenciesPrefStorage.kt
index a52f38f854..e30fdbbe48 100644
--- a/app/src/main/java/com/tangem/tap/persistence/FiatCurrenciesPrefStorage.kt
+++ b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/storage/FiatCurrenciesPrefStorage.kt
@@ -1,15 +1,16 @@
-package com.tangem.tap.persistence
+package com.tangem.data.source.preferences.storage
import android.content.SharedPreferences
import androidx.core.content.edit
import com.tangem.common.json.MoshiJsonConverter
-import com.tangem.datasource.api.tangemTech.models.CurrenciesResponse
-import com.tangem.tap.common.entities.FiatCurrency
+import com.tangem.data.source.preferences.model.DataSourceCurrency
+import com.tangem.data.source.preferences.model.DataSourceFiatCurrency
/**
[REDACTED_AUTHOR]
*/
-class FiatCurrenciesPrefStorage(
+@Deprecated("Create repository instead")
+class FiatCurrenciesPrefStorage internal constructor(
private val preferences: SharedPreferences,
private val converter: MoshiJsonConverter,
) {
@@ -20,26 +21,26 @@ class FiatCurrenciesPrefStorage(
}
}
- fun getAppCurrency(): FiatCurrency {
+ fun getAppCurrency(): DataSourceFiatCurrency? {
val json = preferences.getString(APP_CURRENCY_KEY, "")
- if (json.isNullOrBlank()) return FiatCurrency.Default
+ if (json.isNullOrBlank()) return null
- return converter.fromJson(json) ?: FiatCurrency.Default
+ return converter.fromJson(json)
}
- fun saveAppCurrency(fiatCurrency: FiatCurrency) {
+ fun saveAppCurrency(fiatCurrency: DataSourceFiatCurrency) {
val json = converter.toJson(fiatCurrency)
preferences.edit { putString(APP_CURRENCY_KEY, json) }
}
- fun save(currencies: List) {
+ fun save(currencies: List) {
val json: String = converter.toJson(currencies)
return preferences.edit().putString(FIAT_CURRENCIES_KEY, json).apply()
}
- fun restore(): List {
+ fun restore(): List {
val json = preferences.getString(FIAT_CURRENCIES_KEY, "")
- val type = converter.typedList(CurrenciesResponse.Currency::class.java)
+ val type = converter.typedList(DataSourceCurrency::class.java)
if (json.isNullOrBlank()) return emptyList()
return converter.fromJson(json, type) ?: emptyList()
diff --git a/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/storage/Migration.kt b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/storage/Migration.kt
new file mode 100644
index 0000000000..e1cc25e4ba
--- /dev/null
+++ b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/storage/Migration.kt
@@ -0,0 +1,5 @@
+package com.tangem.data.source.preferences.storage
+
+internal interface Migration {
+ fun migrate()
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/persistence/ToppedUpWalletStorage.kt b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/storage/ToppedUpWalletStorage.kt
similarity index 56%
rename from app/src/main/java/com/tangem/tap/persistence/ToppedUpWalletStorage.kt
rename to data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/storage/ToppedUpWalletStorage.kt
index 892b1a2370..14b1e77b7f 100644
--- a/app/src/main/java/com/tangem/tap/persistence/ToppedUpWalletStorage.kt
+++ b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/storage/ToppedUpWalletStorage.kt
@@ -1,51 +1,50 @@
-package com.tangem.tap.persistence
+package com.tangem.data.source.preferences.storage
import android.content.SharedPreferences
import androidx.core.content.edit
import com.tangem.common.json.MoshiJsonConverter
-import com.tangem.tap.common.analytics.events.AnalyticsParam
-import timber.log.Timber
+import com.tangem.data.source.preferences.model.DataSourceTopupInfo
/**
[REDACTED_AUTHOR]
*/
-class ToppedUpWalletStorage(
+@Deprecated("Create repository instead")
+class ToppedUpWalletStorage internal constructor(
private val preferences: SharedPreferences,
private val jsonConverter: MoshiJsonConverter,
) {
- private val walletList: MutableSet = mutableSetOf()
+ private val walletList: MutableSet = mutableSetOf()
init {
walletList.addAll(restore())
}
- fun save(userWalletInfo: TopupInfo): Boolean {
+ fun save(userWalletInfo: DataSourceTopupInfo): Boolean {
walletList.removeAll { it.walletId == userWalletInfo.walletId }
walletList.add(userWalletInfo)
return save(walletList)
}
- fun restore(walletId: String): TopupInfo? {
+ fun restore(walletId: String): DataSourceTopupInfo? {
return walletList.firstOrNull { it.walletId == walletId }
}
- private fun save(userWallets: MutableSet): Boolean {
+ private fun save(userWallets: MutableSet): Boolean {
return try {
val json = jsonConverter.toJson(userWallets)
preferences.edit(true) { putString(KEY, json) }
true
} catch (ex: Exception) {
- Timber.e(ex)
false
}
}
- private fun restore(): MutableSet {
+ private fun restore(): MutableSet {
val json = preferences.getString(KEY, null) ?: return mutableSetOf()
return try {
- val typedList = jsonConverter.typedList(TopupInfo::class.java)
- val listData = jsonConverter.fromJson>(json, typedList)!!
+ val typedList = jsonConverter.typedList(DataSourceTopupInfo::class.java)
+ val listData = jsonConverter.fromJson>(json, typedList)!!
listData.toMutableSet()
} catch (ex: Exception) {
preferences.edit(true) { remove(KEY) }
@@ -53,13 +52,6 @@ class ToppedUpWalletStorage(
}
}
- data class TopupInfo(
- val walletId: String,
- val cardBalanceState: AnalyticsParam.CardBalanceState,
- ) {
- val isToppedUp: Boolean = cardBalanceState == AnalyticsParam.CardBalanceState.Full
- }
-
companion object {
private const val KEY = "userWalletsInfo"
}
diff --git a/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/storage/UsedCardsPrefStorage.kt
similarity index 67%
rename from app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt
rename to data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/storage/UsedCardsPrefStorage.kt
index f814eb7e16..b1fbf61361 100644
--- a/app/src/main/java/com/tangem/tap/persistence/UsedCardsPrefStorage.kt
+++ b/data/source/preferences/src/main/kotlin/com/tangem/data/source/preferences/storage/UsedCardsPrefStorage.kt
@@ -1,15 +1,16 @@
-package com.tangem.tap.persistence
+package com.tangem.data.source.preferences.storage
import android.content.SharedPreferences
import androidx.core.content.edit
import com.tangem.common.json.MoshiJsonConverter
-import com.tangem.tap.common.extensions.replaceByOrAdd
-import timber.log.Timber
+import com.tangem.data.source.preferences.model.DataSourceUsedCardInfo
+import com.tangem.data.source.preferences.model.DataSourceUsedCardInfoOld
/**
[REDACTED_AUTHOR]
*/
-class UsedCardsPrefStorage(
+@Deprecated("Create repository instead")
+class UsedCardsPrefStorage internal constructor(
private val preferences: SharedPreferences,
private val jsonConverter: MoshiJsonConverter,
) {
@@ -26,7 +27,7 @@ class UsedCardsPrefStorage(
fun scanned(cardId: String) {
val restoredList = restore()
val foundItem = findCardInfo(cardId, restoredList)?.copy(isScanned = true)
- ?: UsedCardInfo(cardId, true)
+ ?: DataSourceUsedCardInfo(cardId, true)
save(foundItem, restoredList)
}
@@ -38,14 +39,14 @@ class UsedCardsPrefStorage(
fun activationStarted(cardId: String) {
val restoredList = restore()
val foundItem = findCardInfo(cardId, restoredList)?.copy(isActivationStarted = true)
- ?: UsedCardInfo(cardId, isActivationStarted = true)
+ ?: DataSourceUsedCardInfo(cardId, isActivationStarted = true)
save(foundItem, restoredList)
}
fun activationFinished(cardId: String) {
val restoredList = restore()
- var foundItem = findCardInfo(cardId, restoredList) ?: UsedCardInfo(cardId)
+ var foundItem = findCardInfo(cardId, restoredList) ?: DataSourceUsedCardInfo(cardId)
foundItem = foundItem.copy(
isActivationStarted = true,
isActivationFinished = true,
@@ -67,33 +68,38 @@ class UsedCardsPrefStorage(
return cardInfo.isActivationStarted && !cardInfo.isActivationFinished
}
- private fun findCardInfo(cardId: String, list: MutableList? = null): UsedCardInfo? {
+ private fun findCardInfo(
+ cardId: String,
+ list: MutableList? = null,
+ ): DataSourceUsedCardInfo? {
val findInList = list ?: restore()
return findInList.firstOrNull { it.cardId == cardId }
}
- private fun save(usedCardInfo: UsedCardInfo?, usedCardsInfo: MutableList) {
+ private fun save(usedCardInfo: DataSourceUsedCardInfo?, usedCardsInfo: MutableList) {
val info = usedCardInfo ?: return
- usedCardsInfo.replaceByOrAdd(info) { it.cardId == info.cardId }
+ with(usedCardsInfo) {
+ val index = indexOfFirst { it.cardId == info.cardId }
+ if (index == -1) {
+ add(info)
+ } else {
+ set(index, info)
+ }
+ }
+
save(usedCardsInfo)
}
- private fun save(list: MutableList): Boolean {
- return try {
- val json = jsonConverter.toJson(list)
- preferences.edit { putString(USED_CARDS_INFO_V2, json) }
- true
- } catch (ex: Exception) {
- Timber.e(ex)
- false
- }
+ private fun save(list: MutableList) {
+ val json = jsonConverter.toJson(list)
+ preferences.edit { putString(USED_CARDS_INFO_V2, json) }
}
- private fun restore(): MutableList {
+ private fun restore(): MutableList {
val json = preferences.getString(USED_CARDS_INFO_V2, null) ?: return mutableListOf()
return try {
- jsonConverter.fromJson(json, jsonConverter.typedList(UsedCardInfo::class.java))!!
+ jsonConverter.fromJson(json, jsonConverter.typedList(DataSourceUsedCardInfo::class.java))!!
} catch (ex: Exception) {
preferences.edit(true) { remove(USED_CARDS_INFO_V2) }
mutableListOf()
@@ -113,7 +119,7 @@ class UsedCardsPrefStorage(
if (restoredCardsInfo.isEmpty()) return
val newCardsInfo = restoredCardsInfo.map { cardInfo ->
- UsedCardInfo(
+ DataSourceUsedCardInfo(
cardId = cardInfo.cardId,
isScanned = cardInfo.isScanned,
isActivationStarted = true,
@@ -123,10 +129,13 @@ class UsedCardsPrefStorage(
storage.save(newCardsInfo)
}
- private fun restore(): MutableList {
+ private fun restore(): MutableList {
val json = storage.preferences.getString(USED_CARDS_INFO, null) ?: return mutableListOf()
return try {
- storage.jsonConverter.fromJson(json, storage.jsonConverter.typedList(UsedCardInfoOld::class.java))!!
+ storage.jsonConverter.fromJson(
+ json,
+ storage.jsonConverter.typedList(DataSourceUsedCardInfoOld::class.java),
+ )!!
} catch (ex: Exception) {
mutableListOf()
} finally {
@@ -134,21 +143,4 @@ class UsedCardsPrefStorage(
}
}
}
-
- private data class UsedCardInfo(
- val cardId: String,
- val isScanned: Boolean = false,
- val isActivationStarted: Boolean = false,
- val isActivationFinished: Boolean = false,
- )
-
- private data class UsedCardInfoOld(
- val cardId: String,
- val isScanned: Boolean = false,
- val isActivationStarted: Boolean = false,
- )
-}
-
-private interface Migration {
- fun migrate()
}
\ No newline at end of file
diff --git a/domain/legacy/src/main/java/com/tangem/domain/DomainWrapped.kt b/domain/legacy/src/main/java/com/tangem/domain/DomainWrapped.kt
index a94866c343..902244560d 100644
--- a/domain/legacy/src/main/java/com/tangem/domain/DomainWrapped.kt
+++ b/domain/legacy/src/main/java/com/tangem/domain/DomainWrapped.kt
@@ -1,13 +1,12 @@
package com.tangem.domain
-import com.tangem.blockchain.common.DerivationStyle
-
/**
[REDACTED_AUTHOR]
* Provides a temporary copies of the app module classes, data structures, etc.
*/
// TODO: refactoring: : after refactoring they should be unwrapped and moved
// to appropriate parts of module
+@Deprecated("After refactoring they should be unwrapped and moved to appropriate parts of module")
sealed interface DomainWrapped {
// Mirror reflection ot the com.tangem.tap.features.wallet.redux.Currency
@@ -30,10 +29,5 @@ sealed interface DomainWrapped {
) : Currency {
override val currencySymbol: String = blockchain.currency
}
-
- fun isCustomCurrency(derivationStyle: DerivationStyle?): Boolean {
- if (derivationPath == null || derivationStyle == null) return false
- return derivationPath != blockchain.derivationPath(derivationStyle)?.rawPath
- }
}
}
\ No newline at end of file
diff --git a/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt b/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt
index fe9b1dfc51..92d51a1c57 100644
--- a/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt
+++ b/domain/legacy/src/main/java/com/tangem/domain/common/extensions/Blockchain.kt
@@ -56,6 +56,8 @@ fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? {
"kava/test" -> Blockchain.KavaTestnet
"ravencoin" -> Blockchain.Ravencoin
"ravencoin/test" -> Blockchain.RavencoinTestnet
+ "cosmos" -> Blockchain.Cosmos
+ "cosmos/test" -> Blockchain.CosmosTestnet
else -> null
}
}
@@ -116,6 +118,8 @@ fun Blockchain.toNetworkId(): String {
Blockchain.KavaTestnet -> "kava/test"
Blockchain.Ravencoin -> "ravencoin"
Blockchain.RavencoinTestnet -> "ravencoin/test"
+ Blockchain.Cosmos -> "cosmos"
+ Blockchain.CosmosTestnet -> "cosmos/test"
}
}
@@ -154,6 +158,7 @@ fun Blockchain.toCoinId(): String {
Blockchain.Unknown -> "unknown"
Blockchain.Kava, Blockchain.KavaTestnet -> "kava"
Blockchain.Ravencoin, Blockchain.RavencoinTestnet -> "ravencoin"
+ Blockchain.Cosmos, Blockchain.CosmosTestnet -> "cosmos"
}
}
diff --git a/domain/legacy/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt b/domain/legacy/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt
index 17f110b952..30085691d3 100644
--- a/domain/legacy/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt
+++ b/domain/legacy/src/main/java/com/tangem/domain/features/addCustomToken/redux/AddCustomTokenHub.kt
@@ -251,11 +251,9 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT
when (state.getCustomTokenType()) {
CustomTokenType.Blockchain -> {
warningsRemove.add(UnsupportedSolanaToken)
- if (alreadyAdded) {
- warningsAdd.add(TokenAlreadyAdded)
- } else {
- warningsRemove.add(TokenAlreadyAdded)
- }
+
+ if (alreadyAdded) warningsAdd.add(TokenAlreadyAdded) else warningsRemove.add(TokenAlreadyAdded)
+
if (state.derivationPathIsSelected()) {
warningsAdd.add(PotentialScamToken)
} else {
@@ -266,12 +264,11 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT
if (tokenIsSupported) {
warningsRemove.add(UnsupportedSolanaToken)
} else {
- val error = ContractAddress.validateValue(ContractAddress.getFieldValue())
- when (error) {
- AddCustomTokenError.FieldIsEmpty -> warningsRemove.add(UnsupportedSolanaToken)
- else -> {
- warningsAdd.add(UnsupportedSolanaToken)
- }
+ val validationResult = ContractAddress.validateValue(ContractAddress.getFieldValue())
+ if (validationResult == AddCustomTokenError.FieldIsEmpty) {
+ warningsRemove.add(UnsupportedSolanaToken)
+ } else {
+ warningsAdd.add(UnsupportedSolanaToken)
}
}
@@ -329,12 +326,7 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT
// blockchain
else -> {
if (state.networkIsSelected()) {
- val alreadyAdded = isBlockchainPersistIntoAppSavedTokensList()
- if (alreadyAdded) {
- disableAddButton()
- } else {
- enableAddButton()
- }
+ if (isBlockchainPersistIntoAppSavedTokensList()) disableAddButton() else enableAddButton()
} else {
disableAddButton()
}
@@ -369,18 +361,18 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT
CustomTokenType.Token -> isTokenPersistIntoAppSavedTokensList()
}
- private fun isTokenPersistIntoAppSavedTokensList(
- tokenId: String? = hubState.foundToken?.id,
- tokenContractAddress: String = ContractAddress.getFieldValue(),
- tokenNetworkId: String = Network.getFieldValue().toNetworkId(),
- selectedDerivation: Blockchain = DerivationPath.getFieldValue(),
- ): Boolean {
+ private fun isTokenPersistIntoAppSavedTokensList(): Boolean {
val savedCurrencies = hubState.appSavedCurrencies ?: return false
+ val tokenId = hubState.foundToken?.id
+ val tokenContractAddress = ContractAddress.getFieldValue()
+ val tokenNetworkId = Network.getFieldValue().toNetworkId()
+ val selectedDerivation = DerivationPath.getFieldValue()
+
val derivationPath = getDerivationPathFromSelectedBlockchain(selectedDerivation)
savedCurrencies.forEach { wrappedCurrency ->
when (wrappedCurrency) {
- is DomainWrapped.Currency.Blockchain -> {}
+ is DomainWrapped.Currency.Blockchain -> Unit
is DomainWrapped.Currency.Token -> {
val sameId = tokenId == wrappedCurrency.token.id
val sameAddress = tokenContractAddress == wrappedCurrency.token.contractAddress
@@ -396,14 +388,12 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT
return false
}
- private fun isBlockchainPersistIntoAppSavedTokensList(
- selectedNetwork: Blockchain = Network.getFieldValue(),
- selectedDerivation: Blockchain = DerivationPath.getFieldValue(),
- ): Boolean {
- val state = hubState
- val savedCurrencies = state.appSavedCurrencies ?: return false
-
+ private fun isBlockchainPersistIntoAppSavedTokensList(): Boolean {
+ val savedCurrencies = hubState.appSavedCurrencies ?: return false
+ val selectedNetwork = Network.getFieldValue()
+ val selectedDerivation = DerivationPath.getFieldValue()
val derivationPath = getDerivationPathFromSelectedBlockchain(selectedDerivation)
+
savedCurrencies.forEach { wrappedCurrency ->
when (wrappedCurrency) {
is DomainWrapped.Currency.Blockchain -> {
@@ -411,7 +401,8 @@ internal class AddCustomTokenHub : BaseStoreHub("AddCustomT
val isSameDerivationPath = derivationPath?.rawPath == wrappedCurrency.derivationPath
if (isSameBlockchain && isSameDerivationPath) return true
}
- is DomainWrapped.Currency.Token -> {}
+
+ is DomainWrapped.Currency.Token -> Unit
}
}
return false
diff --git a/features/wallet/api/.gitignore b/features/wallet/api/.gitignore
new file mode 100644
index 0000000000..42afabfd2a
--- /dev/null
+++ b/features/wallet/api/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/features/wallet/api/build.gradle.kts b/features/wallet/api/build.gradle.kts
new file mode 100644
index 0000000000..1fbece85d5
--- /dev/null
+++ b/features/wallet/api/build.gradle.kts
@@ -0,0 +1,5 @@
+plugins {
+ alias(deps.plugins.android.library)
+ alias(deps.plugins.kotlin.android)
+ id("configuration")
+}
\ No newline at end of file
diff --git a/features/wallet/api/src/main/AndroidManifest.xml b/features/wallet/api/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..85a7d6c7c5
--- /dev/null
+++ b/features/wallet/api/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/features/wallet/impl/.gitignore b/features/wallet/impl/.gitignore
new file mode 100644
index 0000000000..42afabfd2a
--- /dev/null
+++ b/features/wallet/impl/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts
new file mode 100644
index 0000000000..873d96fbe0
--- /dev/null
+++ b/features/wallet/impl/build.gradle.kts
@@ -0,0 +1,36 @@
+plugins {
+ alias(deps.plugins.android.library)
+ alias(deps.plugins.kotlin.android)
+ alias(deps.plugins.kotlin.kapt)
+ alias(deps.plugins.hilt.android)
+ id("configuration")
+}
+
+dependencies {
+ /** AndroidX */
+ implementation(deps.androidx.activity.compose)
+
+ /** Compose */
+ implementation(deps.compose.coil)
+ implementation(deps.compose.constraintLayout)
+ implementation(deps.compose.material)
+ implementation(deps.compose.foundation)
+ implementation(deps.compose.navigation)
+ implementation(deps.compose.navigation.hilt)
+ implementation(deps.compose.ui)
+ implementation(deps.compose.ui.tooling)
+ implementation(deps.compose.shimmer)
+ implementation(deps.compose.accompanist.systemUiController)
+
+ /** DI */
+ implementation(deps.hilt.android)
+ kapt(deps.hilt.kapt)
+
+ /** Core modules */
+ implementation(project(":core:featuretoggles"))
+ implementation(project(":core:ui"))
+
+ /** Feature Apis */
+ implementation(project(":features:wallet:api"))
+
+}
\ No newline at end of file
diff --git a/features/wallet/impl/src/main/AndroidManifest.xml b/features/wallet/impl/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..a1a96bfa00
--- /dev/null
+++ b/features/wallet/impl/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
\ No newline at end of file
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/Tokens.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/Tokens.kt
new file mode 100644
index 0000000000..6d178bdab6
--- /dev/null
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/Tokens.kt
@@ -0,0 +1,355 @@
+package com.tangem.feature.wallet.presentation.ui
+
+import androidx.annotation.DrawableRes
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.Icon
+import androidx.compose.material.Surface
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.constraintlayout.compose.ConstraintLayout
+import androidx.constraintlayout.compose.Dimension
+import coil.compose.SubcomposeAsyncImage
+import coil.request.ImageRequest
+import com.tangem.core.ui.R
+import com.tangem.core.ui.components.*
+import com.tangem.core.ui.res.TangemTheme
+import com.tangem.core.ui.res.TangemTypography
+import com.tangem.feature.wallet.presentation.ui.config.PriceChange
+import com.tangem.feature.wallet.presentation.ui.config.PriceChangeType
+import com.tangem.feature.wallet.presentation.ui.config.TokenV2Config
+import com.tangem.feature.wallet.presentation.ui.state.TokenOptionsUIState
+import com.tangem.feature.wallet.presentation.ui.state.TokenUIState
+
+private const val DOTS = "•••"
+
+@Composable
+fun TokenItemV2(config: TokenV2Config) {
+ when (config.tokenUIState) {
+ TokenUIState.LOADED -> LoadedTokenState(config)
+ TokenUIState.LOADING -> LoadingTokenState()
+ }
+}
+
+@Composable
+private fun LoadedTokenState(config: TokenV2Config) {
+ BaseSurface {
+ ConstraintLayout(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(
+ horizontal = TangemTheme.dimens.spacing12,
+ vertical = TangemTheme.dimens.spacing4,
+ ),
+ ) {
+ val (iconItem, tokenNameItem, fiatItem) = createRefs()
+ TokenIcon(
+ modifier = Modifier.constrainAs(iconItem) {
+ centerVerticallyTo(parent)
+ start.linkTo(parent.start)
+ },
+ tokenIconUrl = config.iconUrl,
+ icon = config.icon,
+ networkIconRes = config.networkIcon,
+ )
+ TokenTitleAmountBlock(
+ modifier = Modifier
+ .padding(horizontal = TangemTheme.dimens.spacing12)
+ .constrainAs(tokenNameItem) {
+ centerVerticallyTo(parent)
+ start.linkTo(iconItem.end)
+ end.linkTo(fiatItem.start)
+ width = Dimension.fillToConstraints
+ },
+ title = config.name,
+ amount = config.amount,
+ hasPending = config.hasPending,
+ )
+ TokenOptionsBlock(
+ config = config,
+ modifier = Modifier.constrainAs(fiatItem) {
+ centerVerticallyTo(parent)
+ end.linkTo(parent.end)
+ },
+ )
+ }
+ }
+}
+
+/**
+ * Block for end part of token item
+ * shows status is reachable, is drag, hidden or show balance
+ */
+@Composable
+private fun TokenOptionsBlock(config: TokenV2Config, modifier: Modifier = Modifier) {
+ when (config.tokenOptionsUIState) {
+ TokenOptionsUIState.VISIBLE -> TokenFiatPercentageBlock(
+ modifier = modifier,
+ fiatAmount = config.fiatAmount,
+ priceChange = config.priceChange,
+ )
+ TokenOptionsUIState.UNREACHABLE -> Text(
+ modifier = modifier,
+ text = "Unreachable", // TODO (conform this text)
+ style = TangemTypography.body2,
+ color = TangemTheme.colors.text.tertiary,
+ )
+ TokenOptionsUIState.HIDDEN -> TokenFiatPercentageBlock(
+ modifier = modifier,
+ fiatAmount = DOTS,
+ priceChange = config.priceChange,
+ )
+ TokenOptionsUIState.DRAG -> Icon(
+ modifier = modifier,
+ painter = painterResource(id = R.drawable.ic_drag_24),
+ tint = TangemTheme.colors.icon.informative,
+ contentDescription = null,
+ )
+ }
+}
+
+@Composable
+private fun LoadingTokenState() {
+ BaseSurface {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(
+ horizontal = TangemTheme.dimens.spacing12,
+ vertical = TangemTheme.dimens.spacing4,
+ ),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween,
+ ) {
+ CircleShimmer(modifier = Modifier.size(size = TangemTheme.dimens.size42))
+ SpacerW12()
+ Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) {
+ ShimmerRectangle(
+ modifier = Modifier.size(
+ width = TangemTheme.dimens.size72,
+ height = TangemTheme.dimens.size12,
+ ),
+ )
+ ShimmerRectangle(
+ modifier = Modifier.size(
+ width = TangemTheme.dimens.size50,
+ height = TangemTheme.dimens.size12,
+ ),
+ )
+ }
+ }
+ Column(
+ verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
+ ) {
+ ShimmerRectangle(
+ modifier = Modifier.size(
+ width = TangemTheme.dimens.size40,
+ height = TangemTheme.dimens.size12,
+ ),
+ )
+ ShimmerRectangle(
+ modifier = Modifier.size(
+ width = TangemTheme.dimens.size40,
+ height = TangemTheme.dimens.size12,
+ ),
+ )
+ }
+ }
+}
+
+@OptIn(ExperimentalMaterialApi::class)
+@Composable
+private fun BaseSurface(onClick: (() -> Unit)? = null, content: @Composable () -> Unit) {
+ Surface(
+ modifier = Modifier.defaultMinSize(minHeight = TangemTheme.dimens.size68),
+ color = TangemTheme.colors.background.primary,
+ onClick = onClick ?: {},
+ enabled = onClick != null,
+ ) {
+ content()
+ }
+}
+
+@Composable
+private fun TokenTitleAmountBlock(title: String, amount: String, hasPending: Boolean, modifier: Modifier = Modifier) {
+ Column(
+ modifier = modifier,
+ verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing2),
+ ) {
+ Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) {
+ Text(
+ text = title,
+ style = TangemTypography.subtitle2,
+ color = TangemTheme.colors.text.primary1,
+ )
+ if (hasPending) {
+ Image(
+ modifier = Modifier.align(Alignment.CenterVertically),
+ painter = painterResource(id = R.drawable.img_loader_15),
+ contentDescription = null,
+ )
+ }
+ }
+ Text(
+ text = amount,
+ style = TangemTypography.body2,
+ color = TangemTheme.colors.text.tertiary,
+ )
+ }
+}
+
+@Composable
+private fun TokenFiatPercentageBlock(fiatAmount: String, priceChange: PriceChange, modifier: Modifier = Modifier) {
+ Column(modifier = modifier.requiredWidth(IntrinsicSize.Max)) {
+ Text(
+ modifier = Modifier.align(Alignment.End),
+ text = fiatAmount,
+ style = TangemTypography.body2,
+ color = TangemTheme.colors.text.primary1,
+ )
+ SpacerH2()
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.End,
+ ) {
+ val iconChangeArrow: Int
+ val changeTextColor: Color
+ if (priceChange.type == PriceChangeType.UP) {
+ iconChangeArrow = R.drawable.img_arrow_up_8
+ changeTextColor = TangemTheme.colors.text.accent
+ } else {
+ iconChangeArrow = R.drawable.img_arrow_down_8
+ changeTextColor = TangemTheme.colors.text.warning
+ }
+ Image(
+ modifier = Modifier.align(Alignment.CenterVertically),
+ painter = painterResource(id = iconChangeArrow),
+ contentDescription = null,
+ )
+ SpacerW4()
+ Text(
+ modifier = Modifier.align(Alignment.CenterVertically),
+ text = priceChange.valuePercent,
+ style = TangemTypography.body2,
+ color = changeTextColor,
+ )
+ }
+ }
+}
+
+@Composable
+private fun TokenIcon(
+ tokenIconUrl: String?,
+ modifier: Modifier = Modifier,
+ @DrawableRes icon: Int? = null,
+ @DrawableRes networkIconRes: Int? = null,
+) {
+ Box(
+ modifier = modifier
+ .padding(end = TangemTheme.dimens.spacing16)
+ .size(TangemTheme.dimens.size42),
+ ) {
+ val tokenImageModifier = Modifier
+ .align(Alignment.BottomStart)
+ .size(TangemTheme.dimens.size36)
+
+ val data = if (tokenIconUrl.isNullOrEmpty()) {
+ icon
+ } else {
+ tokenIconUrl
+ }
+ SubcomposeAsyncImage(
+ modifier = tokenImageModifier,
+ model = ImageRequest.Builder(LocalContext.current)
+ .data(data)
+ .crossfade(true)
+ .build(),
+ loading = { CircleShimmer(modifier = tokenImageModifier) },
+ contentDescription = null,
+ )
+
+ if (networkIconRes != null) {
+ Box(
+ modifier = Modifier
+ .align(Alignment.TopEnd)
+ .size(TangemTheme.dimens.size18)
+ .background(color = Color.White, shape = CircleShape),
+ contentAlignment = Alignment.Center,
+ ) {
+ Image(
+ modifier = Modifier.padding(all = 0.5.dp),
+ painter = painterResource(id = networkIconRes),
+ contentDescription = null,
+ )
+ }
+ }
+ }
+}
+
+// region preview
+
+@Composable
+private fun TokensPreview() {
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing18),
+ ) {
+ val config = TokenV2Config(
+ name = "Polygon",
+ amount = "5,412 MATIC",
+ fiatAmount = "321 $",
+ priceChange = PriceChange(
+ valuePercent = "2%",
+ type = PriceChangeType.UP,
+ ),
+ iconUrl = null,
+ icon = R.drawable.img_polygon_22,
+ networkIcon = R.drawable.img_polygon_22,
+ tokenUIState = TokenUIState.LOADED,
+ tokenOptionsUIState = TokenOptionsUIState.VISIBLE,
+ hasPending = true,
+ )
+ // Loaded item
+ TokenItemV2(config)
+ // Unreachable item
+ TokenItemV2(config.copy(tokenOptionsUIState = TokenOptionsUIState.UNREACHABLE))
+ // Drag item
+ TokenItemV2(config.copy(tokenOptionsUIState = TokenOptionsUIState.DRAG))
+ // Hidden item
+ TokenItemV2(config.copy(tokenOptionsUIState = TokenOptionsUIState.UNREACHABLE))
+ // Loading item
+ TokenItemV2(
+ config.copy(
+ tokenUIState = TokenUIState.LOADING,
+ ),
+ )
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun Preview_Tokens_InLightTheme() {
+ TangemTheme(isDark = false) {
+ TokensPreview()
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun Preview_Tokens_InDarkTheme() {
+ TangemTheme(isDark = true) {
+ TokensPreview()
+ }
+}
+
+// endregion preview
\ No newline at end of file
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/WalletHeader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/WalletHeader.kt
new file mode 100644
index 0000000000..13eb2f12f3
--- /dev/null
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/WalletHeader.kt
@@ -0,0 +1,193 @@
+package com.tangem.feature.wallet.presentation.ui
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.*
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.Icon
+import androidx.compose.material.Surface
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.sp
+import androidx.constraintlayout.compose.ConstraintLayout
+import androidx.constraintlayout.compose.Dimension
+import com.tangem.core.ui.R
+import com.tangem.core.ui.components.*
+import com.tangem.core.ui.res.TangemTheme
+import com.tangem.feature.wallet.presentation.ui.config.WalletHeaderConfig
+import com.tangem.feature.wallet.presentation.ui.state.BalanceUIState
+
+private const val DOTS = "•••"
+
+@OptIn(ExperimentalMaterialApi::class)
+@Composable
+fun WalletHeaderCard(config: WalletHeaderConfig, modifier: Modifier = Modifier) {
+ Surface(
+ modifier = modifier.defaultMinSize(minHeight = TangemTheme.dimens.size108),
+ shape = TangemTheme.shapes.roundedCornersXMedium,
+ color = TangemTheme.colors.background.primary,
+ onClick = config.onClick ?: {},
+ enabled = config.onClick != null,
+ ) {
+ ConstraintLayout(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = TangemTheme.dimens.spacing12),
+ ) {
+ val (balanceBlock, imageItem) = createRefs()
+ Column(
+ modifier = Modifier.constrainAs(balanceBlock) {
+ centerVerticallyTo(parent)
+ start.linkTo(parent.start)
+ end.linkTo(imageItem.start)
+ width = Dimension.fillToConstraints
+ },
+ verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
+ ) {
+ HeaderWalletName(
+ walletName = config.walletName,
+ balanceState = config.balanceState,
+ )
+ HeaderBalanceTitle(
+ balance = config.balance,
+ balanceState = config.balanceState,
+ )
+ Text(
+ text = config.additionalInfo,
+ color = TangemTheme.colors.text.disabled,
+ style = TangemTheme.typography.caption,
+ )
+ }
+ val imageWidth = TangemTheme.dimens.size120
+ Image(
+ modifier = Modifier.constrainAs(imageItem) {
+ centerVerticallyTo(parent)
+ top.linkTo(parent.top)
+ end.linkTo(parent.end)
+ height = Dimension.fillToConstraints
+ width = Dimension.value(imageWidth)
+ },
+ painter = config.cardImage,
+ contentDescription = null,
+ contentScale = ContentScale.FillWidth,
+ )
+ }
+ }
+}
+
+@Composable
+private fun HeaderWalletName(walletName: String, balanceState: BalanceUIState) {
+ if (balanceState == BalanceUIState.HIDDEN) {
+ Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4)) {
+ Text(
+ text = walletName,
+ color = TangemTheme.colors.text.tertiary,
+ style = TangemTheme.typography.body2,
+ maxLines = 1,
+ )
+ Icon(
+ modifier = Modifier.size(size = TangemTheme.dimens.size20),
+ painter = painterResource(id = R.drawable.ic_eye_off_24),
+ contentDescription = null,
+ tint = TangemTheme.colors.icon.informative,
+ )
+ }
+ } else {
+ Text(
+ text = walletName,
+ color = TangemTheme.colors.text.tertiary,
+ style = TangemTheme.typography.body2,
+ maxLines = 1,
+ )
+ }
+}
+
+@Composable
+private fun HeaderBalanceTitle(balance: String, balanceState: BalanceUIState) {
+ when (balanceState) {
+ BalanceUIState.VISIBLE -> {
+ ResizableText(
+ text = balance,
+ color = TangemTheme.colors.text.primary1,
+ style = TangemTheme.typography.h2,
+ fontSizeRange = FontSizeRange(min = 16.sp, max = TangemTheme.typography.h2.fontSize),
+ modifier = Modifier.defaultMinSize(minHeight = TangemTheme.dimens.size32),
+ )
+ }
+ BalanceUIState.LOADING -> {
+ ShimmerRectangle(
+ modifier = Modifier.size(
+ width = TangemTheme.dimens.size102,
+ height = TangemTheme.dimens.size24,
+ ),
+ )
+ }
+ BalanceUIState.HIDDEN -> {
+ Text(
+ text = DOTS,
+ color = TangemTheme.colors.text.primary1,
+ style = TangemTheme.typography.h2,
+ )
+ }
+ BalanceUIState.NONE -> {
+ Text(
+ text = "—",
+ color = TangemTheme.colors.text.primary1,
+ style = TangemTheme.typography.h2,
+ )
+ }
+ }
+}
+
+// region Preview
+
+@Composable
+private fun WarningsPreview() {
+ val walletHeaderConfig = WalletHeaderConfig(
+ walletName = "Wallet 1",
+ balance = "8923,05 $",
+ additionalInfo = "3 cards • Seed enabled",
+ balanceState = BalanceUIState.VISIBLE,
+ cardImage = painterResource(id = R.drawable.ill_businessman_3d),
+ onClick = {},
+ )
+ Column(modifier = Modifier.fillMaxWidth()) {
+ WalletHeaderCard(
+ config = walletHeaderConfig,
+ )
+ SpacerH32()
+ WalletHeaderCard(
+ config = walletHeaderConfig.copy(balanceState = BalanceUIState.HIDDEN),
+ )
+ SpacerH32()
+ WalletHeaderCard(
+ config = walletHeaderConfig.copy(balanceState = BalanceUIState.LOADING),
+ )
+ SpacerH32()
+ WalletHeaderCard(
+ config = walletHeaderConfig.copy(balanceState = BalanceUIState.NONE),
+ )
+ SpacerH32()
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun Preview_Warning_InLightTheme() {
+ TangemTheme(isDark = false) {
+ WarningsPreview()
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun Preview_Warning_InDarkTheme() {
+ TangemTheme(isDark = true) {
+ WarningsPreview()
+ }
+}
+
+// endregion Preview
\ No newline at end of file
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/config/TokenV2Config.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/config/TokenV2Config.kt
new file mode 100644
index 0000000000..bfa34bae3f
--- /dev/null
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/config/TokenV2Config.kt
@@ -0,0 +1,41 @@
+package com.tangem.feature.wallet.presentation.ui.config
+
+import androidx.annotation.DrawableRes
+import com.tangem.feature.wallet.presentation.ui.state.TokenOptionsUIState
+import com.tangem.feature.wallet.presentation.ui.state.TokenUIState
+
+/**
+ * Token config
+ *
+ * @property name of token/coin
+ * @property amount of token
+ * @property fiatAmount amount in fiat
+ * @property priceChange value of price changing
+ * @property iconUrl
+ * @property icon token/coin icon
+ * @property networkIcon
+ * @property tokenUIState token state [TokenUIState] loading etc.
+ * @property tokenOptionsUIState state for token options like 'unreachable', 'hidden' etc
+ * @property hasPending pending tx in blockchain
+ */
+data class TokenV2Config(
+ val name: String,
+ val amount: String,
+ val fiatAmount: String,
+ val priceChange: PriceChange,
+ val iconUrl: String?,
+ @DrawableRes val icon: Int?,
+ @DrawableRes val networkIcon: Int,
+ val tokenUIState: TokenUIState,
+ val tokenOptionsUIState: TokenOptionsUIState,
+ val hasPending: Boolean,
+)
+
+data class PriceChange(
+ val valuePercent: String,
+ val type: PriceChangeType,
+)
+
+enum class PriceChangeType {
+ UP, DOWN
+}
\ No newline at end of file
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/config/WalletHeaderConfig.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/config/WalletHeaderConfig.kt
new file mode 100644
index 0000000000..4a621be4e5
--- /dev/null
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/config/WalletHeaderConfig.kt
@@ -0,0 +1,13 @@
+package com.tangem.feature.wallet.presentation.ui.config
+
+import androidx.compose.ui.graphics.painter.Painter
+import com.tangem.feature.wallet.presentation.ui.state.BalanceUIState
+
+data class WalletHeaderConfig(
+ val walletName: String,
+ val balance: String,
+ val additionalInfo: String,
+ val balanceState: BalanceUIState,
+ val cardImage: Painter,
+ val onClick: (() -> Unit)?,
+)
\ No newline at end of file
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/state/BalanceUIState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/state/BalanceUIState.kt
new file mode 100644
index 0000000000..c4a22f046c
--- /dev/null
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/state/BalanceUIState.kt
@@ -0,0 +1,5 @@
+package com.tangem.feature.wallet.presentation.ui.state
+
+enum class BalanceUIState {
+ VISIBLE, LOADING, HIDDEN, NONE
+}
\ No newline at end of file
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/state/TokenUIState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/state/TokenUIState.kt
new file mode 100644
index 0000000000..0ef9163e07
--- /dev/null
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/state/TokenUIState.kt
@@ -0,0 +1,9 @@
+package com.tangem.feature.wallet.presentation.ui.state
+
+enum class TokenUIState {
+ LOADED, LOADING
+}
+
+enum class TokenOptionsUIState {
+ VISIBLE, HIDDEN, DRAG, UNREACHABLE
+}
\ No newline at end of file
diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml
index 91c34ec357..f6cde60962 100644
--- a/gradle/dependencies.toml
+++ b/gradle/dependencies.toml
@@ -72,7 +72,7 @@ arrow = "1.2.0-RC"
# endregion Other libraries
# region Tangem
-tangemBlockchainSdk = "release-app_4.5-215"
+tangemBlockchainSdk = "develop-210"
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
tangemCardSdk = "develop-235"
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds
diff --git a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/BaseExtensionConfigurations.kt b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/BaseExtensionConfigurations.kt
index c1af6d55fe..c05a661a44 100644
--- a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/BaseExtensionConfigurations.kt
+++ b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/BaseExtensionConfigurations.kt
@@ -20,10 +20,10 @@ internal fun BaseExtension.configureCompilerOptions() {
internal fun BaseExtension.configureCompose(project: Project) {
val useCompose = with(project.path) {
contains(":ui") ||
- contains(":onboarding") ||
+ contains(":onboarding") || // TODO: divide on api/impl after migrating all onboarding to module
contains(":presentation") ||
contains(":app") || // TODO: [REDACTED_JIRA]
- contains(":tester:impl") // TODO: Rename module
+ contains(":impl")
}
buildFeatures.compose = useCompose
if (useCompose) {
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 70f5aeb9e2..3928077a46 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -57,6 +57,9 @@ include(":features:swap:presentation")
include(":features:tester:api")
include(":features:tester:impl")
+
+include(":features:wallet:api")
+include(":features:wallet:impl")
// endregion Feature modules
// region Domain modules
@@ -66,4 +69,8 @@ include(":domain:legacy")
include(":domain:core")
include(":domain:card")
-// endregion Domain modules
\ No newline at end of file
+// endregion Domain modules
+
+// region Data modules
+include(":data:source:preferences")
+// endregion Data modules
\ No newline at end of file