diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index a15171a939..1876b0038c 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -198,6 +198,7 @@ dependencies {
implementation(Library.armadillo)
implementation(Library.googlePlayServicesWallet)
implementation(Library.composeShimmer)
+ implementation(Library.mviCoreWatcher)
/** Testing libraries */
testImplementation(Test.junit)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index e8a5618a92..e7d11764b4 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -101,6 +101,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt b/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt
index 0bfb76a8ac..426f49ca25 100644
--- a/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt
+++ b/app/src/main/java/com/tangem/tap/LockUserWalletsTimer.kt
@@ -16,7 +16,7 @@ import kotlin.time.Duration
internal class LockUserWalletsTimer(
owner: LifecycleOwner,
- private val duration: Duration = with(Duration) { 5.minutes },
+ private val duration: Duration = with(Duration) { 10.minutes },
) : LifecycleOwner by owner,
DefaultLifecycleObserver {
@@ -25,8 +25,6 @@ internal class LockUserWalletsTimer(
field?.cancel()
field = value
}
- private var isStopped = false
- private var openWelcomeScreenWhenResumed = false
init {
lifecycle.addObserver(this)
@@ -35,22 +33,22 @@ internal class LockUserWalletsTimer(
override fun onResume(owner: LifecycleOwner) {
Timber.d(
"""
- Owner resumed
- |- Was stopped: $isStopped
- |- Need to open welcome screen: $openWelcomeScreenWhenResumed
- """.trimIndent(),
+ Owner resumed
+ |- Was stopped: ${preferencesStorage.wasApplicationStopped}
+ |- Need to open welcome screen: ${preferencesStorage.shouldOpenWelcomeScreenOnResume}
+ """.trimIndent(),
)
- isStopped = false
+ preferencesStorage.wasApplicationStopped = false
start()
- if (openWelcomeScreenWhenResumed) {
+ if (preferencesStorage.shouldOpenWelcomeScreenOnResume) {
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Welcome))
- openWelcomeScreenWhenResumed = false
+ preferencesStorage.shouldOpenWelcomeScreenOnResume = false
}
}
override fun onStop(owner: LifecycleOwner) {
Timber.d("Owner stopped")
- isStopped = true
+ preferencesStorage.wasApplicationStopped = true
}
override fun onDestroy(owner: LifecycleOwner) {
@@ -103,13 +101,13 @@ internal class LockUserWalletsTimer(
Timber.d(
"""
Finished
- |- App is stopped: $isStopped
+ |- App is stopped: ${preferencesStorage.wasApplicationStopped}
|- Millis passed: ${currentTime - startTime}
""".trimIndent(),
)
userWalletsListManager.lock()
- if (isStopped) {
- openWelcomeScreenWhenResumed = true
+ if (preferencesStorage.wasApplicationStopped) {
+ preferencesStorage.shouldOpenWelcomeScreenOnResume = true
} else {
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Welcome))
}
diff --git a/app/src/main/java/com/tangem/tap/common/IntentHandler.kt b/app/src/main/java/com/tangem/tap/common/IntentHandler.kt
index cfee84fd69..4ac3547aa0 100644
--- a/app/src/main/java/com/tangem/tap/common/IntentHandler.kt
+++ b/app/src/main/java/com/tangem/tap/common/IntentHandler.kt
@@ -3,6 +3,7 @@ package com.tangem.tap.common
import android.content.Intent
import android.nfc.NfcAdapter
import android.nfc.Tag
+import com.tangem.tap.common.extensions.removePrefixOrNull
import com.tangem.tap.common.redux.navigation.AppScreen
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.domain.walletconnect.WalletConnectManager
@@ -14,14 +15,20 @@ import timber.log.Timber
class IntentHandler {
- private val TRANSACTION_ID_PARAM = "transactionId"
- private val CURRENCY_CODE_PARAM = "baseCurrencyCode"
- private val CURRENCY_AMOUNT_PARAM = "baseCurrencyAmount"
- private val DEPOSIT_WALLET_ADDRESS_PARAM = "depositWalletAddress"
-
fun handleWalletConnectLink(intent: Intent?) {
- if (intent?.scheme == WalletConnectManager.WC_SCHEME) {
- store.dispatch(WalletConnectAction.HandleDeepLink(intent.data?.toString()))
+ val wcUri = when (intent?.scheme) {
+ WalletConnectManager.WC_SCHEME -> {
+ intent.data?.toString()
+ }
+ TANGEM_SCHEME -> {
+ intent.data?.toString()?.removePrefixOrNull(TANGEM_WC_PREFIX)
+ }
+ else -> {
+ null
+ }
+ }
+ if (wcUri != null) {
+ store.dispatch(WalletConnectAction.HandleDeepLink(wcUri))
}
}
@@ -54,16 +61,25 @@ class IntentHandler {
Timber.d("MoonPay Sell: $amount $currency to $destinationAddress")
- store.dispatch(WalletAction.TradeCryptoAction.SendCrypto(
- currencyId = currency,
- amount = amount,
- destinationAddress = destinationAddress,
- transactionId = transactionID
- ))
+ store.dispatch(
+ WalletAction.TradeCryptoAction.SendCrypto(
+ currencyId = currency,
+ amount = amount,
+ destinationAddress = destinationAddress,
+ transactionId = transactionID,
+ ),
+ )
} catch (exception: Exception) {
Timber.d("Not MoonPay URL")
}
}
-
+ companion object {
+ private const val TRANSACTION_ID_PARAM = "transactionId"
+ private const val CURRENCY_CODE_PARAM = "baseCurrencyCode"
+ private const val CURRENCY_AMOUNT_PARAM = "baseCurrencyAmount"
+ private const val DEPOSIT_WALLET_ADDRESS_PARAM = "depositWalletAddress"
+ private const val TANGEM_SCHEME = "tangem"
+ private const val TANGEM_WC_PREFIX = "tangem://wc?uri="
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt b/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt
index 301ae0cb07..45d55aac2a 100644
--- a/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt
+++ b/app/src/main/java/com/tangem/tap/common/analytics/converters/BasicEventConverter.kt
@@ -25,7 +25,7 @@ class BasicSignInEventConverter(
val cardCurrency = ParamCardCurrencyConverter().convert(scanResponse) ?: return null
return Basic.SignedIn(
- state = AnalyticsParam.CardBalanceState.from(value.walletsData),
+ state = AnalyticsParam.CardBalanceState.from(value.walletsDataFromStores),
currency = cardCurrency,
batch = scanResponse.card.batchId,
).apply {
@@ -44,7 +44,7 @@ class BasicTopUpEventConverter(
val data = BasicTopUpFilter.Data(
walletId = scanResponse.card.userWalletId.stringValue,
- cardBalanceState = AnalyticsParam.CardBalanceState.from(value.walletsData),
+ cardBalanceState = AnalyticsParam.CardBalanceState.from(value.walletsDataFromStores),
)
return Basic.ToppedUp(cardCurrency).apply { filterData = data }
@@ -61,16 +61,16 @@ private fun AnalyticsParam.CardBalanceState.Companion.from(walletsData: List acc + i }
- if (balancesCount != state.wallets.size) return false
+ if (balancesCount != state.walletsStores.size) return false
return true
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/extensions/String.kt b/app/src/main/java/com/tangem/tap/common/extensions/String.kt
index 7d54a7bf92..021cc3a7f2 100644
--- a/app/src/main/java/com/tangem/tap/common/extensions/String.kt
+++ b/app/src/main/java/com/tangem/tap/common/extensions/String.kt
@@ -62,4 +62,9 @@ fun String.toQrCode(): Bitmap {
return bmp
}
-fun String.urlEncode(): String = Uri.encode(this)
\ No newline at end of file
+fun String.urlEncode(): String = Uri.encode(this)
+
+fun String.removePrefixOrNull(prefix: String): String? = when {
+ startsWith(prefix) -> substring(prefix.length)
+ else -> null
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/redux/AccessCodeRequestPolicyMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/AccessCodeRequestPolicyMiddleware.kt
new file mode 100644
index 0000000000..e9aaabfd70
--- /dev/null
+++ b/app/src/main/java/com/tangem/tap/common/redux/AccessCodeRequestPolicyMiddleware.kt
@@ -0,0 +1,27 @@
+package com.tangem.tap.common.redux
+
+import com.tangem.domain.common.ScanResponse
+import com.tangem.tap.common.redux.global.GlobalAction
+import com.tangem.tap.preferencesStorage
+import com.tangem.tap.tangemSdkManager
+import org.rekotlin.Middleware
+
+class AccessCodeRequestPolicyMiddleware {
+ val middleware: Middleware = { _, _ ->
+ { next ->
+ { action ->
+ if (action is GlobalAction.SaveScanResponse) {
+ updateAccessCodeRequestPolicy(action.scanResponse)
+ }
+ next(action)
+ }
+ }
+ }
+
+ private fun updateAccessCodeRequestPolicy(scanResponse: ScanResponse) {
+ tangemSdkManager.setAccessCodeRequestPolicy(
+ useBiometricsForAccessCode = preferencesStorage.shouldSaveAccessCodes &&
+ scanResponse.card.isAccessCodeSet,
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt
index fad0770f65..9f7c34b90f 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/AppState.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/AppState.kt
@@ -93,6 +93,7 @@ data class AppState(
SaveWalletMiddleware().middleware,
WalletSelectorMiddleware().middleware,
LockUserWalletsTimerMiddleware().middleware,
+ AccessCodeRequestPolicyMiddleware().middleware,
)
}
}
diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt
index d4c5620fb3..1f51374cae 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalAction.kt
@@ -64,7 +64,7 @@ sealed class GlobalAction : Action {
object Increment : GlobalAction()
}
- data class SaveScanNoteResponse(val scanResponse: ScanResponse) : GlobalAction()
+ data class SaveScanResponse(val scanResponse: ScanResponse) : GlobalAction()
data class SetIfCardVerifiedOnline(val verified: Boolean) : GlobalAction()
diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt
index f4aa80ad03..543e734f8f 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt
@@ -33,7 +33,7 @@ fun globalReducer(action: Action, state: AppState, appStateHolder: AppStateHolde
is GlobalAction.ScanFailsCounter.Reset -> {
globalState.copy(scanCardFailsCounter = 0)
}
- is GlobalAction.SaveScanNoteResponse -> {
+ is GlobalAction.SaveScanResponse -> {
appStateHolder.scanResponse = action.scanResponse
domainStore.dispatch(DomainGlobalAction.SaveScanNoteResponse(action.scanResponse))
globalState.copy(scanResponse = action.scanResponse)
diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt
index c8457793e5..871ef1ea5c 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationMiddleware.kt
@@ -28,15 +28,17 @@ val navigationMiddleware: Middleware = { _, state ->
)
}
is NavigationAction.PopBackTo -> {
- when (val screen = action.screen) {
- AppScreen.Home,
- AppScreen.Welcome,
- -> {
- navState?.activity?.get()?.popBackTo(screen, inclusive = true)
- store.dispatchOnMain(NavigationAction.NavigateTo(screen))
- }
- else -> {
- navState?.activity?.get()?.popBackTo(screen, action.inclusive)
+ if (navState?.backStack?.lastOrNull() != action.screen) {
+ when (val screen = action.screen) {
+ AppScreen.Home,
+ AppScreen.Welcome,
+ -> {
+ navState?.activity?.get()?.popBackTo(screen, inclusive = true)
+ store.dispatchOnMain(NavigationAction.NavigateTo(screen))
+ }
+ else -> {
+ navState?.activity?.get()?.popBackTo(screen, action.inclusive)
+ }
}
}
}
diff --git a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationReducer.kt
index 7d63cb95ee..b6f7522586 100644
--- a/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationReducer.kt
+++ b/app/src/main/java/com/tangem/tap/common/redux/navigation/NavigationReducer.kt
@@ -20,6 +20,8 @@ private fun internalReduce(action: Action, state: AppState): NavigationState {
navState.copy(backStack = navState.backStack + navigationAction.screen)
}
is NavigationAction.PopBackTo -> {
+ if (navState.backStack.lastOrNull() == navigationAction.screen) return navState
+
val screen = navigationAction.screen ?: navState.activity?.get()?.getPreviousScreen()
val index = navState.backStack.lastIndexOf(screen) + 1
state.navigationState.copy(backStack = navState.backStack.subList(0, index))
diff --git a/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt b/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt
index 271886dea1..808cb5bbbc 100644
--- a/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt
+++ b/app/src/main/java/com/tangem/tap/common/shop/TangemShopService.kt
@@ -208,8 +208,8 @@ class TangemShopService(application: Application, shopifyShop: ShopifyShop) {
}
companion object {
- const val TANGEM_WALLET_2_CARDS_SKU = "TG115x2"
- const val TANGEM_WALLET_3_CARDS_SKU = "TG115x3"
+ const val TANGEM_WALLET_2_CARDS_SKU = "TG115X2-S"
+ const val TANGEM_WALLET_3_CARDS_SKU = "TG115X3-S"
val SKUS_TO_DISPLAY = listOf(TANGEM_WALLET_2_CARDS_SKU, TANGEM_WALLET_3_CARDS_SKU)
}
}
diff --git a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt
index 81ddf880c3..01350c6c63 100644
--- a/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/TangemSdkManager.kt
@@ -46,6 +46,14 @@ import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Context) {
+
+ private val userCodeRepository by lazy {
+ UserCodeRepository(
+ biometricManager = tangemSdk.biometricManager,
+ secureStorage = tangemSdk.secureStorage,
+ )
+ }
+
val canUseBiometry: Boolean
get() = tangemSdk.biometricManager.canAuthenticate || needEnrollBiometrics
@@ -119,8 +127,8 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
}
suspend fun saveAccessCode(accessCode: String, cardsIds: Set): CompletionResult {
- return createUserCodeRepository().save(
- cardIds = cardsIds,
+ return userCodeRepository.save(
+ cardsIds = cardsIds,
userCode = UserCode(
type = UserCodeType.AccessCode,
stringValue = accessCode,
@@ -128,8 +136,12 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
)
}
+ suspend fun deleteSavedUserCodes(cardsIds: Set): CompletionResult {
+ return userCodeRepository.delete(cardsIds.toSet())
+ }
+
suspend fun clearSavedUserCodes(): CompletionResult {
- return createUserCodeRepository().clear()
+ return userCodeRepository.clear()
}
suspend fun setPasscode(cardId: String?): CompletionResult {
@@ -166,9 +178,10 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
suspend fun scanCard(
cardId: String? = null,
+ allowRequestAccessCodeFromRepository: Boolean = false,
): CompletionResult {
return runTaskAsyncReturnOnMain(
- runnable = ScanTask(),
+ runnable = ScanTask(allowRequestAccessCodeFromRepository),
cardId = cardId,
initialMessage = Message(context.getString(R.string.initial_message_tap_header)),
)
@@ -221,13 +234,6 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
}
}
- private fun createUserCodeRepository() = with(tangemSdk) {
- UserCodeRepository(
- biometricManager = biometricManager,
- secureStorage = secureStorage,
- )
- }
-
companion object {
val config = Config(
linkedTerminal = true,
diff --git a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt
index 7eeb396463..ea26d15848 100644
--- a/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/TapWalletManager.kt
@@ -33,6 +33,7 @@ import com.tangem.tap.features.wallet.models.toBlockchainNetworks
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.network.NetworkConnectivity
import com.tangem.tap.store
+import com.tangem.tap.tangemSdkManager
import com.tangem.tap.userTokensRepository
import com.tangem.tap.walletStoresManager
import kotlinx.coroutines.Dispatchers
@@ -94,6 +95,7 @@ class TapWalletManager {
val card = scanResponse.card
val attestationFailed = card.attestation.status == Attestation.Status.Failed
+ tangemSdkManager.changeDisplayedCardIdNumbersCount(scanResponse)
store.state.globalState.feedbackManager?.infoHolder?.setCardInfo(scanResponse)
updateConfigManager(scanResponse)
@@ -101,7 +103,7 @@ class TapWalletManager {
store.dispatch(WalletAction.UserWalletChanged(userWallet))
store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse))
store.dispatch(WalletConnectAction.ResetState)
- store.dispatch(GlobalAction.SaveScanNoteResponse(scanResponse))
+ store.dispatch(GlobalAction.SaveScanResponse(scanResponse))
store.dispatch(WalletConnectAction.RestoreSessions(scanResponse))
store.dispatch(GlobalAction.SetIfCardVerifiedOnline(!attestationFailed))
store.dispatch(WalletAction.Warnings.CheckIfNeeded)
@@ -149,7 +151,7 @@ class TapWalletManager {
withMainContext {
store.dispatch(WalletAction.ResetState(data.card))
store.dispatch(WalletConnectAction.ResetState)
- store.dispatch(GlobalAction.SaveScanNoteResponse(data))
+ store.dispatch(GlobalAction.SaveScanResponse(data))
store.dispatch(WalletAction.SetIfTestnetCard(data.card.isTestCard))
store.dispatch(WalletAction.MultiWallet.SetIsMultiwalletAllowed(data.card.isMultiwalletAllowed))
store.dispatch(WalletConnectAction.RestoreSessions(data))
diff --git a/app/src/main/java/com/tangem/tap/domain/model/TotalFiatBalance.kt b/app/src/main/java/com/tangem/tap/domain/model/TotalFiatBalance.kt
index ac3ac6d56a..dd9a0b27a1 100644
--- a/app/src/main/java/com/tangem/tap/domain/model/TotalFiatBalance.kt
+++ b/app/src/main/java/com/tangem/tap/domain/model/TotalFiatBalance.kt
@@ -11,10 +11,6 @@ sealed class TotalFiatBalance {
object Loading : TotalFiatBalance()
- data class Refreshing(
- override val amount: BigDecimal,
- ) : TotalFiatBalance()
-
data class Error(
override val amount: BigDecimal,
) : TotalFiatBalance()
diff --git a/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt b/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt
index 8c3403ede9..4dec0df7d1 100644
--- a/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt
+++ b/app/src/main/java/com/tangem/tap/domain/model/UserWallet.kt
@@ -6,22 +6,23 @@ import com.tangem.domain.common.util.UserWalletId
/**
* Represents user's wallet which stored in app persistence
- * @param name User's wallet name
- * @param walletId User's wallet [UserWalletId]
+ * @param name User wallet name
+ * @param walletId User wallet [UserWalletId]
* @param artworkUrl User wallet card artwork URL
* @param cardsInWallet List of cards IDs assigned with this user's wallet
+ * @param isMultiCurrency Indicates whether this user wallet can work with more than one currency
* @param scanResponse [ScanResponse] of primary user's wallet card.
* TODO: Replace with [com.tangem.domain.common.CardDTO]
* @property cardId ID of user's wallet primary card
* @property hasAccessCode Indicates if the user's wallet primary card has access code
* @property isLocked Indicates if this primary card has no currency wallets
- * @property isSaved Indicates if this user wallet is saved
* */
data class UserWallet(
val name: String,
val walletId: UserWalletId,
val artworkUrl: String,
val cardsInWallet: Set,
+ val isMultiCurrency: Boolean,
val scanResponse: ScanResponse,
) {
val cardId: String
@@ -32,17 +33,18 @@ data class UserWallet(
val isLocked: Boolean
get() = scanResponse.card.wallets.isEmpty()
-
- internal var isSaved: Boolean = true
}
/**
* !!! Workaround !!!
*
* Calculate same [UserWalletId] for twins instead
+ *
+ * TODO: Remove after [REDACTED_JIRA]
* */
fun UserWallet.isTwinnedWith(other: UserWallet): Boolean {
- if (!scanResponse.isTangemTwins()) return false
+ if (!scanResponse.isTangemTwins() || !other.scanResponse.isTangemTwins()) return false
+ if (scanResponse.secondTwinPublicKey == null || other.scanResponse.secondTwinPublicKey == null) return false
if (other.scanResponse.secondTwinPublicKey == scanResponse.card.wallets.firstOrNull()?.publicKey?.toHexString()) {
return true
}
diff --git a/app/src/main/java/com/tangem/tap/domain/model/WalletDataModel.kt b/app/src/main/java/com/tangem/tap/domain/model/WalletDataModel.kt
index 439b900c39..4462f4d8ec 100644
--- a/app/src/main/java/com/tangem/tap/domain/model/WalletDataModel.kt
+++ b/app/src/main/java/com/tangem/tap/domain/model/WalletDataModel.kt
@@ -35,12 +35,6 @@ data class WalletDataModel(
open val pendingTransactions: List = emptyList()
open val errorMessage: String? = null
open val isErrorStatus: Boolean = false
-
- fun asRefreshing() = Refreshing(
- amount = amount,
- pendingTransactions = pendingTransactions,
- errorMessage = errorMessage,
- )
}
object Loading : Status()
@@ -74,12 +68,4 @@ data class WalletDataModel(
object MissedDerivation : Status() {
override val isErrorStatus: Boolean = true
}
-
- data class Refreshing(
- override val amount: BigDecimal,
- override val pendingTransactions: List,
- override val errorMessage: String?,
- ) : Status() {
- override val isErrorStatus: Boolean = errorMessage != null
- }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/model/WalletStoreModel.kt b/app/src/main/java/com/tangem/tap/domain/model/WalletStoreModel.kt
index cba89801a3..a0511784e2 100644
--- a/app/src/main/java/com/tangem/tap/domain/model/WalletStoreModel.kt
+++ b/app/src/main/java/com/tangem/tap/domain/model/WalletStoreModel.kt
@@ -1,6 +1,8 @@
package com.tangem.tap.domain.model
+import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.WalletManager
+import com.tangem.common.hdWallet.DerivationPath
import com.tangem.domain.common.util.UserWalletId
import com.tangem.tap.domain.model.WalletStoreModel.WalletRent
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
@@ -8,21 +10,28 @@ import java.math.BigDecimal
/**
* Contains info about the blockchain and its currencies
- * @param userWalletId ID of the [UserWallet] which uses that store
- * @param blockchainNetwork Store's [BlockchainNetwork]
- * @param walletManager Store's [WalletManager], may be null if it fails to create this manager. TODO: Remove after
- * WalletMiddleware refactoring
+ * @param userWalletId ID of the associated [UserWallet]
+ * @param blockchain [Blockchain] of this WalletStore
+ * @param derivationPath [DerivationPath] of this store, null if the card does not support the
+ * [HD Wallet](https://coinsutra.com/hd-wallets-deterministic-wallet/)
* @param walletsData List of [WalletDataModel] which represents store's blockchain currency and tokens currencies
- * @param walletRent Store's [WalletRent], null if store has no rent or currency balance is greater then
+ * @param walletRent [WalletRent], null if store has no rent or currency balance is greater then
* [WalletRent.exemptionAmount]
+ * @param blockchainNetwork [BlockchainNetwork].
+ * TODO: Remove after WalletMiddleware refactoring
+ * @param walletManager [WalletManager], may be null if it fails to create this manager.
+ * TODO: Remove after WalletMiddleware refactoring
* */
data class WalletStoreModel(
val userWalletId: UserWalletId,
+ val blockchain: Blockchain,
+ val derivationPath: DerivationPath?,
+ val walletsData: List,
+ val walletRent: WalletRent?,
+ @Deprecated("Don't use it, will be removed")
val blockchainNetwork: BlockchainNetwork,
@Deprecated("Don't use it, will be removed")
val walletManager: WalletManager?,
- val walletsData: List,
- val walletRent: WalletRent?,
) {
/**
@@ -35,4 +44,34 @@ data class WalletStoreModel(
val rent: BigDecimal,
val exemptionAmount: BigDecimal,
)
+
+ // TODO: Remove the generated methods after blockchainNetwork and walletManager are removed from the model
+ // region Generated
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is WalletStoreModel) return false
+
+ if (userWalletId != other.userWalletId) return false
+ if (blockchain != other.blockchain) return false
+ if (derivationPath != other.derivationPath) return false
+ if (walletsData != other.walletsData) return false
+ if (walletRent != other.walletRent) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = userWalletId.hashCode()
+ result = 31 * result + blockchain.hashCode()
+ result = 31 * result + (derivationPath?.hashCode() ?: 0)
+ result = 31 * result + walletsData.hashCode()
+ result = 31 * result + (walletRent?.hashCode() ?: 0)
+ return result
+ }
+
+ override fun toString(): String {
+ return "WalletStoreModel(userWalletId=$userWalletId, blockchain=$blockchain, derivationPath=$derivationPath, " +
+ "walletsData=$walletsData, walletRent=$walletRent)"
+ }
+ // endregion Generated
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt
index 377b8cf8c6..6e4d834d38 100644
--- a/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt
+++ b/app/src/main/java/com/tangem/tap/domain/model/builders/UserWalletBuilder.kt
@@ -1,15 +1,48 @@
package com.tangem.tap.domain.model.builders
+import com.tangem.common.extensions.toHexString
+import com.tangem.common.services.Result
+import com.tangem.domain.common.CardDTO
+import com.tangem.domain.common.ProductType
import com.tangem.domain.common.ScanResponse
+import com.tangem.domain.common.TapWorkarounds.isStart2Coin
+import com.tangem.domain.common.TwinCardNumber
+import com.tangem.domain.common.TwinsHelper
import com.tangem.domain.common.util.userWalletId
-import com.tangem.tap.domain.extensions.getOrLoadCardArtworkUrl
+import com.tangem.operations.attestation.OnlineCardVerifier
+import com.tangem.operations.attestation.TangemApi
import com.tangem.tap.domain.model.UserWallet
+import com.tangem.tap.features.wallet.redux.Artwork
class UserWalletBuilder(
private val scanResponse: ScanResponse,
+ private val onlineCardVerifier: OnlineCardVerifier = OnlineCardVerifier(),
) {
private var backupCardsIds: Set = emptySet()
+ private val CardDTO.isBackupNotAllowed: Boolean
+ get() = !this.settings.isBackupAllowed
+
+ private val ScanResponse.userWalletName: String
+ get() = when (productType) {
+ ProductType.Note -> "Note"
+ ProductType.Twins -> "Twin"
+ ProductType.SaltPay -> "SaltPay"
+ ProductType.Wallet -> when {
+ card.isBackupNotAllowed -> "Tangem card"
+ card.isStart2Coin -> "Start2Coin"
+ else -> "Wallet"
+ }
+ }
+
+ private val ScanResponse.isMultiCurrency: Boolean
+ get() = when (productType) {
+ ProductType.Note -> false
+ ProductType.Twins -> false
+ ProductType.SaltPay -> false
+ ProductType.Wallet -> !card.isStart2Coin
+ }
+
fun backupCardsIds(backupCardsIds: Set?) = this.apply {
if (backupCardsIds != null) {
this.backupCardsIds = backupCardsIds
@@ -20,11 +53,44 @@ class UserWalletBuilder(
return with(scanResponse) {
UserWallet(
walletId = card.userWalletId,
- name = productType.name,
- artworkUrl = card.getOrLoadCardArtworkUrl(),
+ name = userWalletName,
+ artworkUrl = loadArtworkUrl(card.cardId, card.cardPublicKey),
cardsInWallet = backupCardsIds.plus(card.cardId),
scanResponse = this,
+ isMultiCurrency = isMultiCurrency,
)
}
}
+
+ private suspend fun loadArtworkUrl(cardId: String, cardPublicKey: ByteArray): String {
+ return when (val result = onlineCardVerifier.getCardInfo(cardId, cardPublicKey)) {
+ is Result.Success -> {
+ val artworkId = result.data.artwork?.id
+ if (artworkId.isNullOrEmpty()) {
+ getFallbackArtworkUrl(cardId)
+ } else {
+ getUrlForArtwork(cardId, cardPublicKey.toHexString(), artworkId)
+ }
+ }
+
+ is Result.Failure -> getFallbackArtworkUrl(cardId)
+ }
+ }
+
+ private fun getFallbackArtworkUrl(cardId: String): String {
+ return when {
+ cardId.startsWith(Artwork.SERGIO_CARD_ID) -> Artwork.SERGIO_CARD_URL
+ cardId.startsWith(Artwork.MARTA_CARD_ID) -> Artwork.MARTA_CARD_URL
+ else -> when (TwinsHelper.getTwinCardNumber(cardId)) {
+ TwinCardNumber.First -> Artwork.TWIN_CARD_1
+ TwinCardNumber.Second -> Artwork.TWIN_CARD_2
+ else -> Artwork.DEFAULT_IMG_URL
+ }
+ }
+ }
+
+ private fun getUrlForArtwork(cardId: String, cardPublicKeyHex: String, artworkId: String): String {
+ return TangemApi.Companion.BaseUrl.VERIFY.url + TangemApi.ARTWORK +
+ "?artworkId=${artworkId}&CID=${cardId}&publicKey=$cardPublicKeyHex"
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt b/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt
index f41c664276..e76a291668 100644
--- a/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt
+++ b/app/src/main/java/com/tangem/tap/domain/model/builders/WalletStoreBuilder.kt
@@ -4,6 +4,7 @@ import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.WalletManager
+import com.tangem.common.hdWallet.DerivationPath
import com.tangem.domain.common.util.UserWalletId
import com.tangem.tap.domain.model.WalletDataModel
import com.tangem.tap.domain.model.WalletStoreModel
@@ -54,10 +55,12 @@ private class BlockchainNetworkWalletStoreBuilderImpl(
return WalletStoreModel(
userWalletId = userWalletId,
- blockchainNetwork = blockchainNetwork,
- walletManager = walletManager,
+ blockchain = blockchainNetwork.blockchain,
+ derivationPath = blockchainNetwork.derivationPath?.let { DerivationPath(it) },
walletsData = (listOf(blockchainWalletData) + tokensWalletsData),
walletRent = null,
+ walletManager = walletManager,
+ blockchainNetwork = blockchainNetwork,
)
}
}
@@ -74,10 +77,12 @@ private class WalletMangerWalletStoreBuilderImpl(
return WalletStoreModel(
userWalletId = userWalletId,
- blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager),
- walletManager = walletManager,
+ blockchain = wallet.blockchain,
+ derivationPath = wallet.publicKey.derivationPath,
walletsData = (listOf(blockchainWalletData) + listOfNotNull(tokenWalletsData)),
walletRent = null,
+ walletManager = walletManager,
+ blockchainNetwork = BlockchainNetwork.fromWalletManager(walletManager),
)
}
}
diff --git a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt
index 6ab45894b9..d940a338ad 100644
--- a/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt
+++ b/app/src/main/java/com/tangem/tap/domain/scanCard/ScanCardProcessor.kt
@@ -82,6 +82,7 @@ object ScanCardProcessor {
nextHandler = { scanResponse1 ->
showDisclaimerIfNeed(
scanResponse = scanResponse1,
+ onProgressStateChange = onProgressStateChange,
nextHandler = { scanResponse2 ->
onScanSuccess(
scanResponse = scanResponse2,
@@ -127,6 +128,7 @@ object ScanCardProcessor {
private suspend inline fun showDisclaimerIfNeed(
scanResponse: ScanResponse,
+ crossinline onProgressStateChange: suspend (showProgress: Boolean) -> Unit,
crossinline nextHandler: suspend (ScanResponse) -> Unit,
) {
val disclaimerType = DisclaimerType.get(scanResponse)
@@ -137,11 +139,18 @@ object ScanCardProcessor {
} else scope.launch(Dispatchers.Main) {
delay(DELAY_SDK_DIALOG_CLOSE)
store.dispatchOnMain(
- DisclaimerAction.Show {
- scope.launch(Dispatchers.Main) {
- nextHandler(scanResponse)
- }
- },
+ DisclaimerAction.Show(
+ onAcceptCallback = {
+ scope.launch(Dispatchers.Main) {
+ nextHandler(scanResponse)
+ }
+ },
+ onDismissCallback = {
+ scope.launch(Dispatchers.Main) {
+ onProgressStateChange(false)
+ }
+ },
+ ),
)
}
}
diff --git a/app/src/main/java/com/tangem/tap/domain/totalBalance/implementation/DefaultTotalFiatBalanceCalculator.kt b/app/src/main/java/com/tangem/tap/domain/totalBalance/implementation/DefaultTotalFiatBalanceCalculator.kt
index 512e986cbf..0c0f909c80 100644
--- a/app/src/main/java/com/tangem/tap/domain/totalBalance/implementation/DefaultTotalFiatBalanceCalculator.kt
+++ b/app/src/main/java/com/tangem/tap/domain/totalBalance/implementation/DefaultTotalFiatBalanceCalculator.kt
@@ -33,9 +33,6 @@ internal class DefaultTotalFiatBalanceCalculator : TotalFiatBalanceCalculator {
when (walletsData.findStatus()) {
TotalFiatBalanceStatus.Loading -> TotalFiatBalance.Loading
- TotalFiatBalanceStatus.Refreshing -> TotalFiatBalance.Refreshing(
- amount = prevAmount ?: BigDecimal.ZERO,
- )
TotalFiatBalanceStatus.Error -> TotalFiatBalance.Error(calculateAmount())
TotalFiatBalanceStatus.Loaded -> TotalFiatBalance.Loaded(calculateAmount())
}
@@ -54,7 +51,6 @@ internal class DefaultTotalFiatBalanceCalculator : TotalFiatBalanceCalculator {
private fun Sequence.mapToStatus(): Sequence {
return this.map { walletData ->
when (walletData.status) {
- is WalletDataModel.Refreshing -> TotalFiatBalanceStatus.Refreshing
is WalletDataModel.VerifiedOnline,
is WalletDataModel.SameCurrencyTransactionInProgress,
is WalletDataModel.TransactionInProgress,
@@ -84,18 +80,10 @@ internal class DefaultTotalFiatBalanceCalculator : TotalFiatBalanceCalculator {
): TotalFiatBalanceStatus {
return when (prevStatus) {
TotalFiatBalanceStatus.Loading -> prevStatus
- TotalFiatBalanceStatus.Refreshing -> when (newStatus) {
- TotalFiatBalanceStatus.Loading -> prevStatus
- TotalFiatBalanceStatus.Refreshing,
- TotalFiatBalanceStatus.Error,
- TotalFiatBalanceStatus.Loaded,
- -> newStatus
- }
TotalFiatBalanceStatus.Loaded,
TotalFiatBalanceStatus.Error,
-> when (newStatus) {
TotalFiatBalanceStatus.Loading,
- TotalFiatBalanceStatus.Refreshing,
TotalFiatBalanceStatus.Error,
-> newStatus
TotalFiatBalanceStatus.Loaded -> prevStatus
@@ -105,7 +93,6 @@ internal class DefaultTotalFiatBalanceCalculator : TotalFiatBalanceCalculator {
private enum class TotalFiatBalanceStatus {
Loading,
- Refreshing,
Error,
Loaded,
}
diff --git a/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt
index ae9a72152a..28016d704f 100644
--- a/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt
+++ b/app/src/main/java/com/tangem/tap/domain/twins/CreateFirstTwinWalletTask.kt
@@ -10,6 +10,9 @@ import com.tangem.operations.wallet.CreateWalletTask
import com.tangem.operations.wallet.PurgeWalletCommand
class CreateFirstTwinWalletTask(private val firstCardId: String) : CardSessionRunnable {
+
+ override val allowsRequestAccessCodeFromRepository: Boolean = false
+
override fun run(
session: CardSession,
callback: (result: CompletionResult) -> Unit,
diff --git a/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt
index 57b66d7a62..628c041112 100644
--- a/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt
+++ b/app/src/main/java/com/tangem/tap/domain/twins/CreateSecondTwinWalletTask.kt
@@ -20,6 +20,8 @@ class CreateSecondTwinWalletTask(
private val creatingWalletMessage: Message,
) : CardSessionRunnable {
+ override val allowsRequestAccessCodeFromRepository: Boolean = false
+
override fun run(session: CardSession, callback: (result: CompletionResult) -> Unit) {
val card = session.environment.card
val publicKey = card?.wallets?.firstOrNull()?.publicKey
diff --git a/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt b/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt
index 73b9e2f40a..d4e00cb3fe 100644
--- a/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt
+++ b/app/src/main/java/com/tangem/tap/domain/twins/FinalizeTwinTask.kt
@@ -13,6 +13,8 @@ class FinalizeTwinTask(
private val twinPublicKey: ByteArray, private val issuerKeys: KeyPair,
) : CardSessionRunnable {
+ override val allowsRequestAccessCodeFromRepository: Boolean = false
+
override fun run(
session: CardSession,
callback: (result: CompletionResult) -> Unit,
diff --git a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt
index 8310047019..b910fcb589 100644
--- a/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/twins/TwinCardsManager.kt
@@ -20,11 +20,9 @@ class TwinCardsManager(
assetReader: AssetReader,
) {
private val firstCardId: String = card.cardId
- private var secondCardId: String? = null
private var currentCardPublicKey: String? = null
- var secondCardPublicKey: String? = null
- private set
+ private var secondCardPublicKey: String? = null
private val issuerKeyPair: KeyPair = getIssuerKeys(assetReader, card.issuer.publicKey.toHexString())
@@ -57,7 +55,6 @@ class TwinCardsManager(
when (response) {
is CompletionResult.Success -> {
secondCardPublicKey = response.data.wallet.publicKey.toHexString()
- secondCardId = response.data.cardId
}
is CompletionResult.Failure -> {}
}
diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt
index 62ea1a37a4..dfd62c05f5 100644
--- a/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/UserWalletsListManager.kt
@@ -14,24 +14,23 @@ interface UserWalletsListManager {
val hasSavedUserWallets: Boolean
suspend fun unlockWithBiometry(): CompletionResult
- suspend fun unlockWithCard(userWallet: UserWallet): CompletionResult
fun lock()
- suspend fun selectWallet(walletId: UserWalletId): CompletionResult
+ suspend fun selectWallet(userWalletId: UserWalletId): CompletionResult
/**
- * Save user's wallet
- * @param userWallet User's wallet to save
+ * Save user wallet
+ * @param userWallet [UserWallet] to save
* @param canOverride If false, then terminate with [UserWalletListError.WalletAlreadySaved] when user tries to save an
* already saved card
- * @return [CompletionResult] operation result
+ * @return [CompletionResult] of operation
* */
suspend fun save(userWallet: UserWallet, canOverride: Boolean = false): CompletionResult
- suspend fun delete(walletIds: List): CompletionResult
+ suspend fun delete(userWalletIds: List): CompletionResult
suspend fun clear(): CompletionResult
- suspend fun get(walletId: UserWalletId): CompletionResult
+ suspend fun get(userWalletId: UserWalletId): CompletionResult
companion object
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt
index 248719dd20..f7781ca770 100644
--- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt
@@ -28,21 +28,19 @@ internal class BiometricUserWalletsListManager(
override val userWallets: Flow>
get() = state
- .mapLatest { it.wallets }
+ .mapLatest { it.userWallets }
.distinctUntilChanged()
override val selectedUserWallet: Flow
get() = state
.mapLatest { state ->
- state.wallets.find {
- it.walletId == state.selectedWalletId
- }
+ findSelectedUserWallet(state.userWallets)
}
.filterNotNull()
.distinctUntilChanged()
override val selectedUserWalletSync: UserWallet?
- get() = findSelectedWallet()
+ get() = findSelectedUserWallet()
override val isLocked: Flow
get() = state
@@ -53,44 +51,13 @@ internal class BiometricUserWalletsListManager(
get() = state.value.isLocked
override val hasSavedUserWallets: Boolean
- get() = selectedUserWalletRepository.get() != null
+ get() = publicInformationRepository.isNotEmpty()
override suspend fun unlockWithBiometry(): CompletionResult {
return unlockWithBiometryInternal()
.map { selectedUserWalletSync }
}
- override suspend fun unlockWithCard(userWallet: UserWallet): CompletionResult {
- state.update { prevState ->
- // If the previous state contains a saved user wallet with the same ID, it is also saved
- userWallet.isSaved = prevState.wallets.any {
- it.walletId == userWallet.walletId && it.isSaved
- }
-
- val newEncryptionKeys = prevState.encryptionKeys
- .plus(UserWalletEncryptionKey(userWallet))
- .distinctBy { it.walletId }
- val newUserWallets = prevState.wallets
- .plus(userWallet)
- .distinctBy { it.walletId }
-
- prevState.copy(
- encryptionKeys = newEncryptionKeys,
- wallets = newUserWallets,
- selectedWalletId = userWallet.walletId,
- )
- }
-
- return loadModels()
- .map {
- state.update { prevState ->
- prevState.copy(
- isLocked = prevState.wallets.any { it.isLocked },
- )
- }
- }
- }
-
override fun lock() {
state.update { prevState ->
prevState.copy(
@@ -100,85 +67,62 @@ internal class BiometricUserWalletsListManager(
}
}
- override suspend fun selectWallet(walletId: UserWalletId): CompletionResult = catching {
- if (state.value.selectedWalletId == walletId) {
- return@catching findSelectedWallet()!!
+ override suspend fun selectWallet(userWalletId: UserWalletId): CompletionResult = catching {
+ if (state.value.selectedUserWalletId == userWalletId) {
+ return@catching findSelectedUserWallet()!!
}
- if (!state.value.isLocked) {
- selectedUserWalletRepository.set(walletId)
+ selectedUserWalletRepository.set(userWalletId)
- state.update { prevState ->
- prevState.copy(
- selectedWalletId = walletId,
- )
- }
+ state.update { prevState ->
+ prevState.copy(
+ selectedUserWalletId = userWalletId,
+ )
}
- findSelectedWallet()!!
+ findSelectedUserWallet()!!
}
- override suspend fun save(
- userWallet: UserWallet,
- canOverride: Boolean,
- ): CompletionResult = withUnlock {
- val isWalletSaved = state.value.wallets
- .filter { it.isSaved }
- .any {
- // Workaround, check [UserWallet.isTwinnedWith]
- it.cardsInWallet.contains(userWallet.cardId) || it.isTwinnedWith(userWallet)
- }
-
- if (isWalletSaved && !canOverride) {
- CompletionResult.Failure(UserWalletListError.WalletAlreadySaved)
+ override suspend fun save(userWallet: UserWallet, canOverride: Boolean): CompletionResult {
+ return if (canOverride) {
+ saveInternal(userWallet)
} else {
- val newEncryptionKeys = state.value.encryptionKeys
- .plus(UserWalletEncryptionKey(userWallet))
- .distinctBy { it.walletId }
+ val isWalletSaved = state.value.userWallets
+ .any {
+ // Workaround, check [UserWallet.isTwinnedWith]
+ it.cardsInWallet.contains(userWallet.cardId) || it.isTwinnedWith(userWallet)
+ }
- keysRepository.store(newEncryptionKeys)
- .doOnSuccess {
- state.update { prevState ->
- prevState.copy(
- encryptionKeys = newEncryptionKeys,
- )
- }
- }
- .flatMap { publicInformationRepository.save(userWallet) }
- .flatMap { sensitiveInformationRepository.save(userWallet) }
- .flatMap { loadModels() }
- .doOnSuccess {
- userWallet.isSaved = true
- }
+ if (isWalletSaved) {
+ CompletionResult.Failure(UserWalletListError.WalletAlreadySaved)
+ } else {
+ saveInternal(userWallet)
+ }
}
}
- override suspend fun delete(walletIds: List): CompletionResult {
- val walletIdsToRemove = state.value.wallets
- .map { it.walletId }
- .filter { it in walletIds }
- val remainingEncryptionKeys = state.value.encryptionKeys
- .filter { it.walletId !in walletIdsToRemove }
+ override suspend fun delete(userWalletIds: List): CompletionResult {
+ if (userWalletIds.isEmpty()) {
+ return CompletionResult.Success(Unit)
+ }
- changeSelectedWalletIfNeeded(walletIdsToRemove)
+ changeSelectedUserWalletIdIfNeeded(userWalletIds)
- return sensitiveInformationRepository.delete(walletIdsToRemove)
- .flatMap { publicInformationRepository.delete(walletIdsToRemove) }
- .flatMap { keysRepository.store(remainingEncryptionKeys) }
+ return sensitiveInformationRepository.delete(userWalletIds)
+ .flatMap { publicInformationRepository.delete(userWalletIds) }
+ .flatMap { keysRepository.delete(userWalletIds) }
.map {
state.update { prevState ->
prevState.copy(
- encryptionKeys = remainingEncryptionKeys,
- wallets = prevState.wallets.filter { it.walletId !in walletIdsToRemove },
+ encryptionKeys = prevState.encryptionKeys.filter { it.walletId !in userWalletIds },
+ userWallets = prevState.userWallets.filter { it.walletId !in userWalletIds },
)
}
}
}
override suspend fun clear(): CompletionResult {
- return sensitiveInformationRepository.delete(
- walletIds = state.value.wallets.map { it.walletId },
- )
+ return sensitiveInformationRepository.clear()
.flatMap { publicInformationRepository.clear() }
.flatMap { keysRepository.clear() }
.map {
@@ -187,17 +131,37 @@ internal class BiometricUserWalletsListManager(
}
}
- override suspend fun get(walletId: UserWalletId): CompletionResult {
+ override suspend fun get(userWalletId: UserWalletId): CompletionResult {
return catching {
- state.value.wallets.first { it.walletId == walletId }
+ state.value.userWallets.first { it.walletId == userWalletId }
}
}
- private suspend inline fun withUnlock(
- block: () -> CompletionResult,
- ): CompletionResult {
- return (if (state.value.isLocked) unlockWithBiometryInternal() else CompletionResult.Success(Unit))
- .flatMap { block() }
+ private suspend fun saveInternal(userWallet: UserWallet): CompletionResult {
+ val newEncryptionKeys = state.value.encryptionKeys
+ .plus(UserWalletEncryptionKey(userWallet))
+ .distinctBy { it.walletId }
+
+ return keysRepository.store(newEncryptionKeys)
+ .doOnSuccess {
+ state.update { prevState ->
+ prevState.copy(
+ encryptionKeys = newEncryptionKeys,
+ selectedUserWalletId = userWallet.walletId,
+ )
+ }
+ }
+ .flatMap { publicInformationRepository.save(userWallet) }
+ .flatMap { sensitiveInformationRepository.save(userWallet) }
+ .map { selectedUserWalletRepository.set(userWallet.walletId) }
+ .flatMap { loadModels() }
+ .doOnSuccess {
+ state.update { prevState ->
+ prevState.copy(
+ isLocked = prevState.userWallets.any { it.isLocked },
+ )
+ }
+ }
}
private suspend fun unlockWithBiometryInternal(): CompletionResult {
@@ -223,11 +187,11 @@ internal class BiometricUserWalletsListManager(
return getSavedUserWallets()
.map { userWallets ->
if (userWallets.isNotEmpty()) state.update { prevState ->
- val wallets = (userWallets + prevState.wallets).distinctBy { it.walletId }
+ val wallets = (userWallets + prevState.userWallets).distinctBy { it.walletId }
prevState.copy(
- wallets = wallets,
- selectedWalletId = findOrSetSelectedWallet(prevState.selectedWalletId, wallets),
+ userWallets = wallets,
+ selectedUserWalletId = findOrSetSelectedUserWalletId(prevState.selectedUserWalletId, wallets),
)
}
}
@@ -247,56 +211,55 @@ internal class BiometricUserWalletsListManager(
}
}
- private fun findOrSetSelectedWallet(
+ private fun findOrSetSelectedUserWalletId(
prevSelectedWalletId: UserWalletId?,
userWallets: List,
): UserWalletId? {
- return prevSelectedWalletId
- ?: (selectedUserWalletRepository.get()
- ?: (userWallets.firstOrNull()?.walletId
- ?.also { selectedUserWalletRepository.set(it) }))
+ val findUnlockedAndSet = {
+ userWallets.firstOrNull { !it.isLocked }
+ ?.walletId
+ ?.also { selectedUserWalletRepository.set(it) }
+ }
+
+ return prevSelectedWalletId ?: (selectedUserWalletRepository.get() ?: findUnlockedAndSet())
}
- private fun changeSelectedWalletIfNeeded(
- walletsIdsToRemove: List,
- ) {
- val remainingWallets = state.value.wallets.filter {
+ private fun changeSelectedUserWalletIdIfNeeded(walletsIdsToRemove: List) {
+ val remainingWallets = state.value.userWallets.filter {
it.walletId !in walletsIdsToRemove
}
- val selectedWallet = findSelectedWallet()
+ val selectedWallet = findSelectedUserWallet()
when {
remainingWallets.isEmpty() -> {
state.update { prevState ->
prevState.copy(
- selectedWalletId = null,
+ selectedUserWalletId = null,
)
}
selectedUserWalletRepository.set(null)
}
!remainingWallets.contains(selectedWallet) -> {
- val newSelectedWallet = remainingWallets.first()
+ val newSelectedWallet = remainingWallets.firstOrNull { !it.isLocked }
state.update { prevState ->
prevState.copy(
- selectedWalletId = newSelectedWallet.walletId,
+ selectedUserWalletId = newSelectedWallet?.walletId,
)
}
- selectedUserWalletRepository.set(newSelectedWallet.walletId)
+ selectedUserWalletRepository.set(newSelectedWallet?.walletId)
}
}
}
- private fun findSelectedWallet(): UserWallet? {
- return with(state.value) {
- wallets.find {
- it.walletId == selectedWalletId
- }
+ private fun findSelectedUserWallet(userWallets: List = state.value.userWallets): UserWallet? {
+ return userWallets.find {
+ it.walletId == state.value.selectedUserWalletId
}
}
private data class State(
val encryptionKeys: List = emptyList(),
- val wallets: List = emptyList(),
- val selectedWalletId: UserWalletId? = null,
+ val userWallets: List = emptyList(),
+ val selectedUserWalletId: UserWalletId? = null,
val isLocked: Boolean = true,
)
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt
index a3c3a3e929..e616c48758 100644
--- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt
+++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/DummyUserWalletsListManager.kt
@@ -26,15 +26,11 @@ class DummyUserWalletsListManager : UserWalletsListManager {
return CompletionResult.Success(null)
}
- override suspend fun unlockWithCard(userWallet: UserWallet): CompletionResult {
- return CompletionResult.Success(Unit)
- }
-
override fun lock() {
/* no-op */
}
- override suspend fun selectWallet(walletId: UserWalletId): CompletionResult {
+ override suspend fun selectWallet(userWalletId: UserWalletId): CompletionResult {
return catching {
error("Not implemented")
}
@@ -44,7 +40,7 @@ class DummyUserWalletsListManager : UserWalletsListManager {
return CompletionResult.Success(Unit)
}
- override suspend fun delete(walletIds: List): CompletionResult {
+ override suspend fun delete(userWalletIds: List): CompletionResult {
return CompletionResult.Success(Unit)
}
@@ -52,7 +48,7 @@ class DummyUserWalletsListManager : UserWalletsListManager {
return CompletionResult.Success(Unit)
}
- override suspend fun get(walletId: UserWalletId): CompletionResult {
+ override suspend fun get(userWalletId: UserWalletId): CompletionResult {
return catching {
error("Not implemented")
}
diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletInformation.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletInformation.kt
index b2308343ff..d383e11e3f 100644
--- a/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletInformation.kt
+++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/model/UserWalletInformation.kt
@@ -17,4 +17,5 @@ internal data class UserWalletPublicInformation(
val artworkUrl: String,
val cardsInWallet: Set,
val scanResponse: ScanResponse,
+ val isMultiCurrency: Boolean,
)
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt
index 974879ca31..f10e0beeb5 100644
--- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt
+++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsKeysRepository.kt
@@ -1,10 +1,35 @@
package com.tangem.tap.domain.userWalletList.repository
import com.tangem.common.CompletionResult
+import com.tangem.domain.common.util.UserWalletId
import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey
internal interface UserWalletsKeysRepository {
+ /**
+ * Obtaining the encryption keys of all user wallets from the biometric vault. Biometric authentication required
+ * If that operation runs more than biometric cipher key expiration time then the user will not receive all
+ * encryption keys
+ * @return [CompletionResult] of operation with stored [UserWalletEncryptionKey] list
+ * */
suspend fun getAll(): CompletionResult>
+
+ /**
+ * Store the encryption keys for user wallets. Biometric authentication not required
+ * @param encryptionKeys List of encryption keys for user wallets
+ * @return [CompletionResult] of operation
+ * */
suspend fun store(encryptionKeys: List): CompletionResult
+
+ /**
+ * Delete encryption keys for user wallets. Biometric authentication not required
+ * @param userWalletsIds List of [UserWalletId] whose encryption keys will be deleted
+ * @return [CompletionResult] of operation
+ * */
+ suspend fun delete(userWalletsIds: List): CompletionResult
+
+ /**
+ * Clear all encryption keys for user wallets. Biometric authentication not required
+ * @return [CompletionResult] of operation
+ * */
suspend fun clear(): CompletionResult
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt
index 8765a6b804..f0387d3f0e 100644
--- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt
+++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsPublicInformationRepository.kt
@@ -12,4 +12,6 @@ internal interface UserWalletsPublicInformationRepository {
suspend fun delete(walletIds: List): CompletionResult
suspend fun clear(): CompletionResult
+
+ fun isNotEmpty(): Boolean
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt
index dcc5677814..7e03055150 100644
--- a/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt
+++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/repository/UserWalletsSensitiveInformationRepository.kt
@@ -12,5 +12,6 @@ internal interface UserWalletsSensitiveInformationRepository {
encryptionKeys: List,
): CompletionResult