Updated on 2026-08-14

This commit is contained in:
Tangem 2024-08-01 13:33:39 +03:00
parent 186f8f77c4
commit e8301388db
8 changed files with 89 additions and 63 deletions

View file

@ -5,11 +5,8 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.fragment.app.viewModels
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.common.routing.AppRouter
import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.store
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@ -25,12 +22,6 @@ internal class CardSettingsFragment : ComposeFragment() {
override fun ScreenContent(modifier: Modifier) {
val state by viewModel.screenState.collectAsStateWithLifecycle()
CardSettingsScreen(
modifier = modifier,
state = state,
onBackClick = {
store.dispatchNavigationAction(AppRouter::pop)
},
)
CardSettingsScreen(modifier = modifier, state = state)
}
}

View file

@ -1,10 +1,13 @@
package com.tangem.tap.features.details.ui.cardsettings
import android.content.res.Configuration
import androidx.compose.foundation.*
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@ -13,18 +16,14 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.tap.features.details.ui.common.DetailsMainButton
import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold
import com.tangem.wallet.R
@Composable
internal fun CardSettingsScreen(
state: CardSettingsScreenState,
onBackClick: () -> Unit,
modifier: Modifier = Modifier,
) {
internal fun CardSettingsScreen(state: CardSettingsScreenState, modifier: Modifier = Modifier) {
val needReadCard = state.cardDetails == null
SettingsScreensScaffold(
@ -37,7 +36,7 @@ internal fun CardSettingsScreen(
}
},
titleRes = R.string.card_settings_title,
onBackClick = onBackClick,
onBackClick = state.onBackClick,
)
}
@ -180,7 +179,7 @@ private fun CardSettings(state: CardSettingsScreenState) {
// region Preview
@Composable
private fun CardSettingsScreenStateSample() {
CardSettingsScreen(state = CardSettingsScreenState(onScanCardClick = {}, onElementClick = {}), {})
CardSettingsScreen(state = CardSettingsScreenState(onBackClick = {}, onScanCardClick = {}, onElementClick = {}))
}
@Preview(showBackground = true, widthDp = 360)

View file

@ -12,6 +12,7 @@ internal data class CardSettingsScreenState(
val cardDetails: List<CardInfo>? = null,
val onScanCardClick: () -> Unit,
val onElementClick: (CardInfo) -> Unit,
val onBackClick: () -> Unit,
)
internal sealed class CardInfo(

View file

@ -7,6 +7,7 @@ import androidx.lifecycle.viewModelScope
import com.tangem.common.CompletionResult
import com.tangem.common.doOnSuccess
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.common.routing.bundle.unbundle
import com.tangem.core.analytics.Analytics
import com.tangem.domain.card.ScanCardProcessor
@ -23,14 +24,14 @@ import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.domain.extensions.signedHashesCount
import com.tangem.tap.domain.sdk.TangemSdkManager
import com.tangem.tap.features.details.ui.cardsettings.domain.CardSettingsInteractor
import com.tangem.tap.features.details.ui.common.utils.*
import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
import com.tangem.tap.store
import com.tangem.wallet.R
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@ -39,6 +40,7 @@ import javax.inject.Inject
internal class CardSettingsViewModel @Inject constructor(
private val scanCardProcessor: ScanCardProcessor,
private val tangemSdkManager: TangemSdkManager,
private val cardSettingsInteractor: CardSettingsInteractor,
savedStateHandle: SavedStateHandle,
) : ViewModel() {
@ -46,24 +48,28 @@ internal class CardSettingsViewModel @Inject constructor(
?.unbundle(UserWalletId.serializer())
?: error("User wallet ID is required for CardSettingsViewModel")
private val scannedScanResponse = MutableStateFlow<ScanResponse?>(value = null)
val screenState: MutableStateFlow<CardSettingsScreenState> = MutableStateFlow(getInitialState())
init {
cardSettingsInteractor.scannedScanResponse
.filterNotNull()
.onEach(::updateCardDetails)
.launchIn(viewModelScope)
}
private fun getInitialState() = CardSettingsScreenState(
cardDetails = null,
onElementClick = ::handleClickingItem,
onScanCardClick = ::scanCard,
onBackClick = ::onBackClick,
)
private fun scanCard() = viewModelScope.launch {
scanCardProcessor.scan(allowsRequestAccessCodeFromRepository = true)
.doOnSuccess { scanResponse ->
scannedScanResponse.value = scanResponse
val scannedUserWalletId = UserWalletIdBuilder.scanResponse(scanResponse).build()
if (userWalletId == scannedUserWalletId || scannedUserWalletId == null) {
updateCardDetails(scanResponse)
cardSettingsInteractor.initialize(scanResponse)
} else {
store.dispatchDialogShow(
AppDialog.SimpleOkDialogRes(
@ -141,16 +147,14 @@ internal class CardSettingsViewModel @Inject constructor(
}
}
is CardInfo.AccessCodeRecovery -> {
store.dispatchNavigationAction {
push(route = AppRoute.AccessCodeRecovery(userWalletId))
}
store.dispatchNavigationAction { push(AppRoute.AccessCodeRecovery) }
}
else -> {}
}
}
private fun resetWalletToFactorySettings() {
val scanResponse = requireNotNull(scannedScanResponse.value) {
val scanResponse = requireNotNull(cardSettingsInteractor.scannedScanResponse.value) {
"Impossible to reset card if ScanResponse is null"
}
@ -181,7 +185,7 @@ internal class CardSettingsViewModel @Inject constructor(
}
private fun changeAccessCode() = viewModelScope.launch {
val scanResponse = requireNotNull(scannedScanResponse.value) { "Scan response is null" }
val scanResponse = requireNotNull(cardSettingsInteractor.scannedScanResponse.value) { "Scan response is null" }
when (val result = tangemSdkManager.setAccessCode(scanResponse.card.cardId)) {
is CompletionResult.Success -> Analytics.send(Settings.CardSettings.UserCodeChanged())
@ -196,4 +200,9 @@ internal class CardSettingsViewModel @Inject constructor(
val isNotAllowed = hasPermanentWallet || cardTypesResolver.isStart2Coin()
return !isNotAllowed
}
private fun onBackClick() {
cardSettingsInteractor.clear()
store.dispatchNavigationAction(AppRouter::pop)
}
}

View file

@ -1,23 +1,16 @@
package com.tangem.tap.features.details.ui.cardsettings.coderecovery
import android.os.Bundle
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import arrow.core.getOrElse
import com.tangem.common.doOnSuccess
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.common.routing.bundle.unbundle
import com.tangem.core.analytics.Analytics
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Settings
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.domain.sdk.TangemSdkManager
import com.tangem.tap.features.details.ui.cardsettings.domain.CardSettingsInteractor
import com.tangem.tap.features.details.ui.common.utils.isAccessCodeRecoveryEnabled
import com.tangem.tap.store
import dagger.hilt.android.lifecycle.HiltViewModel
@ -28,25 +21,21 @@ import javax.inject.Inject
@HiltViewModel
internal class AccessCodeRecoveryViewModel @Inject constructor(
private val getUserWalletUseCase: GetUserWalletUseCase,
private val tangemSdkManager: TangemSdkManager,
savedStateHandle: SavedStateHandle,
private val cardSettingsInteractor: CardSettingsInteractor,
) : ViewModel() {
private val userWalletId = savedStateHandle.get<Bundle>(AppRoute.AccessCodeRecovery.USER_WALLET_ID_KEY)
?.unbundle(UserWalletId.serializer())
?: error("UserWalletId is required for AccessCodeRecoveryViewModel")
private val scannedScanResponse = cardSettingsInteractor.scannedScanResponse.value
?: error("Scan response is null")
val screenState = MutableStateFlow(
value = getInitialState(),
)
private fun getInitialState(): AccessCodeRecoveryScreenState {
val userWallet = getUserWallet()
val isEnabled = isAccessCodeRecoveryEnabled(
typeResolver = userWallet.scanResponse.cardTypesResolver,
card = userWallet.scanResponse.card,
typeResolver = scannedScanResponse.cardTypesResolver,
card = scannedScanResponse.card,
)
return AccessCodeRecoveryScreenState(
@ -59,11 +48,10 @@ internal class AccessCodeRecoveryViewModel @Inject constructor(
}
private fun saveChanges() = viewModelScope.launch {
val userWallet = getUserWallet()
val isEnabled = screenState.value.enabledSelection
tangemSdkManager
.setAccessCodeRecoveryEnabled(userWallet.cardId, isEnabled)
.setAccessCodeRecoveryEnabled(scannedScanResponse.card.cardId, isEnabled)
.doOnSuccess {
Analytics.send(
Settings.CardSettings.AccessCodeRecoveryChanged(
@ -71,6 +59,16 @@ internal class AccessCodeRecoveryViewModel @Inject constructor(
),
)
cardSettingsInteractor.update { scanResponse ->
scanResponse.copy(
card = scanResponse.card.copy(
userSettings = scanResponse.card.userSettings?.copy(
isUserCodeRecoveryAllowed = isEnabled,
),
),
)
}
store.dispatchNavigationAction(AppRouter::pop)
}
}
@ -78,14 +76,9 @@ internal class AccessCodeRecoveryViewModel @Inject constructor(
private fun selectOption(isEnabled: Boolean) {
screenState.update {
it.copy(
enabledSelection = isEnabled,
isSaveChangesEnabled = isEnabled != it.enabledOnCard,
)
}
}
private fun getUserWallet(): UserWallet {
return getUserWalletUseCase(userWalletId).getOrElse {
error("Unable to get user wallet $userWalletId: $it")
}
}
}

View file

@ -0,0 +1,35 @@
package com.tangem.tap.features.details.ui.cardsettings.domain
import com.tangem.domain.models.scan.ScanResponse
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import javax.inject.Inject
import javax.inject.Singleton
/**
* Interactor for sharing logic and data between all card settings screens
*
[REDACTED_AUTHOR]
*/
@Singleton
internal class CardSettingsInteractor @Inject constructor() {
private val _scannedScanResponse = MutableStateFlow<ScanResponse?>(value = null)
val scannedScanResponse: StateFlow<ScanResponse?> = _scannedScanResponse
fun initialize(scanResponse: ScanResponse) {
_scannedScanResponse.value = scanResponse
}
fun update(transform: (ScanResponse) -> ScanResponse) {
_scannedScanResponse.update {
requireNotNull(it)
transform(it)
}
}
fun clear() {
_scannedScanResponse.value = null
}
}

View file

@ -23,6 +23,7 @@ import com.tangem.tap.common.analytics.events.Settings
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.common.extensions.onUserWalletSelected
import com.tangem.tap.features.details.redux.ResetCardDialog
import com.tangem.tap.features.details.ui.cardsettings.domain.CardSettingsInteractor
import com.tangem.tap.features.details.ui.common.utils.getResetToFactoryDescription
import com.tangem.tap.store
import com.tangem.utils.extensions.DELAY_SDK_DIALOG_CLOSE
@ -44,6 +45,7 @@ internal class ResetCardViewModel @Inject constructor(
private val deleteWalletUseCase: DeleteWalletUseCase,
private val userWalletsListManager: UserWalletsListManager,
private val analyticsEventHandler: AnalyticsEventHandler,
private val cardSettingsInteractor: CardSettingsInteractor,
savedStateHandle: SavedStateHandle,
) : ViewModel() {
@ -255,6 +257,8 @@ internal class ResetCardViewModel @Inject constructor(
}
private fun finishFullReset() {
cardSettingsInteractor.clear()
val newSelectedWallet = userWalletsListManager.selectedUserWalletSync
if (newSelectedWallet != null) {