Updated on 2026-08-14
This commit is contained in:
commit
9fe8c2d3fa
19 changed files with 202 additions and 136 deletions
|
|
@ -41,7 +41,6 @@ import com.tangem.domain.settings.repositories.SettingsRepository
|
|||
import com.tangem.domain.tokens.GetPolkadotCheckHasImmortalUseCase
|
||||
import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.legacy.asLockable
|
||||
import com.tangem.feature.qrscanning.QrScanningRouter
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
|
||||
import com.tangem.features.managetokens.navigation.ManageTokensUi
|
||||
|
|
@ -440,10 +439,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac
|
|||
}
|
||||
|
||||
private fun navigateToInitialScreen(intentWhichStartedActivity: Intent?) {
|
||||
val canSaveWallets = runCatching { userWalletsListManager.asLockable()?.isLockedSync }
|
||||
.fold(onSuccess = { true }, onFailure = { false })
|
||||
|
||||
if (canSaveWallets && userWalletsListManager.hasUserWallets) {
|
||||
if (userWalletsListManager.isLockable && userWalletsListManager.hasUserWallets) {
|
||||
store.dispatch(
|
||||
NavigationAction.NavigateTo(
|
||||
screen = AppScreen.Welcome,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysRepository
|
|||
import com.tangem.tap.domain.userWalletList.repository.UserWalletsPublicInformationRepository
|
||||
import com.tangem.tap.domain.userWalletList.repository.UserWalletsSensitiveInformationRepository
|
||||
import com.tangem.tap.domain.userWalletList.utils.encryptionKey
|
||||
import com.tangem.tap.domain.userWalletList.utils.lockAll
|
||||
import com.tangem.tap.domain.userWalletList.utils.toUserWallets
|
||||
import com.tangem.tap.domain.userWalletList.utils.updateWith
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
|
|
@ -27,6 +28,8 @@ internal class BiometricUserWalletsListManager(
|
|||
) : UserWalletsListManager.Lockable {
|
||||
private val state = MutableStateFlow(State())
|
||||
|
||||
override val isLockable: Boolean = true
|
||||
|
||||
override val userWallets: Flow<List<UserWallet>>
|
||||
get() = state
|
||||
.mapLatest { it.userWallets }
|
||||
|
|
@ -78,11 +81,13 @@ internal class BiometricUserWalletsListManager(
|
|||
}
|
||||
|
||||
override fun lock() {
|
||||
state.update { State() }
|
||||
}
|
||||
|
||||
override fun isLockable(): Boolean {
|
||||
return true
|
||||
state.update { prevState ->
|
||||
prevState.copy(
|
||||
encryptionKeys = emptyList(),
|
||||
userWallets = prevState.userWallets.lockAll(),
|
||||
isLocked = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun select(userWalletId: UserWalletId): CompletionResult<UserWallet> = catching {
|
||||
|
|
|
|||
|
|
@ -33,31 +33,49 @@ internal class GeneralUserWalletsListManager(
|
|||
) : UserWalletsListManager.Lockable {
|
||||
|
||||
private val applicationScope = CoroutineScope(dispatchers.io)
|
||||
private val implementation = MutableStateFlow(runtimeUserWalletsListManager)
|
||||
private val implementation: MutableStateFlow<UserWalletsListManager?> = MutableStateFlow(value = null)
|
||||
|
||||
private val requireImplementation: UserWalletsListManager
|
||||
get() = requireNotNull(implementation.value) {
|
||||
"UserWalletsListManager is not initialized"
|
||||
}
|
||||
|
||||
init {
|
||||
subscribeOnCurrentManager()
|
||||
}
|
||||
|
||||
override val isLockable: Boolean
|
||||
get() = requireImplementation.isLockable
|
||||
|
||||
override val userWallets: Flow<List<UserWallet>>
|
||||
get() = implementation.flatMapLatest { it.userWallets }
|
||||
get() = implementation.transformLatest { impl ->
|
||||
if (impl != null && impl.hasUserWallets) {
|
||||
emitAll(impl.userWallets)
|
||||
}
|
||||
}
|
||||
|
||||
override val selectedUserWallet: Flow<UserWallet>
|
||||
get() = implementation.flatMapLatest { it.selectedUserWallet }
|
||||
get() = implementation.transformLatest { impl ->
|
||||
if (impl != null && impl.hasUserWallets) {
|
||||
emitAll(impl.selectedUserWallet)
|
||||
}
|
||||
}
|
||||
|
||||
override val selectedUserWalletSync: UserWallet?
|
||||
get() = implementation.value.selectedUserWalletSync
|
||||
get() = requireImplementation.selectedUserWalletSync
|
||||
|
||||
override val hasUserWallets: Boolean
|
||||
get() = implementation.value.hasUserWallets
|
||||
get() = requireImplementation.hasUserWallets
|
||||
|
||||
override val walletsCount: Int
|
||||
get() = implementation.value.walletsCount
|
||||
get() = requireImplementation.walletsCount
|
||||
|
||||
override val isLocked: Flow<Boolean>
|
||||
get() = implementation.flatMapLatest {
|
||||
if (it is UserWalletsListManager.Lockable) {
|
||||
it.isLocked
|
||||
get() = implementation.transformLatest { impl ->
|
||||
if (impl == null) return@transformLatest
|
||||
|
||||
if (impl is UserWalletsListManager.Lockable) {
|
||||
emitAll(impl.isLocked)
|
||||
} else {
|
||||
error("RuntimeUserWalletsListManager is not lockable")
|
||||
}
|
||||
|
|
@ -65,43 +83,45 @@ internal class GeneralUserWalletsListManager(
|
|||
|
||||
override val isLockedSync: Boolean
|
||||
get() {
|
||||
val implementation = implementation.value
|
||||
return if (implementation is UserWalletsListManager.Lockable) {
|
||||
implementation.isLockedSync
|
||||
val impl = requireImplementation
|
||||
|
||||
return if (impl is UserWalletsListManager.Lockable) {
|
||||
impl.isLockedSync
|
||||
} else {
|
||||
error("RuntimeUserWalletsListManager is not lockable")
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun select(userWalletId: UserWalletId): CompletionResult<UserWallet> {
|
||||
return implementation.value.select(userWalletId)
|
||||
return requireImplementation.select(userWalletId)
|
||||
}
|
||||
|
||||
override suspend fun save(userWallet: UserWallet, canOverride: Boolean): CompletionResult<Unit> {
|
||||
return implementation.value.save(userWallet, canOverride)
|
||||
return requireImplementation.save(userWallet, canOverride)
|
||||
}
|
||||
|
||||
override suspend fun update(
|
||||
userWalletId: UserWalletId,
|
||||
update: suspend (UserWallet) -> UserWallet,
|
||||
): CompletionResult<UserWallet> {
|
||||
return implementation.value.update(userWalletId, update)
|
||||
return requireImplementation.update(userWalletId, update)
|
||||
}
|
||||
|
||||
override suspend fun delete(userWalletIds: List<UserWalletId>): CompletionResult<Unit> {
|
||||
return implementation.value.delete(userWalletIds)
|
||||
return requireImplementation.delete(userWalletIds)
|
||||
}
|
||||
|
||||
override suspend fun clear(): CompletionResult<Unit> {
|
||||
return implementation.value.clear()
|
||||
return requireImplementation.clear()
|
||||
}
|
||||
|
||||
override suspend fun get(userWalletId: UserWalletId): CompletionResult<UserWallet> {
|
||||
return implementation.value.get(userWalletId)
|
||||
return requireImplementation.get(userWalletId)
|
||||
}
|
||||
|
||||
override suspend fun unlock(type: UserWalletsListManager.Lockable.UnlockType): CompletionResult<UserWallet> {
|
||||
val implementation = implementation.value
|
||||
val implementation = requireImplementation
|
||||
|
||||
return if (implementation is UserWalletsListManager.Lockable) {
|
||||
implementation.unlock(type)
|
||||
} else {
|
||||
|
|
@ -110,7 +130,8 @@ internal class GeneralUserWalletsListManager(
|
|||
}
|
||||
|
||||
override fun lock() {
|
||||
val implementation = implementation.value
|
||||
val implementation = requireImplementation
|
||||
|
||||
return if (implementation is UserWalletsListManager.Lockable) {
|
||||
implementation.lock()
|
||||
} else {
|
||||
|
|
@ -118,10 +139,6 @@ internal class GeneralUserWalletsListManager(
|
|||
}
|
||||
}
|
||||
|
||||
override fun isLockable(): Boolean {
|
||||
return implementation.value.isLockable()
|
||||
}
|
||||
|
||||
private fun subscribeOnCurrentManager() {
|
||||
appPreferencesStore.get(key = PreferencesKeys.SAVE_USER_WALLETS_KEY, default = false)
|
||||
.distinctUntilChanged()
|
||||
|
|
@ -144,17 +161,17 @@ internal class GeneralUserWalletsListManager(
|
|||
destinationManager = possibleManager,
|
||||
)
|
||||
|
||||
previousManager.clear()
|
||||
previousManager?.clear()
|
||||
}
|
||||
.flowOn(dispatchers.io)
|
||||
.launchIn(applicationScope)
|
||||
}
|
||||
|
||||
private suspend fun copySelectedUserWallet(
|
||||
sourceManager: UserWalletsListManager,
|
||||
sourceManager: UserWalletsListManager?,
|
||||
destinationManager: UserWalletsListManager,
|
||||
): UserWalletsListManager {
|
||||
sourceManager.selectedUserWalletSync?.let { selectedWallet ->
|
||||
sourceManager?.selectedUserWalletSync?.let { selectedWallet ->
|
||||
destinationManager.save(selectedWallet, canOverride = true)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ import kotlinx.coroutines.flow.*
|
|||
internal class RuntimeUserWalletsListManager : UserWalletsListManager {
|
||||
private val state = MutableStateFlow(State())
|
||||
|
||||
override val isLockable: Boolean = false
|
||||
|
||||
override val userWallets: Flow<List<UserWallet>>
|
||||
get() = state
|
||||
.mapLatest { listOfNotNull(it.userWallet) }
|
||||
|
|
@ -34,7 +36,7 @@ internal class RuntimeUserWalletsListManager : UserWalletsListManager {
|
|||
* only 1 wallet stored in runtime implementation
|
||||
*/
|
||||
override val walletsCount: Int
|
||||
get() = 1
|
||||
get() = if (hasUserWallets) 1 else 0
|
||||
|
||||
override suspend fun select(userWalletId: UserWalletId): CompletionResult<UserWallet> = catching {
|
||||
state.value.userWallet
|
||||
|
|
@ -85,10 +87,6 @@ internal class RuntimeUserWalletsListManager : UserWalletsListManager {
|
|||
state.value.userWallet ?: walletNotFound()
|
||||
}
|
||||
|
||||
override fun isLockable(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
private fun saveInternal(userWallet: UserWallet): CompletionResult<Unit> = catching {
|
||||
state.update { prevState ->
|
||||
prevState.copy(
|
||||
|
|
|
|||
|
|
@ -61,4 +61,14 @@ internal fun List<UserWallet>.updateWith(
|
|||
?: wallet
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun List<UserWallet>.lockAll(): List<UserWallet> = map(UserWallet::lock)
|
||||
|
||||
internal fun UserWallet.lock(): UserWallet = copy(
|
||||
scanResponse = scanResponse.copy(
|
||||
card = scanResponse.card.copy(
|
||||
wallets = emptyList(),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
@ -48,19 +48,30 @@ class WalletConnectSdkHelper {
|
|||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
suspend fun prepareTransactionData(data: EthTransactionData): WcTransactionData? {
|
||||
suspend fun prepareTransactionData(data: EthTransactionData): WcTransactionData {
|
||||
val transaction = data.transaction
|
||||
val blockchain = Blockchain.fromNetworkId(data.networkId) ?: return null
|
||||
val walletManager = getWalletManager(blockchain, data.rawDerivationPath) ?: return null
|
||||
val blockchain = requireNotNull(Blockchain.fromNetworkId(data.networkId)) {
|
||||
"Blockchain not found"
|
||||
}
|
||||
val walletManager = requireNotNull(getWalletManager(blockchain, data.rawDerivationPath)) {
|
||||
"WalletManager not found"
|
||||
}
|
||||
|
||||
walletManager.safeUpdate(isDemoCard())
|
||||
val wallet = walletManager.wallet
|
||||
val balance = wallet.amounts[AmountType.Coin]?.value ?: return null
|
||||
val balance = requireNotNull(wallet.amounts[AmountType.Coin]?.value) {
|
||||
"Coin balance not found"
|
||||
}
|
||||
|
||||
val decimals = wallet.blockchain.decimals()
|
||||
|
||||
val value = (transaction.value ?: "0").hexToBigDecimal()
|
||||
.movePointLeft(decimals) ?: return null
|
||||
val value = (transaction.value ?: "0")
|
||||
.hexToBigDecimal()
|
||||
.movePointLeft(decimals)
|
||||
|
||||
requireNotNull(value) {
|
||||
"Transaction amount is null"
|
||||
}
|
||||
|
||||
val gasLimit = getGasLimitFromTx(value, walletManager, transaction)
|
||||
|
||||
|
|
@ -69,20 +80,23 @@ class WalletConnectSdkHelper {
|
|||
is Result.Success -> result.data.toBigDecimal()
|
||||
is Result.Failure -> {
|
||||
(result.error as? Throwable)?.let { Timber.e(it, "getGasPrice failed") }
|
||||
return null
|
||||
|
||||
error("Unable to get gas price: ${result.error}")
|
||||
}
|
||||
null -> return null
|
||||
null -> error("Gas price is null")
|
||||
}
|
||||
|
||||
val fee = (gasLimit * gasPrice).movePointLeft(decimals)
|
||||
val total = value + fee
|
||||
|
||||
val destinationAddress = requireNotNull(transaction.to) { "Destination address is null" }
|
||||
|
||||
val transactionData = TransactionData(
|
||||
amount = Amount(value, wallet.blockchain),
|
||||
// TODO refactoring
|
||||
fee = Fee.Common(Amount(fee, wallet.blockchain)),
|
||||
sourceAddress = transaction.from,
|
||||
destinationAddress = transaction.to!!,
|
||||
destinationAddress = destinationAddress,
|
||||
extras = EthereumTransactionExtras(
|
||||
data = transaction.data.removePrefix(HEX_PREFIX).hexToBytes(),
|
||||
gasLimit = gasLimit.toBigInteger(),
|
||||
|
|
@ -102,6 +116,7 @@ class WalletConnectSdkHelper {
|
|||
id = data.id,
|
||||
type = data.type,
|
||||
)
|
||||
|
||||
return WcTransactionData(
|
||||
type = data.type,
|
||||
transaction = transactionData,
|
||||
|
|
|
|||
|
|
@ -374,6 +374,7 @@ internal class DefaultWalletConnectRepository(
|
|||
|
||||
override fun rejectRequest(requestData: RequestData, error: WalletConnectError) {
|
||||
val session = currentSessions.find { it.topic == requestData.topic }
|
||||
|
||||
analyticsHandler.send(
|
||||
WalletConnect.RequestHandled(
|
||||
WalletConnect.RequestHandledParams(
|
||||
|
|
@ -385,17 +386,18 @@ internal class DefaultWalletConnectRepository(
|
|||
),
|
||||
),
|
||||
)
|
||||
cancelRequest(requestData.topic, requestData.requestId)
|
||||
|
||||
cancelRequest(requestData.topic, requestData.requestId, error.error)
|
||||
}
|
||||
|
||||
override fun cancelRequest(topic: String, id: Long) {
|
||||
override fun cancelRequest(topic: String, id: Long, message: String) {
|
||||
Web3Wallet.respondSessionRequest(
|
||||
params = Wallet.Params.SessionRequestResponse(
|
||||
sessionTopic = topic,
|
||||
jsonRpcResponse = Wallet.Model.JsonRpcResponse.JsonRpcError(
|
||||
id = id,
|
||||
code = 0,
|
||||
message = "",
|
||||
message = message,
|
||||
),
|
||||
),
|
||||
onSuccess = {},
|
||||
|
|
@ -406,8 +408,8 @@ internal class DefaultWalletConnectRepository(
|
|||
override fun reject() {
|
||||
Web3Wallet.rejectSession(
|
||||
params = Wallet.Params.SessionReject(
|
||||
sessionProposal?.proposerPublicKey ?: "",
|
||||
"",
|
||||
proposerPublicKey = sessionProposal?.proposerPublicKey ?: "",
|
||||
reason = "",
|
||||
),
|
||||
onSuccess = {
|
||||
Timber.d("Rejected successfully: $it")
|
||||
|
|
|
|||
|
|
@ -260,10 +260,18 @@ class WalletConnectInteractor(
|
|||
)
|
||||
else -> {
|
||||
currentRequest = sessionRequest
|
||||
val data = prepareRequestData(sessionRequest)
|
||||
if (data != null) {
|
||||
handler.onSessionRequest(data)
|
||||
|
||||
val data = prepareRequestData(sessionRequest).getOrElse { e ->
|
||||
val wrappedError = e as? WalletConnectError ?: WalletConnectError.UnknownError(
|
||||
message = e.localizedMessage ?: "Unknown error",
|
||||
)
|
||||
|
||||
walletConnectRepository.rejectRequest(requestData, wrappedError)
|
||||
handler.onSessionRejected(wrappedError)
|
||||
return
|
||||
}
|
||||
|
||||
handler.onSessionRequest(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -353,7 +361,9 @@ class WalletConnectInteractor(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun prepareRequestData(sessionRequest: WalletConnectEvents.SessionRequest): WcPreparedRequest? {
|
||||
private suspend fun prepareRequestData(
|
||||
sessionRequest: WalletConnectEvents.SessionRequest,
|
||||
): Result<WcPreparedRequest> {
|
||||
return sessionRequestConverter.prepareRequest(sessionRequest, userWalletId)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,5 +27,5 @@ interface WalletConnectRepository {
|
|||
|
||||
fun rejectRequest(requestData: RequestData, error: WalletConnectError)
|
||||
|
||||
fun cancelRequest(topic: String, id: Long)
|
||||
fun cancelRequest(topic: String, id: Long, message: String = "")
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import com.tangem.tap.domain.walletconnect.WalletConnectSdkHelper
|
|||
import com.tangem.tap.domain.walletconnect2.domain.mapper.mapToTransaction
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.BnbData
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.EthTransactionData
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectError
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WcEthTransactionType
|
||||
|
||||
|
|
@ -17,15 +18,18 @@ internal class WcSessionRequestConverter(
|
|||
suspend fun prepareRequest(
|
||||
sessionRequest: WalletConnectEvents.SessionRequest,
|
||||
userWalletId: String,
|
||||
): WcPreparedRequest? {
|
||||
val networkId = blockchainHelper.chainIdToNetworkIdOrNull(sessionRequest.chainId ?: "") ?: return null
|
||||
): Result<WcPreparedRequest> = runCatching {
|
||||
val networkId = requireNotNull(blockchainHelper.chainIdToNetworkIdOrNull(sessionRequest.chainId.orEmpty())) {
|
||||
"Failed to get network ID for chain ID: ${sessionRequest.chainId}"
|
||||
}
|
||||
val derivationPath = getDerivationPath(
|
||||
sessionsRepository = sessionsRepository,
|
||||
sessionRequest = sessionRequest,
|
||||
userWalletId = userWalletId,
|
||||
walletAddress = getWalletAddress(sessionRequest.request),
|
||||
)
|
||||
return when (val request = sessionRequest.request) {
|
||||
|
||||
when (val request = sessionRequest.request) {
|
||||
is WcRequest.EthSendTransaction -> {
|
||||
val data = sdkHelper.prepareTransactionData(
|
||||
EthTransactionData(
|
||||
|
|
@ -38,7 +42,8 @@ internal class WcSessionRequestConverter(
|
|||
metaName = sessionRequest.metaName,
|
||||
metaUrl = sessionRequest.metaUrl,
|
||||
),
|
||||
) ?: return null
|
||||
)
|
||||
|
||||
WcPreparedRequest.EthTransaction(
|
||||
preparedRequestData = data,
|
||||
topic = sessionRequest.topic,
|
||||
|
|
@ -58,7 +63,8 @@ internal class WcSessionRequestConverter(
|
|||
metaName = sessionRequest.metaName,
|
||||
metaUrl = sessionRequest.metaUrl,
|
||||
),
|
||||
) ?: return null
|
||||
)
|
||||
|
||||
WcPreparedRequest.EthTransaction(
|
||||
preparedRequestData = data,
|
||||
topic = sessionRequest.topic,
|
||||
|
|
@ -122,7 +128,7 @@ internal class WcSessionRequestConverter(
|
|||
derivationPath = derivationPath,
|
||||
)
|
||||
}
|
||||
else -> null
|
||||
else -> throw WalletConnectError.UnsupportedMethod
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,8 +20,12 @@ sealed class WalletConnectError(val error: String) : Exception() {
|
|||
override val message: String?,
|
||||
) : WalletConnectError("ExternalApprovalError")
|
||||
|
||||
object WrongUserWallet : WalletConnectError("WrongUserWallet")
|
||||
object UnsupportedMethod : WalletConnectError("UnsupportedMethod")
|
||||
object SigningError : WalletConnectError("SigningError")
|
||||
object ValidationError : WalletConnectError("ValidationError")
|
||||
data class UnknownError(
|
||||
override val message: String,
|
||||
) : WalletConnectError(message)
|
||||
|
||||
data object WrongUserWallet : WalletConnectError("WrongUserWallet")
|
||||
data object UnsupportedMethod : WalletConnectError("UnsupportedMethod")
|
||||
data object SigningError : WalletConnectError("SigningError")
|
||||
data object ValidationError : WalletConnectError("ValidationError")
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ import com.tangem.domain.qrscanning.models.SourceType
|
|||
import com.tangem.feature.qrscanning.QrScanningRouter
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
|
||||
|
|
@ -20,6 +21,7 @@ import com.tangem.tap.features.demo.DemoHelper
|
|||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.Middleware
|
||||
|
|
@ -135,6 +137,17 @@ class WalletConnectMiddleware {
|
|||
),
|
||||
)
|
||||
}
|
||||
is WalletConnectError.UnknownError -> {
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.ShowDialog(
|
||||
AppDialog.SimpleOkDialogRes(
|
||||
headerId = R.string.wallet_connect_title,
|
||||
messageId = R.string.wallet_connect_error_with_framework_message,
|
||||
args = listOf(action.error.message),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
is WalletConnectError.ExternalApprovalError -> {
|
||||
Timber.e(action.error, "ExternalApprovalError ${action.error.message}")
|
||||
// do not show dialog on this event
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
},
|
||||
{
|
||||
"name": "TOKEN_LIST_LCE_ENABLED",
|
||||
"version": "5.10.0"
|
||||
"version": "5.11.0"
|
||||
},
|
||||
{
|
||||
"name": "CARDANO_TOKENS_SUPPORT_ENABLED",
|
||||
|
|
|
|||
|
|
@ -7,6 +7,11 @@ import kotlinx.coroutines.flow.Flow
|
|||
|
||||
interface UserWalletsListManager {
|
||||
|
||||
/**
|
||||
* Indicates that the [UserWalletsListManager] is [UserWalletsListManager.Lockable]
|
||||
* */
|
||||
val isLockable: Boolean
|
||||
|
||||
/** [Flow] with all saved [UserWallet]s updates */
|
||||
val userWallets: Flow<List<UserWallet>>
|
||||
|
||||
|
|
@ -84,11 +89,6 @@ interface UserWalletsListManager {
|
|||
*/
|
||||
suspend fun get(userWalletId: UserWalletId): CompletionResult<UserWallet>
|
||||
|
||||
/**
|
||||
* Indicates that the [UserWalletsListManager] supports [UserWalletsListManager.Lockable]
|
||||
* */
|
||||
fun isLockable(): Boolean
|
||||
|
||||
interface Lockable : UserWalletsListManager {
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ suspend fun UserWalletsListManager.unlockIfLockable(type: UnlockType = UnlockTyp
|
|||
* [UserWalletsListManager.Lockable] otherwise
|
||||
* */
|
||||
fun UserWalletsListManager.asLockable(): UserWalletsListManager.Lockable? {
|
||||
if (this.isLockable()) {
|
||||
if (this.isLockable) {
|
||||
return this as? UserWalletsListManager.Lockable
|
||||
}
|
||||
return null
|
||||
|
|
|
|||
|
|
@ -1,17 +1,14 @@
|
|||
package com.tangem.domain.wallets.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.raise.either
|
||||
import arrow.core.right
|
||||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.models.DeleteWalletError
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
/**
|
||||
* Use case for updating user wallet
|
||||
* Use case for deleting user wallet
|
||||
*
|
||||
* @property userWalletsListManager user wallets list manager
|
||||
*
|
||||
|
|
@ -19,13 +16,21 @@ import com.tangem.domain.wallets.models.UserWalletId
|
|||
*/
|
||||
class DeleteWalletUseCase(private val userWalletsListManager: UserWalletsListManager) {
|
||||
|
||||
suspend operator fun invoke(userWalletId: UserWalletId): Either<DeleteWalletError, Unit> {
|
||||
/**
|
||||
* Deletes user wallet with provided ID.
|
||||
*
|
||||
* @param userWalletId ID of user wallet to be deleted.
|
||||
*
|
||||
* @return [Either] with [DeleteWalletError] or [Boolean] which indicates that there are still saved wallets.
|
||||
* */
|
||||
suspend operator fun invoke(userWalletId: UserWalletId): Either<DeleteWalletError, Boolean> {
|
||||
return either {
|
||||
userWalletsListManager.delete(userWalletIds = listOf(userWalletId))
|
||||
.doOnSuccess { return Unit.right() }
|
||||
.doOnFailure { return DeleteWalletError.UnableToDelete.left() }
|
||||
.doOnFailure {
|
||||
raise(DeleteWalletError.UnableToDelete)
|
||||
}
|
||||
|
||||
return Unit.right()
|
||||
userWalletsListManager.hasUserWallets
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,6 @@ import androidx.lifecycle.LifecycleOwner
|
|||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.settings.CanUseBiometryUseCase
|
||||
import com.tangem.domain.settings.IsWalletsScrollPreviewEnabled
|
||||
|
|
@ -32,10 +31,11 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
|||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.saveIn
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
|
|
@ -197,9 +197,9 @@ internal class WalletViewModel @Inject constructor(
|
|||
is WalletsUpdateActionResolver.Action.UpdateWalletName -> {
|
||||
stateHolder.update(transformer = RenameWalletTransformer(action.selectedWalletId, action.name))
|
||||
}
|
||||
is WalletsUpdateActionResolver.Action.NoAccessibleWallets -> closeScreen(screen = AppScreen.Welcome)
|
||||
is WalletsUpdateActionResolver.Action.NoWallets -> closeScreen(screen = AppScreen.Home)
|
||||
is WalletsUpdateActionResolver.Action.Unknown -> Unit
|
||||
is WalletsUpdateActionResolver.Action.Unknown -> {
|
||||
Timber.w("Unable to perfom action: $action")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -313,13 +313,6 @@ internal class WalletViewModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun closeScreen(screen: AppScreen) {
|
||||
if (!screenLifecycleProvider.isBackgroundState.value) {
|
||||
stateHolder.clear()
|
||||
router.popBackStack(screen = screen)
|
||||
}
|
||||
}
|
||||
|
||||
private fun scrollToWallet(index: Int, onConsume: () -> Unit = {}) {
|
||||
stateHolder.update(
|
||||
ScrollToWalletTransformer(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.viewmodels
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
|
|
@ -23,16 +24,14 @@ internal class WalletsUpdateActionResolver @Inject constructor(
|
|||
) {
|
||||
|
||||
fun resolve(wallets: List<UserWallet>, currentState: WalletScreenState): Action {
|
||||
val selectedWallet = wallets.getSelectedWallet()
|
||||
val selectedWallet = getSelectedWalletSyncUseCase().getOrElse {
|
||||
error("Unable to find selected wallet: $it")
|
||||
}
|
||||
|
||||
val action = if (selectedWallet == null) {
|
||||
createNoSelectedWalletAction(wallets)
|
||||
val action = if (isFirstInitialization(currentState)) {
|
||||
createInitializeWalletsAction(wallets, selectedWallet)
|
||||
} else {
|
||||
if (isFirstInitialization(currentState)) {
|
||||
createInitializeWalletsAction(wallets, selectedWallet)
|
||||
} else {
|
||||
getUpdateContentAction(currentState, wallets, selectedWallet)
|
||||
}
|
||||
getUpdateContentAction(currentState, wallets, selectedWallet)
|
||||
}
|
||||
|
||||
Timber.d("Resolved action: $action")
|
||||
|
|
@ -40,22 +39,6 @@ internal class WalletsUpdateActionResolver @Inject constructor(
|
|||
return action
|
||||
}
|
||||
|
||||
private fun List<UserWallet>.getSelectedWallet(): UserWallet? {
|
||||
return when {
|
||||
isEmpty() -> null
|
||||
size == 1 -> if (first().isLocked) null else first()
|
||||
else -> getSelectedWalletSyncUseCase().fold(ifLeft = { null }, ifRight = { it })
|
||||
}
|
||||
}
|
||||
|
||||
private fun createNoSelectedWalletAction(wallets: List<UserWallet>): Action {
|
||||
return when {
|
||||
wallets.isEmpty() -> Action.NoWallets
|
||||
wallets.all(UserWallet::isLocked) -> Action.NoAccessibleWallets
|
||||
else -> Action.Unknown
|
||||
}
|
||||
}
|
||||
|
||||
private fun isFirstInitialization(state: WalletScreenState): Boolean {
|
||||
return state.selectedWalletIndex == NOT_INITIALIZED_WALLET_INDEX
|
||||
}
|
||||
|
|
@ -289,10 +272,6 @@ internal class WalletsUpdateActionResolver @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
data object NoAccessibleWallets : Action()
|
||||
|
||||
data object NoWallets : Action()
|
||||
|
||||
data object Unknown : Action()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,11 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.viewmodels.intents
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.domain.card.DeleteSavedAccessCodesUseCase
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.core.navigation.ReduxNavController
|
||||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.DeleteWalletUseCase
|
||||
|
|
@ -43,6 +47,7 @@ internal class WalletCardClickIntentsImplementor @Inject constructor(
|
|||
private val deleteSavedAccessCodesUseCase: DeleteSavedAccessCodesUseCase,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val reduxStateHolder: ReduxStateHolder,
|
||||
private val reduxNavController: ReduxNavController,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : BaseWalletClickIntents(), WalletCardClickIntents {
|
||||
|
||||
|
|
@ -81,18 +86,26 @@ internal class WalletCardClickIntentsImplementor @Inject constructor(
|
|||
viewModelScope.launch(dispatchers.main) {
|
||||
walletScreenContentLoader.cancel(userWalletId)
|
||||
|
||||
val deletedUserWallet = getUserWalletUseCase(userWalletId).getOrNull() ?: return@launch
|
||||
val walletToDelete = getUserWalletUseCase(userWalletId).getOrNull() ?: return@launch
|
||||
val hasUserWallets = deleteWalletUseCase(userWalletId).getOrElse {
|
||||
Timber.e("Unable to delete user wallet: $it")
|
||||
return@launch
|
||||
}
|
||||
|
||||
deleteSavedAccessCodesUseCase(cardId = deletedUserWallet.cardId)
|
||||
.onLeft { Timber.e(it.toString()) }
|
||||
deleteSavedAccessCodesUseCase(cardId = walletToDelete.cardId).onLeft {
|
||||
Timber.e("Unable to delete user wallet access code: $it")
|
||||
}
|
||||
|
||||
deleteWalletUseCase(userWalletId)
|
||||
.onRight {
|
||||
getSelectedWalletSyncUseCase().getOrNull()?.let {
|
||||
reduxStateHolder.onUserWalletSelected(it)
|
||||
}
|
||||
if (hasUserWallets) {
|
||||
val selectedWallet = getSelectedWalletSyncUseCase().getOrElse {
|
||||
error("Unable to find selected wallet: $it")
|
||||
}
|
||||
.onLeft { Timber.e(it.toString()) }
|
||||
|
||||
reduxStateHolder.onUserWalletSelected(selectedWallet)
|
||||
} else {
|
||||
stateHolder.clear()
|
||||
reduxNavController.navigate(NavigationAction.PopBackTo(AppScreen.Home))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue