Updated on 2026-08-14
This commit is contained in:
commit
8164aa0c1a
1109 changed files with 35558 additions and 15187 deletions
|
|
@ -14,7 +14,8 @@ import androidx.lifecycle.Lifecycle
|
|||
import androidx.transition.TransitionInflater
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
||||
|
|
@ -36,7 +37,7 @@ abstract class BaseFragment(layoutId: Int) : Fragment(layoutId), FragmentOnBackP
|
|||
}
|
||||
|
||||
override fun handleOnBackPressed() {
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
store.dispatchNavigationAction(AppRouter::pop)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.tap.features.customtoken.impl.presentation
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
|
|
@ -8,7 +9,6 @@ import androidx.compose.ui.platform.LocalLifecycleOwner
|
|||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.components.SystemBarsEffect
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.ui.AddCustomTokenScreen
|
||||
|
|
@ -32,15 +32,12 @@ internal class AddCustomTokenFragment : ComposeFragment() {
|
|||
val viewModel = hiltViewModel<AddCustomTokenViewModel>().apply {
|
||||
LocalLifecycleOwner.current.lifecycle.addObserver(this)
|
||||
}
|
||||
val statusBarColor = TangemTheme.colors.background.secondary
|
||||
SystemBarsEffect {
|
||||
setSystemBarsColor(color = statusBarColor)
|
||||
}
|
||||
|
||||
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
|
||||
AddCustomTokenScreen(
|
||||
modifier = Modifier.systemBarsPadding(),
|
||||
modifier = Modifier
|
||||
.background(TangemTheme.colors.background.primary)
|
||||
.systemBarsPadding(),
|
||||
stateHolder = state,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
package com.tangem.tap.features.customtoken.impl.presentation.routers
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.common.routing.utils.popTo
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -12,11 +14,11 @@ import com.tangem.wallet.R
|
|||
internal class DefaultCustomTokenRouter : CustomTokenRouter {
|
||||
|
||||
override fun popBackStack() {
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
store.dispatchNavigationAction(AppRouter::pop)
|
||||
}
|
||||
|
||||
override fun openWalletScreen() {
|
||||
store.dispatch(NavigationAction.PopBackTo(screen = AppScreen.Wallet))
|
||||
store.dispatchNavigationAction { popTo<AppRoute.Wallet>() }
|
||||
}
|
||||
|
||||
override fun openUnsupportedNetworkAlert(blockchain: Blockchain) {
|
||||
|
|
@ -32,7 +34,7 @@ internal class DefaultCustomTokenRouter : CustomTokenRouter {
|
|||
val alert = AppDialog.SimpleOkDialogRes(
|
||||
headerId = R.string.common_error,
|
||||
messageId = R.string.common_unknown_error,
|
||||
onOk = { store.dispatch(NavigationAction.PopBackTo()) },
|
||||
onOk = { store.dispatchNavigationAction(AppRouter::pop) },
|
||||
)
|
||||
store.dispatchDialogShow(alert)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import com.tangem.domain.demo.DemoConfig
|
|||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.extensions.dispatchNotification
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupAction
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -21,7 +20,6 @@ object DemoHelper {
|
|||
private val disabledActionFeatures = listOf(
|
||||
WalletConnectAction.StartWalletConnect::class.java,
|
||||
BackupAction.StartBackup::class.java,
|
||||
DetailsAction.ResetToFactory.Start::class.java,
|
||||
)
|
||||
|
||||
fun isDemoCard(scanResponse: ScanResponse): Boolean = isDemoCardId(scanResponse.card.cardId)
|
||||
|
|
|
|||
|
|
@ -14,26 +14,6 @@ sealed class DetailsAction : Action {
|
|||
val shouldSaveUserWallets: Boolean,
|
||||
) : DetailsAction()
|
||||
|
||||
data object ReCreateTwinsWallet : DetailsAction()
|
||||
|
||||
sealed class ResetToFactory : DetailsAction() {
|
||||
data object Start : ResetToFactory()
|
||||
data object Proceed : ResetToFactory()
|
||||
data class AcceptCondition1(val accepted: Boolean) : ResetToFactory()
|
||||
data class AcceptCondition2(val accepted: Boolean) : ResetToFactory()
|
||||
data object Failure : ResetToFactory()
|
||||
data object Success : ResetToFactory()
|
||||
|
||||
data class ShowDialog(val dialog: CardSettingsState.Dialog) : ResetToFactory()
|
||||
|
||||
data object DismissDialog : ResetToFactory()
|
||||
}
|
||||
|
||||
data object ScanCard : DetailsAction()
|
||||
|
||||
data class PrepareCardSettingsData(val scanResponse: ScanResponse) : DetailsAction()
|
||||
|
||||
data object ResetCardSettingsData : DetailsAction()
|
||||
data object ScanAndSaveUserWallet : DetailsAction() {
|
||||
|
||||
data object Success : DetailsAction()
|
||||
|
|
@ -43,26 +23,6 @@ sealed class DetailsAction : Action {
|
|||
|
||||
data object DismissError : DetailsAction()
|
||||
|
||||
sealed class AccessCodeRecovery : DetailsAction() {
|
||||
object Open : AccessCodeRecovery()
|
||||
data class SaveChanges(val enabled: Boolean) : AccessCodeRecovery() {
|
||||
data class Success(val enabled: Boolean) : AccessCodeRecovery()
|
||||
}
|
||||
|
||||
data class SelectOption(val enabled: Boolean) : AccessCodeRecovery()
|
||||
}
|
||||
|
||||
sealed class ManageSecurity : DetailsAction() {
|
||||
data object OpenSecurity : ManageSecurity()
|
||||
data class SelectOption(val option: SecurityOption) : ManageSecurity()
|
||||
data object SaveChanges : ManageSecurity() {
|
||||
data object Success : ManageSecurity()
|
||||
data object Failure : ManageSecurity()
|
||||
}
|
||||
|
||||
data object ChangeAccessCode : ManageSecurity()
|
||||
}
|
||||
|
||||
sealed class AppSettings : DetailsAction() {
|
||||
data class SwitchPrivacySetting(
|
||||
val enable: Boolean,
|
||||
|
|
@ -96,6 +56,8 @@ sealed class DetailsAction : Action {
|
|||
data class ChangeAppCurrency(
|
||||
val currency: AppCurrency,
|
||||
) : AppSettings()
|
||||
|
||||
data class Prepare(val state: AppSettingsState) : AppSettings()
|
||||
}
|
||||
|
||||
data class ChangeAppCurrency(val currency: AppCurrency) : DetailsAction()
|
||||
|
|
|
|||
|
|
@ -1,40 +1,36 @@
|
|||
package com.tangem.tap.features.details.redux
|
||||
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import com.tangem.common.*
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.core.UserCodeRequestPolicy
|
||||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.common.routing.utils.popTo
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
import com.tangem.domain.common.TapWorkarounds.isTangemTwins
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListError
|
||||
import com.tangem.domain.wallets.legacy.asLockable
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Settings
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.extensions.onUserWalletSelected
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.saveIn
|
||||
import com.tangem.wallet.R
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
|
|
@ -42,17 +38,13 @@ import kotlinx.coroutines.flow.flow
|
|||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.rekotlin.Action
|
||||
import org.rekotlin.Middleware
|
||||
import timber.log.Timber
|
||||
import com.tangem.core.analytics.models.AnalyticsParam as CoreAnalyticsParam
|
||||
|
||||
class DetailsMiddleware {
|
||||
private val eraseWalletMiddleware = EraseWalletMiddleware()
|
||||
private val manageSecurityMiddleware = ManageSecurityMiddleware()
|
||||
private val appSettingsMiddleware = AppSettingsMiddleware()
|
||||
private val accessCodeRecoveryMiddleware = AccessCodeRecoveryMiddleware()
|
||||
val detailsMiddleware: Middleware<AppState> = { _, stateProvider ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
|
|
@ -69,152 +61,11 @@ class DetailsMiddleware {
|
|||
|
||||
private fun handleAction(state: DetailsState, action: Action) {
|
||||
when (action) {
|
||||
is DetailsAction.ResetToFactory -> eraseWalletMiddleware.handle(action)
|
||||
is DetailsAction.ManageSecurity -> manageSecurityMiddleware.handle(action, state)
|
||||
is DetailsAction.AppSettings -> appSettingsMiddleware.handle(state, action)
|
||||
is DetailsAction.ReCreateTwinsWallet -> {
|
||||
store.dispatch(TwinCardsAction.SetMode(CreateTwinWalletMode.RecreateWallet))
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingTwins))
|
||||
}
|
||||
is DetailsAction.AccessCodeRecovery -> accessCodeRecoveryMiddleware.handle(state, action)
|
||||
is DetailsAction.ScanCard -> scanCard(state)
|
||||
is DetailsAction.ScanAndSaveUserWallet -> scanAndSaveUserWallet()
|
||||
}
|
||||
}
|
||||
|
||||
class EraseWalletMiddleware {
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
fun handle(action: DetailsAction.ResetToFactory) {
|
||||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
|
||||
when (action) {
|
||||
is DetailsAction.ResetToFactory.Start -> {
|
||||
val card = store.state.detailsState.cardSettingsState?.card ?: return
|
||||
if (card.isTangemTwins) {
|
||||
store.dispatch(DetailsAction.ReCreateTwinsWallet)
|
||||
return
|
||||
} else {
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.ResetToFactory))
|
||||
}
|
||||
}
|
||||
is DetailsAction.ResetToFactory.Proceed -> {
|
||||
val card = store.state.detailsState.cardSettingsState?.card ?: return
|
||||
scope.launch {
|
||||
val userWalletId = UserWalletIdBuilder.card(card).build()
|
||||
|
||||
// we must require a password regardless of biometric settings
|
||||
val policy = tangemSdkManager.userCodeRequestPolicy
|
||||
val doBeforeErase = {
|
||||
val type = if (card.isAccessCodeSet) {
|
||||
UserCodeType.AccessCode
|
||||
} else if (card.isPasscodeSet == true) {
|
||||
UserCodeType.Passcode
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
type?.let {
|
||||
tangemSdkManager.setUserCodeRequestPolicy(UserCodeRequestPolicy.Always(type))
|
||||
}
|
||||
}
|
||||
|
||||
val doAfterErase = {
|
||||
tangemSdkManager.setUserCodeRequestPolicy(policy)
|
||||
}
|
||||
|
||||
doBeforeErase()
|
||||
tangemSdkManager.resetToFactorySettings(card.cardId, true)
|
||||
.flatMap {
|
||||
userWalletsListManager.delete(listOfNotNull(userWalletId))
|
||||
}
|
||||
.flatMap {
|
||||
tangemSdkManager.deleteSavedUserCodes(setOf(card.cardId))
|
||||
}
|
||||
.doOnSuccess {
|
||||
Analytics.send(Settings.CardSettings.FactoryResetFinished())
|
||||
|
||||
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync
|
||||
if (selectedUserWallet != null) {
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Wallet))
|
||||
store.onUserWalletSelected(selectedUserWallet)
|
||||
} else {
|
||||
val isLocked = runCatching { userWalletsListManager.asLockable()?.isLockedSync }
|
||||
.fold(onSuccess = { true }, onFailure = { false })
|
||||
if (isLocked && userWalletsListManager.hasUserWallets) {
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Welcome))
|
||||
} else {
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home))
|
||||
}
|
||||
}
|
||||
}
|
||||
.doOnFailure { error ->
|
||||
if (error is TangemSdkError && error !is TangemSdkError.UserCancelled) {
|
||||
Analytics.send(Settings.CardSettings.FactoryResetFinished(error = error))
|
||||
}
|
||||
}
|
||||
.doOnResult {
|
||||
doAfterErase()
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ManageSecurityMiddleware {
|
||||
@Suppress("ComplexMethod")
|
||||
fun handle(action: DetailsAction.ManageSecurity, detailsState: DetailsState) {
|
||||
when (action) {
|
||||
is DetailsAction.ManageSecurity.OpenSecurity -> {
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.DetailsSecurity))
|
||||
}
|
||||
is DetailsAction.ManageSecurity.SaveChanges -> {
|
||||
val cardSettingsState = detailsState.cardSettingsState
|
||||
val cardId = cardSettingsState?.card?.cardId
|
||||
val selectedOption = cardSettingsState?.manageSecurityState?.selectedOption
|
||||
scope.launch {
|
||||
val result = when (selectedOption) {
|
||||
SecurityOption.LongTap -> tangemSdkManager.setLongTap(cardId)
|
||||
SecurityOption.PassCode -> tangemSdkManager.setPasscode(cardId)
|
||||
SecurityOption.AccessCode -> tangemSdkManager.setAccessCode(cardId)
|
||||
else -> return@launch
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
val paramValue = AnalyticsParam.SecurityMode.from(selectedOption)
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
Analytics.send(Settings.CardSettings.SecurityModeChanged(paramValue))
|
||||
store.dispatch(GlobalAction.UpdateSecurityOptions(selectedOption))
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
store.dispatch(DetailsAction.ManageSecurity.SaveChanges.Success)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
val error = result.error
|
||||
if (error is TangemSdkError && error !is TangemSdkError.UserCancelled) {
|
||||
Analytics.send(Settings.CardSettings.SecurityModeChanged(paramValue, error))
|
||||
}
|
||||
store.dispatch(DetailsAction.ManageSecurity.SaveChanges.Failure)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is DetailsAction.ManageSecurity.ChangeAccessCode -> {
|
||||
val card = store.state.detailsState.cardSettingsState?.card ?: return
|
||||
scope.launch {
|
||||
when (tangemSdkManager.setAccessCode(card.cardId)) {
|
||||
is CompletionResult.Success -> Analytics.send(Settings.CardSettings.UserCodeChanged())
|
||||
is CompletionResult.Failure -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AppSettingsMiddleware {
|
||||
|
||||
private val checkBiometricsStatusJobHolder = JobHolder()
|
||||
|
|
@ -246,6 +97,7 @@ class DetailsMiddleware {
|
|||
is DetailsAction.AppSettings.SwitchPrivacySetting.Success,
|
||||
is DetailsAction.AppSettings.SwitchPrivacySetting.Failure,
|
||||
is DetailsAction.AppSettings.BiometricsStatusChanged,
|
||||
is DetailsAction.AppSettings.Prepare,
|
||||
-> Unit
|
||||
}
|
||||
}
|
||||
|
|
@ -274,7 +126,7 @@ class DetailsMiddleware {
|
|||
|
||||
private fun enrollBiometrics() {
|
||||
Analytics.send(Settings.AppSettings.ButtonEnableBiometricAuthentication)
|
||||
store.dispatchOnMain(NavigationAction.OpenBiometricsSettings)
|
||||
activityResultCaller.openSystemBiometrySettings()
|
||||
}
|
||||
|
||||
private fun changeAppThemeMode(appThemeMode: AppThemeMode) {
|
||||
|
|
@ -395,7 +247,7 @@ class DetailsMiddleware {
|
|||
deleteSavedAccessCodes()
|
||||
store.inject(DaggerGraphState::walletsRepository).saveShouldSaveUserWallets(item = false)
|
||||
|
||||
store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Home))
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Home) }
|
||||
|
||||
return CompletionResult.Success(Unit)
|
||||
}
|
||||
|
|
@ -429,63 +281,6 @@ class DetailsMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
class AccessCodeRecoveryMiddleware {
|
||||
fun handle(state: DetailsState, action: DetailsAction.AccessCodeRecovery) {
|
||||
when (action) {
|
||||
is DetailsAction.AccessCodeRecovery.Open -> {
|
||||
Analytics.send(Settings.CardSettings.AccessCodeRecoveryButton())
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.AccessCodeRecovery))
|
||||
}
|
||||
is DetailsAction.AccessCodeRecovery.SaveChanges -> {
|
||||
scope.launch {
|
||||
tangemSdkManager
|
||||
.setAccessCodeRecoveryEnabled(state.cardSettingsState?.card?.cardId, action.enabled)
|
||||
.doOnSuccess {
|
||||
Analytics.send(
|
||||
Settings.CardSettings.AccessCodeRecoveryChanged(
|
||||
AnalyticsParam.AccessCodeRecoveryStatus.from(action.enabled),
|
||||
),
|
||||
)
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo())
|
||||
store.dispatchOnMain(
|
||||
DetailsAction.AccessCodeRecovery.SaveChanges.Success(action.enabled),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
is DetailsAction.AccessCodeRecovery.SelectOption -> Unit
|
||||
is DetailsAction.AccessCodeRecovery.SaveChanges.Success -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun scanCard(state: DetailsState) = scope.launch {
|
||||
store.inject(DaggerGraphState::scanCardProcessor)
|
||||
.scan(allowsRequestAccessCodeFromRepository = true)
|
||||
.doOnSuccess { scanResponse ->
|
||||
// if we use biometric, scanResponse in GlobalState is null, and crashes NPE on twin cards
|
||||
store.dispatch(GlobalAction.SaveScanResponse(scanResponse))
|
||||
val currentUserWalletId = state.scanResponse
|
||||
?.let { UserWalletIdBuilder.scanResponse(it).build() }
|
||||
val scannedUserWalletId = UserWalletIdBuilder.scanResponse(scanResponse)
|
||||
.build()
|
||||
val isSameWallet = currentUserWalletId == scannedUserWalletId
|
||||
|
||||
if (isSameWallet) {
|
||||
store.dispatchOnMain(
|
||||
DetailsAction.PrepareCardSettingsData(scanResponse = scanResponse),
|
||||
)
|
||||
} else {
|
||||
store.dispatchDialogShow(
|
||||
AppDialog.SimpleOkDialogRes(
|
||||
headerId = R.string.common_warning,
|
||||
messageId = R.string.error_wrong_wallet_tapped,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun scanAndSaveUserWallet() = scope.launch(Dispatchers.IO) {
|
||||
val cardSdkConfigRepository = store.inject(DaggerGraphState::cardSdkConfigRepository)
|
||||
|
||||
|
|
@ -502,7 +297,7 @@ class DetailsMiddleware {
|
|||
store.dispatchWithMain(DetailsAction.ScanAndSaveUserWallet.Success)
|
||||
},
|
||||
disclaimerWillShow = {
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo())
|
||||
store.dispatchNavigationAction(AppRouter::pop)
|
||||
},
|
||||
onSuccess = { scanResponse ->
|
||||
createUserWallet(scanResponse)
|
||||
|
|
@ -546,7 +341,7 @@ class DetailsMiddleware {
|
|||
store.onUserWalletSelected(userWallet)
|
||||
|
||||
store.dispatchWithMain(DetailsAction.ScanAndSaveUserWallet.Success)
|
||||
store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Wallet))
|
||||
store.dispatchNavigationAction { popTo<AppRoute.Wallet>() }
|
||||
}
|
||||
.doOnFailure { error ->
|
||||
if (error is UserWalletsListError.WalletAlreadySaved) {
|
||||
|
|
@ -554,7 +349,7 @@ class DetailsMiddleware {
|
|||
store.onUserWalletSelected(userWallet)
|
||||
|
||||
store.dispatchWithMain(DetailsAction.ScanAndSaveUserWallet.Success)
|
||||
store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Wallet))
|
||||
store.dispatchNavigationAction { popTo<AppRoute.Wallet>() }
|
||||
} else {
|
||||
Timber.e(error, "Unable to create user wallet")
|
||||
handleError(error, prevUseBiometricsForAccessCode)
|
||||
|
|
|
|||
|
|
@ -1,21 +1,15 @@
|
|||
package com.tangem.tap.features.details.redux
|
||||
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
import com.tangem.domain.common.CardTypesResolver
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.domain.extensions.signedHashesCount
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.rekotlin.Action
|
||||
import java.util.EnumSet
|
||||
|
||||
object DetailsReducer {
|
||||
fun reduce(action: Action, state: AppState): DetailsState = internalReduce(action, state)
|
||||
|
|
@ -29,16 +23,6 @@ private fun internalReduce(action: Action, state: AppState): DetailsState {
|
|||
is DetailsAction.PrepareScreen -> {
|
||||
handlePrepareScreen(action, state)
|
||||
}
|
||||
is DetailsAction.PrepareCardSettingsData -> {
|
||||
handlePrepareCardSettingsScreen(scanResponse = action.scanResponse, state = detailsState)
|
||||
}
|
||||
is DetailsAction.ResetCardSettingsData -> detailsState.copy(cardSettingsState = null)
|
||||
is DetailsAction.ResetToFactory -> {
|
||||
handleEraseWallet(action, detailsState)
|
||||
}
|
||||
is DetailsAction.ManageSecurity -> {
|
||||
handleSecurityAction(action, detailsState)
|
||||
}
|
||||
is DetailsAction.AppSettings -> {
|
||||
handlePrivacyAction(action, detailsState)
|
||||
}
|
||||
|
|
@ -47,7 +31,6 @@ private fun internalReduce(action: Action, state: AppState): DetailsState {
|
|||
selectedAppCurrency = action.currency,
|
||||
),
|
||||
)
|
||||
is DetailsAction.AccessCodeRecovery -> handleAccessCodeRecoveryAction(action, detailsState)
|
||||
is DetailsAction.ScanAndSaveUserWallet -> detailsState.copy(
|
||||
isScanningInProgress = true,
|
||||
)
|
||||
|
|
@ -61,19 +44,12 @@ private fun internalReduce(action: Action, state: AppState): DetailsState {
|
|||
is DetailsAction.DismissError -> detailsState.copy(
|
||||
error = null,
|
||||
)
|
||||
else -> detailsState
|
||||
}
|
||||
}
|
||||
|
||||
private fun handlePrepareScreen(action: DetailsAction.PrepareScreen, state: AppState): DetailsState {
|
||||
return DetailsState(
|
||||
scanResponse = action.scanResponse,
|
||||
// If the current screen is ResetToFactory, we must save the ResetToFactory's state
|
||||
cardSettingsState = if (store.state.navigationState.backStack.lastOrNull() == AppScreen.ResetToFactory) {
|
||||
state.detailsState.cardSettingsState
|
||||
} else {
|
||||
null
|
||||
},
|
||||
createBackupAllowed = action.scanResponse.card.backupStatus == CardDTO.BackupStatus.NoBackup,
|
||||
appSettingsState = AppSettingsState(
|
||||
isBiometricsAvailable = runBlocking {
|
||||
|
|
@ -96,139 +72,6 @@ private fun handlePrepareScreen(action: DetailsAction.PrepareScreen, state: AppS
|
|||
)
|
||||
}
|
||||
|
||||
private fun handlePrepareCardSettingsScreen(scanResponse: ScanResponse, state: DetailsState): DetailsState {
|
||||
val cardTypesResolver = scanResponse.cardTypesResolver
|
||||
val card = scanResponse.card
|
||||
val isTangemWallet = cardTypesResolver.isTangemWallet() || cardTypesResolver.isWallet2()
|
||||
val isShowPasswordResetRadioButton = isTangemWallet && card.backupStatus is CardDTO.BackupStatus.Active
|
||||
|
||||
val cardSettingsState = CardSettingsState(
|
||||
cardInfo = card.toCardInfo(cardTypesResolver),
|
||||
scanResponse = scanResponse,
|
||||
manageSecurityState = prepareSecurityOptions(card, cardTypesResolver),
|
||||
card = scanResponse.card,
|
||||
resetCardAllowed = isResetToFactoryAllowedByCard(card, cardTypesResolver),
|
||||
resetButtonEnabled = false,
|
||||
condition1Checked = false,
|
||||
condition2Checked = false,
|
||||
accessCodeRecovery = if (cardTypesResolver.isWallet2()) {
|
||||
val enabled = card.userSettings?.isUserCodeRecoveryAllowed ?: false
|
||||
AccessCodeRecoveryState(
|
||||
enabledOnCard = enabled,
|
||||
enabledSelection = enabled,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
},
|
||||
isShowPasswordResetRadioButton = isShowPasswordResetRadioButton,
|
||||
dialog = null,
|
||||
)
|
||||
|
||||
return state.copy(cardSettingsState = cardSettingsState)
|
||||
}
|
||||
|
||||
private fun prepareSecurityOptions(card: CardDTO, cardTypesResolver: CardTypesResolver): ManageSecurityState {
|
||||
val securityOption = when {
|
||||
card.isAccessCodeSet -> {
|
||||
SecurityOption.AccessCode
|
||||
}
|
||||
|
||||
card.isPasscodeSet == true -> {
|
||||
SecurityOption.PassCode
|
||||
}
|
||||
|
||||
else -> {
|
||||
SecurityOption.LongTap
|
||||
}
|
||||
}
|
||||
val allowedSecurityOptions = when {
|
||||
cardTypesResolver.isStart2Coin() || cardTypesResolver.isTangemNote() -> {
|
||||
EnumSet.of(SecurityOption.LongTap)
|
||||
}
|
||||
card.settings.isBackupAllowed -> {
|
||||
EnumSet.of(securityOption)
|
||||
}
|
||||
else ->
|
||||
prepareAllowedSecurityOptions(cardTypesResolver = cardTypesResolver, currentSecurityOption = securityOption)
|
||||
}
|
||||
return ManageSecurityState(
|
||||
currentOption = securityOption,
|
||||
allowedOptions = allowedSecurityOptions,
|
||||
selectedOption = securityOption,
|
||||
)
|
||||
}
|
||||
|
||||
private fun isResetToFactoryAllowedByCard(card: CardDTO, cardTypesResolver: CardTypesResolver): Boolean {
|
||||
val hasPermanentWallet = card.wallets.any { it.settings.isPermanent }
|
||||
val isNotAllowed = hasPermanentWallet || cardTypesResolver.isStart2Coin()
|
||||
return !isNotAllowed
|
||||
}
|
||||
|
||||
private fun handleEraseWallet(action: DetailsAction.ResetToFactory, state: DetailsState): DetailsState {
|
||||
val cardSettingsState = state.cardSettingsState
|
||||
return when (action) {
|
||||
is DetailsAction.ResetToFactory.AcceptCondition1 -> {
|
||||
val warning1Checked = action.accepted
|
||||
val resetButtonEnabled = if (cardSettingsState?.isShowPasswordResetRadioButton == true) {
|
||||
warning1Checked && cardSettingsState.condition2Checked
|
||||
} else {
|
||||
warning1Checked
|
||||
}
|
||||
state.copy(
|
||||
cardSettingsState = cardSettingsState?.copy(
|
||||
condition1Checked = action.accepted,
|
||||
resetButtonEnabled = resetButtonEnabled,
|
||||
),
|
||||
)
|
||||
}
|
||||
is DetailsAction.ResetToFactory.AcceptCondition2 -> {
|
||||
val warning2Checked = action.accepted
|
||||
state.copy(
|
||||
cardSettingsState = cardSettingsState?.copy(
|
||||
condition2Checked = action.accepted,
|
||||
resetButtonEnabled = warning2Checked && cardSettingsState.condition1Checked,
|
||||
),
|
||||
)
|
||||
}
|
||||
is DetailsAction.ResetToFactory.ShowDialog -> {
|
||||
state.copy(cardSettingsState = cardSettingsState?.copy(dialog = action.dialog))
|
||||
}
|
||||
is DetailsAction.ResetToFactory.DismissDialog -> {
|
||||
state.copy(cardSettingsState = cardSettingsState?.copy(dialog = null))
|
||||
}
|
||||
else -> state
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSecurityAction(action: DetailsAction.ManageSecurity, state: DetailsState): DetailsState {
|
||||
return when (action) {
|
||||
is DetailsAction.ManageSecurity.SelectOption -> {
|
||||
val manageSecurityState = state.cardSettingsState?.manageSecurityState?.copy(
|
||||
selectedOption = action.option,
|
||||
)
|
||||
state.copy(cardSettingsState = state.cardSettingsState?.copy(manageSecurityState = manageSecurityState))
|
||||
}
|
||||
is DetailsAction.ManageSecurity.SaveChanges.Success -> {
|
||||
// Setting options to show only LongTap from now on for non-twins
|
||||
val manageSecurityState = state.cardSettingsState?.manageSecurityState?.copy(
|
||||
currentOption = state.cardSettingsState.manageSecurityState.selectedOption,
|
||||
allowedOptions = state.scanResponse?.cardTypesResolver?.let {
|
||||
prepareAllowedSecurityOptions(
|
||||
cardTypesResolver = it,
|
||||
currentSecurityOption = state.cardSettingsState.manageSecurityState.selectedOption,
|
||||
)
|
||||
} ?: EnumSet.of(SecurityOption.LongTap),
|
||||
)
|
||||
state.copy(
|
||||
cardSettingsState = state.cardSettingsState?.copy(
|
||||
manageSecurityState = manageSecurityState,
|
||||
),
|
||||
)
|
||||
}
|
||||
else -> state
|
||||
}
|
||||
}
|
||||
|
||||
private fun handlePrivacyAction(action: DetailsAction.AppSettings, state: DetailsState): DetailsState {
|
||||
return when (action) {
|
||||
is DetailsAction.AppSettings.SwitchPrivacySetting -> state.copy(
|
||||
|
|
@ -281,69 +124,11 @@ private fun handlePrivacyAction(action: DetailsAction.AppSettings, state: Detail
|
|||
isHidingEnabled = action.hideBalance,
|
||||
),
|
||||
)
|
||||
is DetailsAction.AppSettings.Prepare -> state.copy(
|
||||
appSettingsState = action.state,
|
||||
)
|
||||
is DetailsAction.AppSettings.EnrollBiometrics,
|
||||
is DetailsAction.AppSettings.CheckBiometricsStatus,
|
||||
-> state
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAccessCodeRecoveryAction(
|
||||
action: DetailsAction.AccessCodeRecovery,
|
||||
state: DetailsState,
|
||||
): DetailsState {
|
||||
return when (action) {
|
||||
DetailsAction.AccessCodeRecovery.Open -> {
|
||||
val accessCodeRecovery = state.cardSettingsState?.accessCodeRecovery?.copy(
|
||||
enabledSelection = state.cardSettingsState.accessCodeRecovery.enabledOnCard,
|
||||
)
|
||||
state.copy(cardSettingsState = state.cardSettingsState?.copy(accessCodeRecovery = accessCodeRecovery))
|
||||
}
|
||||
is DetailsAction.AccessCodeRecovery.SaveChanges -> state
|
||||
is DetailsAction.AccessCodeRecovery.SelectOption -> {
|
||||
val accessCodeRecovery = state.cardSettingsState?.accessCodeRecovery?.copy(
|
||||
enabledSelection = action.enabled,
|
||||
)
|
||||
state.copy(cardSettingsState = state.cardSettingsState?.copy(accessCodeRecovery = accessCodeRecovery))
|
||||
}
|
||||
is DetailsAction.AccessCodeRecovery.SaveChanges.Success -> {
|
||||
val accessCodeRecovery = state.cardSettingsState?.accessCodeRecovery?.copy(
|
||||
enabledOnCard = action.enabled,
|
||||
enabledSelection = action.enabled,
|
||||
)
|
||||
state.copy(cardSettingsState = state.cardSettingsState?.copy(accessCodeRecovery = accessCodeRecovery))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun prepareAllowedSecurityOptions(
|
||||
cardTypesResolver: CardTypesResolver,
|
||||
currentSecurityOption: SecurityOption?,
|
||||
): EnumSet<SecurityOption> {
|
||||
val allowedSecurityOptions = EnumSet.of(SecurityOption.LongTap)
|
||||
|
||||
if (cardTypesResolver.isTangemTwins()) {
|
||||
allowedSecurityOptions.add(SecurityOption.PassCode)
|
||||
}
|
||||
if (currentSecurityOption == SecurityOption.AccessCode) {
|
||||
allowedSecurityOptions.add(SecurityOption.AccessCode)
|
||||
}
|
||||
if (currentSecurityOption == SecurityOption.PassCode) {
|
||||
allowedSecurityOptions.add(SecurityOption.PassCode)
|
||||
}
|
||||
return allowedSecurityOptions
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun CardDTO.toCardInfo(cardTypesResolver: CardTypesResolver): CardInfo {
|
||||
val cardId = this.cardId.chunked(4).joinToString(separator = " ")
|
||||
val issuer = this.issuer.name
|
||||
val signedHashes = this.signedHashesCount()
|
||||
|
||||
return CardInfo(
|
||||
cardId = cardId,
|
||||
issuer = issuer,
|
||||
signedHashes = signedHashes,
|
||||
isTwin = cardTypesResolver.isTangemTwins(),
|
||||
hasBackup = backupStatus?.isActive == true,
|
||||
)
|
||||
}
|
||||
|
|
@ -3,15 +3,11 @@ package com.tangem.tap.features.details.redux
|
|||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.entities.Button
|
||||
import org.rekotlin.StateType
|
||||
import java.util.EnumSet
|
||||
|
||||
data class DetailsState(
|
||||
val scanResponse: ScanResponse? = null,
|
||||
val cardSettingsState: CardSettingsState? = null,
|
||||
val privacyPolicyUrl: String? = null,
|
||||
val createBackupAllowed: Boolean = false,
|
||||
val isScanningInProgress: Boolean = false,
|
||||
|
|
@ -19,52 +15,13 @@ data class DetailsState(
|
|||
val appSettingsState: AppSettingsState = AppSettingsState(),
|
||||
) : StateType
|
||||
|
||||
data class CardInfo(
|
||||
val cardId: String,
|
||||
val issuer: String,
|
||||
val signedHashes: Int,
|
||||
val isTwin: Boolean,
|
||||
val hasBackup: Boolean,
|
||||
)
|
||||
|
||||
/**
|
||||
* @property enabledOnCard whether access code recovery is enabled on card
|
||||
* @property enabledSelection current selected option in app (not saved on card yet)
|
||||
*/
|
||||
data class AccessCodeRecoveryState(
|
||||
val enabledOnCard: Boolean,
|
||||
val enabledSelection: Boolean,
|
||||
)
|
||||
|
||||
data class CardSettingsState(
|
||||
val cardInfo: CardInfo,
|
||||
val card: CardDTO,
|
||||
val scanResponse: ScanResponse,
|
||||
val manageSecurityState: ManageSecurityState?,
|
||||
val resetCardAllowed: Boolean,
|
||||
val resetButtonEnabled: Boolean,
|
||||
val condition1Checked: Boolean,
|
||||
val condition2Checked: Boolean,
|
||||
val accessCodeRecovery: AccessCodeRecoveryState? = null,
|
||||
val isShowPasswordResetRadioButton: Boolean,
|
||||
val dialog: Dialog?,
|
||||
) {
|
||||
|
||||
sealed interface Dialog {
|
||||
data object StartResetDialog : Dialog
|
||||
data object ContinueResetDialog : Dialog
|
||||
data object InterruptedResetDialog : Dialog
|
||||
data object CompletedResetDialog : Dialog
|
||||
}
|
||||
sealed class ResetCardDialog {
|
||||
data object StartResetDialog : ResetCardDialog()
|
||||
data object ContinueResetDialog : ResetCardDialog()
|
||||
data object InterruptedResetDialog : ResetCardDialog()
|
||||
data object CompletedResetDialog : ResetCardDialog()
|
||||
}
|
||||
|
||||
data class ManageSecurityState(
|
||||
val currentOption: SecurityOption = SecurityOption.LongTap,
|
||||
val selectedOption: SecurityOption = currentOption,
|
||||
val allowedOptions: EnumSet<SecurityOption> = EnumSet.allOf(SecurityOption::class.java),
|
||||
val buttonProceed: Button = Button(true),
|
||||
)
|
||||
|
||||
data class AppSettingsState(
|
||||
val saveWallets: Boolean = false,
|
||||
val saveAccessCodes: Boolean = false,
|
||||
|
|
|
|||
|
|
@ -1,19 +1,17 @@
|
|||
package com.tangem.tap.features.details.redux.walletconnect
|
||||
|
||||
import androidx.core.os.bundleOf
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.domain.qrscanning.models.SourceType
|
||||
import com.tangem.feature.qrscanning.QrScanningRouter
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
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
|
||||
import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.Account
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectError
|
||||
|
|
@ -65,14 +63,7 @@ class WalletConnectMiddleware {
|
|||
// TODO check
|
||||
store.dispatchOnMain(WalletConnectAction.ShowClipboardOrScanQrDialog(uri))
|
||||
} else {
|
||||
store.dispatchOnMain(
|
||||
NavigationAction.NavigateTo(
|
||||
screen = AppScreen.QrScanning,
|
||||
bundle = bundleOf(
|
||||
QrScanningRouter.SOURCE_KEY to SourceType.WALLET_CONNECT,
|
||||
),
|
||||
),
|
||||
)
|
||||
store.dispatchNavigationAction { push(AppRoute.QrScanning(SourceType.WALLET_CONNECT)) }
|
||||
}
|
||||
}
|
||||
is WalletConnectAction.ShowClipboardOrScanQrDialog -> {
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ import com.tangem.blockchain.common.Blockchain
|
|||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.blockchain.common.derivation.DerivationStyle
|
||||
import com.tangem.core.navigation.StateDialog
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.redux.StateDialog
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcPreparedRequest
|
||||
import com.tangem.tap.domain.walletconnect2.domain.WcSignMessage
|
||||
import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents
|
||||
|
|
|
|||
|
|
@ -6,8 +6,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.components.SystemBarsEffect
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.components.NavigationBar3ButtonsScrim
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
|
|
@ -22,12 +21,8 @@ internal class AppCurrencySelectorFragment : ComposeFragment() {
|
|||
|
||||
@Composable
|
||||
override fun ScreenContent(modifier: Modifier) {
|
||||
val systemBarsColor = TangemTheme.colors.background.secondary
|
||||
SystemBarsEffect {
|
||||
setSystemBarsColor(systemBarsColor)
|
||||
}
|
||||
|
||||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
NavigationBar3ButtonsScrim()
|
||||
AppCurrencySelectorScreen(
|
||||
modifier = modifier,
|
||||
state = uiState,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
|
@ -40,7 +41,9 @@ internal fun AppCurrencySelectorScreen(state: AppCurrencySelectorState, modifier
|
|||
val listState = rememberLazyListState()
|
||||
|
||||
Scaffold(
|
||||
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
modifier = modifier
|
||||
.imePadding()
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
containerColor = TangemTheme.colors.background.secondary,
|
||||
topBar = {
|
||||
TopBar(
|
||||
|
|
@ -49,6 +52,7 @@ internal fun AppCurrencySelectorScreen(state: AppCurrencySelectorState, modifier
|
|||
state = state,
|
||||
)
|
||||
},
|
||||
contentWindowInsets = ScaffoldDefaults.contentWindowInsets.exclude(WindowInsets.navigationBars),
|
||||
content = { paddingValues ->
|
||||
val contentModifier = Modifier
|
||||
.padding(paddingValues)
|
||||
|
|
@ -62,7 +66,7 @@ internal fun AppCurrencySelectorScreen(state: AppCurrencySelectorState, modifier
|
|||
modifier = contentModifier,
|
||||
listState = listState,
|
||||
currencies = state.items,
|
||||
selectedId = state.selectedId.orEmpty(),
|
||||
selectedId = state.selectedId,
|
||||
onCurrencyClick = state.onCurrencyClick,
|
||||
)
|
||||
}
|
||||
|
|
@ -175,6 +179,7 @@ private fun SearchBar(onInputChange: (String) -> Unit, modifier: Modifier = Modi
|
|||
|
||||
@Composable
|
||||
private fun LoadingList(modifier: Modifier = Modifier) {
|
||||
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
|
||||
Column(modifier = modifier) {
|
||||
repeat(times = 10) {
|
||||
Row(
|
||||
|
|
@ -199,6 +204,7 @@ private fun LoadingList(modifier: Modifier = Modifier) {
|
|||
)
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(bottomBarHeight))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -210,9 +216,11 @@ private fun CurrenciesList(
|
|||
onCurrencyClick: (Currency) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
|
||||
LazyColumn(
|
||||
modifier = modifier,
|
||||
state = listState,
|
||||
contentPadding = PaddingValues(bottom = bottomBarHeight),
|
||||
) {
|
||||
items(
|
||||
items = currencies,
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@ package com.tangem.tap.features.details.ui.appcurrency
|
|||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.navigation.ReduxNavController
|
||||
|
||||
import com.tangem.domain.appcurrency.GetAvailableCurrenciesUseCase
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.SelectAppCurrencyUseCase
|
||||
|
|
@ -20,7 +21,7 @@ internal class AppCurrencySelectorViewModel @Inject constructor(
|
|||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val getAvailableCurrenciesUseCase: GetAvailableCurrenciesUseCase,
|
||||
private val selectAppCurrencyUseCase: SelectAppCurrencyUseCase,
|
||||
private val reduxNavController: ReduxNavController,
|
||||
private val router: AppRouter,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
) : ViewModel(), AppCurrencySelectorIntents {
|
||||
|
|
@ -34,7 +35,7 @@ internal class AppCurrencySelectorViewModel @Inject constructor(
|
|||
val uiState: StateFlow<AppCurrencySelectorState> = stateController.stateFlow
|
||||
|
||||
override fun onBackClick() {
|
||||
reduxNavController.popBackStack()
|
||||
router.pop()
|
||||
}
|
||||
|
||||
override fun onSearchClick() {
|
||||
|
|
@ -52,7 +53,7 @@ internal class AppCurrencySelectorViewModel @Inject constructor(
|
|||
analyticsEventHandler.send(
|
||||
event = Settings.AppSettings.MainCurrencyChanged(currencyType = currency.name),
|
||||
)
|
||||
reduxNavController.popBackStack()
|
||||
router.pop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.store
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
|
|
@ -35,8 +35,7 @@ internal class AppSettingsFragment : ComposeFragment() {
|
|||
modifier = modifier,
|
||||
state = state,
|
||||
onBackClick = {
|
||||
store.dispatch(DetailsAction.ResetCardSettingsData)
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
store.dispatchNavigationAction(AppRouter::pop)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,17 @@
|
|||
package com.tangem.tap.features.details.ui.appsettings
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.systemBars
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
|
|
@ -32,6 +36,7 @@ internal fun AppSettingsScreen(state: AppSettingsScreenState, onBackClick: () ->
|
|||
},
|
||||
titleRes = R.string.app_settings_title,
|
||||
onBackClick = onBackClick,
|
||||
addBottomInsets = false,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -44,7 +49,11 @@ private fun AppSettings(state: AppSettingsScreenState.Content) {
|
|||
null -> Unit
|
||||
}
|
||||
|
||||
LazyColumn {
|
||||
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
|
||||
|
||||
LazyColumn(
|
||||
contentPadding = PaddingValues(bottom = bottomBarHeight),
|
||||
) {
|
||||
items(
|
||||
items = state.items,
|
||||
key = Item::id,
|
||||
|
|
|
|||
|
|
@ -1,16 +1,20 @@
|
|||
package com.tangem.tap.features.details.ui.appsettings
|
||||
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.*
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
|
||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
import com.tangem.domain.apptheme.repository.AppThemeModeRepository
|
||||
import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
|
||||
import com.tangem.domain.settings.CanUseBiometryUseCase
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.features.details.DetailsFeatureToggles
|
||||
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.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||
import com.tangem.tap.features.details.redux.AppSetting
|
||||
|
|
@ -26,14 +30,22 @@ import dagger.hilt.android.lifecycle.HiltViewModel
|
|||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.StoreSubscriber
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@HiltViewModel
|
||||
internal class AppSettingsViewModel @Inject constructor(
|
||||
private val appCurrencyRepository: AppCurrencyRepository,
|
||||
private val walletsRepository: WalletsRepository,
|
||||
private val canUseBiometryUseCase: CanUseBiometryUseCase,
|
||||
private val balanceHidingRepository: BalanceHidingRepository,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val appThemeModeRepository: AppThemeModeRepository,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val appSettingsItemsAnalyticsSender: AppSettingsItemsAnalyticsSender,
|
||||
private val detailsFeatureToggles: DetailsFeatureToggles,
|
||||
) : ViewModel(),
|
||||
StoreSubscriber<DetailsState>,
|
||||
DefaultLifecycleObserver {
|
||||
|
|
@ -50,6 +62,9 @@ internal class AppSettingsViewModel @Inject constructor(
|
|||
|
||||
init {
|
||||
bootstrapAppCurrencyUpdates()
|
||||
if (detailsFeatureToggles.isRedesignEnabled) {
|
||||
bootstrapBiometricsUpdates()
|
||||
}
|
||||
|
||||
subscribeToStoreChanges()
|
||||
sendItemsAnalytics()
|
||||
|
|
@ -128,7 +143,7 @@ internal class AppSettingsViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun showAppCurrencySelector() {
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.AppCurrencySelector))
|
||||
store.dispatchNavigationAction { push(AppRoute.AppCurrencySelector) }
|
||||
}
|
||||
|
||||
private fun showThemeModeSelector(selectedMode: AppThemeMode) {
|
||||
|
|
@ -214,6 +229,19 @@ internal class AppSettingsViewModel @Inject constructor(
|
|||
.saveIn(appCurrencyUpdatesJobHolder)
|
||||
}
|
||||
|
||||
private fun bootstrapBiometricsUpdates() = viewModelScope.launch {
|
||||
val state = AppSettingsState(
|
||||
saveWallets = walletsRepository.shouldSaveUserWalletsSync(),
|
||||
saveAccessCodes = settingsRepository.shouldSaveAccessCodes(),
|
||||
isBiometricsAvailable = canUseBiometryUseCase(),
|
||||
isHidingEnabled = balanceHidingRepository.getBalanceHidingSettings().isHidingEnabledInSettings,
|
||||
selectedAppCurrency = appCurrencyRepository.getSelectedAppCurrency().firstOrNull() ?: AppCurrency.Default,
|
||||
selectedThemeMode = appThemeModeRepository.getAppThemeMode().firstOrNull() ?: AppThemeMode.DEFAULT,
|
||||
)
|
||||
|
||||
store.dispatchWithMain(DetailsAction.AppSettings.Prepare(state))
|
||||
}
|
||||
|
||||
private fun subscribeToStoreChanges() {
|
||||
store.subscribe(subscriber = this) { state ->
|
||||
state.skipRepeats { oldState, newState ->
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
package com.tangem.tap.features.details.ui.cardsettings
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.store
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -22,15 +20,8 @@ internal class CardSettingsFragment : ComposeFragment() {
|
|||
|
||||
@Composable
|
||||
override fun ScreenContent(modifier: Modifier) {
|
||||
LocalLifecycleOwner.current.lifecycle.addObserver(viewModel)
|
||||
val state by viewModel.screenState.collectAsStateWithLifecycle()
|
||||
|
||||
CardSettingsScreen(
|
||||
modifier = modifier,
|
||||
state = viewModel.screenState.value,
|
||||
onBackClick = {
|
||||
store.dispatch(DetailsAction.ResetCardSettingsData)
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
},
|
||||
)
|
||||
CardSettingsScreen(modifier = modifier, state = state)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -4,18 +4,15 @@ import androidx.annotation.StringRes
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.tangem.tap.features.details.redux.AccessCodeRecoveryState
|
||||
import com.tangem.tap.features.details.redux.SecurityOption
|
||||
import com.tangem.tap.features.details.ui.securitymode.toTitleRes
|
||||
import com.tangem.tap.features.details.ui.utils.toResetCardDescriptionText
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.tap.features.details.redux.CardInfo as ReduxCardInfo
|
||||
|
||||
internal data class CardSettingsScreenState(
|
||||
val cardDetails: List<CardInfo>? = null,
|
||||
val accessCodeRecoveryState: AccessCodeRecoveryState? = null,
|
||||
val onScanCardClick: () -> Unit,
|
||||
val onElementClick: (CardInfo) -> Unit,
|
||||
val onBackClick: () -> Unit,
|
||||
)
|
||||
|
||||
internal sealed class CardInfo(
|
||||
|
|
@ -44,7 +41,7 @@ internal sealed class CardInfo(
|
|||
clickable = clickable,
|
||||
)
|
||||
|
||||
object ChangeAccessCode : CardInfo(
|
||||
data object ChangeAccessCode : CardInfo(
|
||||
titleRes = TextReference.Res(R.string.card_settings_change_access_code),
|
||||
subtitle = TextReference.Res(R.string.card_settings_change_access_code_footer),
|
||||
clickable = true,
|
||||
|
|
@ -60,9 +57,9 @@ internal sealed class CardInfo(
|
|||
clickable = true,
|
||||
)
|
||||
|
||||
class ResetToFactorySettings(cardInfo: ReduxCardInfo) : CardInfo(
|
||||
class ResetToFactorySettings(description: TextReference) : CardInfo(
|
||||
titleRes = TextReference.Res(R.string.card_settings_reset_card_to_factory),
|
||||
subtitle = cardInfo.toResetCardDescriptionText(),
|
||||
subtitle = description,
|
||||
clickable = true,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,105 +1,132 @@
|
|||
package com.tangem.tap.features.details.ui.cardsettings
|
||||
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import android.os.Bundle
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import arrow.core.Either
|
||||
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.common.TapWorkarounds.isTangemTwins
|
||||
import com.tangem.domain.common.getTwinCardIdForUser
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.domain.card.ScanCardProcessor
|
||||
import com.tangem.domain.common.CardTypesResolver
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Settings
|
||||
import com.tangem.tap.features.details.redux.CardSettingsState
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.features.details.redux.DetailsState
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
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 org.rekotlin.StoreSubscriber
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
internal class CardSettingsViewModel @Inject constructor(
|
||||
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
||||
) :
|
||||
ViewModel(), DefaultLifecycleObserver, StoreSubscriber<DetailsState> {
|
||||
private val scanCardProcessor: ScanCardProcessor,
|
||||
private val tangemSdkManager: TangemSdkManager,
|
||||
private val cardSettingsInteractor: CardSettingsInteractor,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel() {
|
||||
|
||||
var screenState: MutableState<CardSettingsScreenState> =
|
||||
mutableStateOf(updateState(store.state.detailsState.cardSettingsState))
|
||||
private val userWalletId = savedStateHandle.get<Bundle>(AppRoute.CardSettings.USER_WALLET_ID_KEY)
|
||||
?.unbundle(UserWalletId.serializer())
|
||||
?: error("User wallet ID is required for CardSettingsViewModel")
|
||||
|
||||
override fun onStart(owner: LifecycleOwner) {
|
||||
when (val selectedWalletEither = getSelectedWalletSyncUseCase()) {
|
||||
is Either.Left -> {
|
||||
Timber.e(selectedWalletEither.value.toString())
|
||||
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 ->
|
||||
val scannedUserWalletId = UserWalletIdBuilder.scanResponse(scanResponse).build()
|
||||
if (userWalletId == scannedUserWalletId || scannedUserWalletId == null) {
|
||||
cardSettingsInteractor.initialize(scanResponse)
|
||||
} else {
|
||||
store.dispatchDialogShow(
|
||||
AppDialog.SimpleOkDialogRes(
|
||||
headerId = R.string.common_warning,
|
||||
messageId = R.string.error_wrong_wallet_tapped,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateCardDetails(scanResponse: ScanResponse) {
|
||||
val card = scanResponse.card
|
||||
val cardTypesResolver = scanResponse.cardTypesResolver
|
||||
val cardId = cardTypesResolver.getCardId()
|
||||
|
||||
val currentSecurityOption = getCurrentSecurityOption(card)
|
||||
val allowedSecurityOptions = getAllowedSecurityOptions(
|
||||
card = card,
|
||||
cardTypesResolver = cardTypesResolver,
|
||||
currentSecurityOption = currentSecurityOption,
|
||||
)
|
||||
val isResetCardAllowed = isResetToFactoryAllowedByCard(card, cardTypesResolver)
|
||||
|
||||
val cardDetails = buildList {
|
||||
CardInfo.CardId(cardId).let(::add)
|
||||
CardInfo.Issuer(card.issuer.name).let(::add)
|
||||
|
||||
if (!cardTypesResolver.isTangemTwins()) {
|
||||
CardInfo.SignedHashes(card.signedHashesCount().toString()).let(::add)
|
||||
}
|
||||
|
||||
CardInfo.SecurityMode(
|
||||
currentSecurityOption,
|
||||
clickable = allowedSecurityOptions.size > 1,
|
||||
).let(::add)
|
||||
|
||||
if (card.backupStatus?.isActive == true && card.isAccessCodeSet) {
|
||||
CardInfo.ChangeAccessCode.let(::add)
|
||||
}
|
||||
|
||||
if (isAccessCodeRecoveryAllowed(cardTypesResolver)) {
|
||||
CardInfo.AccessCodeRecovery(isAccessCodeRecoveryEnabled(cardTypesResolver, card)).let(::add)
|
||||
}
|
||||
|
||||
if (isResetCardAllowed) {
|
||||
CardInfo.ResetToFactorySettings(
|
||||
description = getResetToFactoryDescription(
|
||||
isActiveBackupStatus = card.backupStatus?.isActive == true,
|
||||
typesResolver = cardTypesResolver,
|
||||
),
|
||||
).let(::add)
|
||||
}
|
||||
is Either.Right -> Unit
|
||||
}
|
||||
|
||||
store.subscribe(this) { state ->
|
||||
state.skipRepeats { oldState, newState ->
|
||||
oldState.detailsState == newState.detailsState
|
||||
}.select { it.detailsState }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStop(owner: LifecycleOwner) {
|
||||
store.unsubscribe(this)
|
||||
}
|
||||
|
||||
override fun newState(state: DetailsState) {
|
||||
screenState.value = updateState(state.cardSettingsState)
|
||||
}
|
||||
|
||||
private fun updateState(state: CardSettingsState?): CardSettingsScreenState {
|
||||
return if (state?.manageSecurityState == null) {
|
||||
CardSettingsScreenState(
|
||||
cardDetails = null,
|
||||
accessCodeRecoveryState = null,
|
||||
onElementClick = {},
|
||||
onScanCardClick = {
|
||||
store.dispatch(DetailsAction.ScanCard)
|
||||
},
|
||||
)
|
||||
} else {
|
||||
val cardId = if (state.card.isTangemTwins) {
|
||||
state.card.getTwinCardIdForUser()
|
||||
} else {
|
||||
state.cardInfo.cardId
|
||||
}
|
||||
val cardDetails: MutableList<CardInfo> = mutableListOf(
|
||||
CardInfo.CardId(cardId),
|
||||
CardInfo.Issuer(state.cardInfo.issuer),
|
||||
)
|
||||
|
||||
if (!state.card.isTangemTwins) {
|
||||
cardDetails.add(CardInfo.SignedHashes(state.cardInfo.signedHashes.toString()))
|
||||
}
|
||||
cardDetails.add(
|
||||
CardInfo.SecurityMode(
|
||||
securityOption = state.manageSecurityState.currentOption,
|
||||
clickable = state.manageSecurityState.allowedOptions.size > 1,
|
||||
),
|
||||
)
|
||||
if (state.card.backupStatus?.isActive == true && state.card.isAccessCodeSet) {
|
||||
cardDetails.add(CardInfo.ChangeAccessCode)
|
||||
}
|
||||
if (state.accessCodeRecovery != null) {
|
||||
cardDetails.add(CardInfo.AccessCodeRecovery(state.accessCodeRecovery.enabledOnCard))
|
||||
}
|
||||
if (state.resetCardAllowed) {
|
||||
cardDetails.add(CardInfo.ResetToFactorySettings(state.cardInfo))
|
||||
}
|
||||
CardSettingsScreenState(
|
||||
cardDetails = cardDetails,
|
||||
accessCodeRecoveryState = state.accessCodeRecovery,
|
||||
onScanCardClick = { },
|
||||
onElementClick = {
|
||||
handleClickingItem(it)
|
||||
},
|
||||
)
|
||||
screenState.update { state ->
|
||||
state.copy(cardDetails = cardDetails)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -107,20 +134,75 @@ internal class CardSettingsViewModel @Inject constructor(
|
|||
when (item) {
|
||||
is CardInfo.ChangeAccessCode -> {
|
||||
Analytics.send(Settings.CardSettings.ButtonChangeUserCode(AnalyticsParam.UserCode.AccessCode))
|
||||
store.dispatch(DetailsAction.ManageSecurity.ChangeAccessCode)
|
||||
changeAccessCode()
|
||||
}
|
||||
is CardInfo.ResetToFactorySettings -> {
|
||||
Analytics.send(Settings.CardSettings.ButtonFactoryReset())
|
||||
store.dispatch(DetailsAction.ResetToFactory.Start)
|
||||
resetWalletToFactorySettings()
|
||||
}
|
||||
is CardInfo.SecurityMode -> {
|
||||
Analytics.send(Settings.CardSettings.ButtonChangeSecurityMode())
|
||||
store.dispatch(DetailsAction.ManageSecurity.OpenSecurity)
|
||||
store.dispatchNavigationAction {
|
||||
push(route = AppRoute.DetailsSecurity(userWalletId))
|
||||
}
|
||||
}
|
||||
is CardInfo.AccessCodeRecovery -> {
|
||||
store.dispatch(DetailsAction.AccessCodeRecovery.Open)
|
||||
store.dispatchNavigationAction { push(AppRoute.AccessCodeRecovery) }
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun resetWalletToFactorySettings() {
|
||||
val scanResponse = requireNotNull(cardSettingsInteractor.scannedScanResponse.value) {
|
||||
"Impossible to reset card if ScanResponse is null"
|
||||
}
|
||||
|
||||
if (scanResponse.cardTypesResolver.isTangemTwins()) {
|
||||
store.dispatch(TwinCardsAction.SetMode(CreateTwinWalletMode.RecreateWallet(scanResponse)))
|
||||
|
||||
store.dispatchNavigationAction { push(AppRoute.OnboardingTwins) }
|
||||
} else {
|
||||
val card = scanResponse.card
|
||||
|
||||
store.dispatchNavigationAction {
|
||||
push(
|
||||
route = AppRoute.ResetToFactory(
|
||||
userWalletId = userWalletId,
|
||||
cardId = card.cardId,
|
||||
isActiveBackupStatus = card.backupStatus?.isActive == true,
|
||||
backupCardsCount = when (val status = card.backupStatus) {
|
||||
is CardDTO.BackupStatus.Active -> status.cardCount
|
||||
is CardDTO.BackupStatus.CardLinked,
|
||||
CardDTO.BackupStatus.NoBackup,
|
||||
null,
|
||||
-> 0
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun changeAccessCode() = viewModelScope.launch {
|
||||
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())
|
||||
is CompletionResult.Failure -> {
|
||||
Timber.e("Failed to change access code: ${result.error}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isResetToFactoryAllowedByCard(card: CardDTO, cardTypesResolver: CardTypesResolver): Boolean {
|
||||
val hasPermanentWallet = card.wallets.any { it.settings.isPermanent }
|
||||
val isNotAllowed = hasPermanentWallet || cardTypesResolver.isStart2Coin()
|
||||
return !isNotAllowed
|
||||
}
|
||||
|
||||
private fun onBackClick() {
|
||||
cardSettingsInteractor.clear()
|
||||
store.dispatchNavigationAction(AppRouter::pop)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,54 +1,33 @@
|
|||
package com.tangem.tap.features.details.ui.cardsettings.coderecovery
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
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.features.details.redux.DetailsState
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.store
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.rekotlin.StoreSubscriber
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class AccessCodeRecoveryFragment : ComposeFragment(), StoreSubscriber<DetailsState> {
|
||||
class AccessCodeRecoveryFragment : ComposeFragment() {
|
||||
|
||||
private val viewModel = AccessCodeRecoveryViewModel(store)
|
||||
|
||||
private var screenState: MutableState<AccessCodeRecoveryScreenState> =
|
||||
mutableStateOf(viewModel.updateState(store.state.detailsState.cardSettingsState?.accessCodeRecovery))
|
||||
private val viewModel: AccessCodeRecoveryViewModel by viewModels()
|
||||
|
||||
@Inject
|
||||
override lateinit var uiDependencies: UiDependencies
|
||||
|
||||
@Composable
|
||||
override fun ScreenContent(modifier: Modifier) {
|
||||
val state by viewModel.screenState.collectAsStateWithLifecycle()
|
||||
|
||||
AccessCodeRecoveryScreen(
|
||||
state = screenState.value,
|
||||
onBackClick = { store.dispatch(NavigationAction.PopBackTo()) },
|
||||
state = state,
|
||||
onBackClick = { store.dispatchNavigationAction(AppRouter::pop) },
|
||||
)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
store.subscribe(this) { state ->
|
||||
state.skipRepeats { oldState, newState ->
|
||||
oldState.detailsState == newState.detailsState
|
||||
}.select { it.detailsState }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
store.unsubscribe(this)
|
||||
}
|
||||
|
||||
override fun newState(state: DetailsState) {
|
||||
if (activity == null || view == null) return
|
||||
screenState.value =
|
||||
viewModel.updateState(store.state.detailsState.cardSettingsState?.accessCodeRecovery)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,6 @@
|
|||
package com.tangem.tap.features.details.ui.cardsettings.coderecovery
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -57,7 +53,7 @@ fun AccessCodeRecoveryOptions(state: AccessCodeRecoveryScreenState) {
|
|||
DetailsMainButton(
|
||||
title = stringResource(id = R.string.common_save_changes),
|
||||
enabled = state.isSaveChangesEnabled,
|
||||
onClick = { state.onSaveChangesClick(state.enabledSelection) },
|
||||
onClick = { state.onSaveChangesClick() },
|
||||
modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing20),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,6 @@ data class AccessCodeRecoveryScreenState(
|
|||
val enabledOnCard: Boolean,
|
||||
val enabledSelection: Boolean,
|
||||
val isSaveChangesEnabled: Boolean,
|
||||
val onSaveChangesClick: (Boolean) -> Unit,
|
||||
val onSaveChangesClick: () -> Unit,
|
||||
val onOptionClick: (Boolean) -> Unit,
|
||||
)
|
||||
|
|
@ -1,29 +1,83 @@
|
|||
package com.tangem.tap.features.details.ui.cardsettings.coderecovery
|
||||
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.details.redux.AccessCodeRecoveryState
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import org.rekotlin.Store
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
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
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
class AccessCodeRecoveryViewModel(val store: Store<AppState>) {
|
||||
@HiltViewModel
|
||||
internal class AccessCodeRecoveryViewModel @Inject constructor(
|
||||
private val tangemSdkManager: TangemSdkManager,
|
||||
private val cardSettingsInteractor: CardSettingsInteractor,
|
||||
) : ViewModel() {
|
||||
|
||||
fun updateState(state: AccessCodeRecoveryState?): AccessCodeRecoveryScreenState {
|
||||
// We shouldn't get to this screen here when this state is null
|
||||
return if (state == null) {
|
||||
AccessCodeRecoveryScreenState(
|
||||
enabledOnCard = false,
|
||||
enabledSelection = false,
|
||||
isSaveChangesEnabled = false,
|
||||
onSaveChangesClick = {},
|
||||
onOptionClick = {},
|
||||
)
|
||||
} else {
|
||||
AccessCodeRecoveryScreenState(
|
||||
enabledOnCard = state.enabledOnCard,
|
||||
enabledSelection = state.enabledSelection,
|
||||
isSaveChangesEnabled = state.enabledOnCard != state.enabledSelection,
|
||||
onSaveChangesClick = { store.dispatch(DetailsAction.AccessCodeRecovery.SaveChanges(it)) },
|
||||
onOptionClick = { store.dispatch(DetailsAction.AccessCodeRecovery.SelectOption(it)) },
|
||||
private val scannedScanResponse = cardSettingsInteractor.scannedScanResponse.value
|
||||
?: error("Scan response is null")
|
||||
|
||||
val screenState = MutableStateFlow(
|
||||
value = getInitialState(),
|
||||
)
|
||||
|
||||
private fun getInitialState(): AccessCodeRecoveryScreenState {
|
||||
val isEnabled = isAccessCodeRecoveryEnabled(
|
||||
typeResolver = scannedScanResponse.cardTypesResolver,
|
||||
card = scannedScanResponse.card,
|
||||
)
|
||||
|
||||
return AccessCodeRecoveryScreenState(
|
||||
enabledOnCard = isEnabled,
|
||||
enabledSelection = isEnabled,
|
||||
isSaveChangesEnabled = false,
|
||||
onSaveChangesClick = ::saveChanges,
|
||||
onOptionClick = ::selectOption,
|
||||
)
|
||||
}
|
||||
|
||||
private fun saveChanges() = viewModelScope.launch {
|
||||
val isEnabled = screenState.value.enabledSelection
|
||||
|
||||
tangemSdkManager
|
||||
.setAccessCodeRecoveryEnabled(scannedScanResponse.card.cardId, isEnabled)
|
||||
.doOnSuccess {
|
||||
Analytics.send(
|
||||
Settings.CardSettings.AccessCodeRecoveryChanged(
|
||||
AnalyticsParam.AccessCodeRecoveryStatus.from(isEnabled),
|
||||
),
|
||||
)
|
||||
|
||||
cardSettingsInteractor.update { scanResponse ->
|
||||
scanResponse.copy(
|
||||
card = scanResponse.card.copy(
|
||||
userSettings = scanResponse.card.userSettings?.copy(
|
||||
isUserCodeRecoveryAllowed = isEnabled,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
store.dispatchNavigationAction(AppRouter::pop)
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectOption(isEnabled: Boolean) {
|
||||
screenState.update {
|
||||
it.copy(
|
||||
enabledSelection = isEnabled,
|
||||
isSaveChangesEnabled = isEnabled != it.enabledOnCard,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -13,8 +13,8 @@ import androidx.compose.ui.res.painterResource
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.PrimaryButtonIconEnd
|
||||
import com.tangem.core.ui.components.SystemBarsEffect
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.utils.WindowInsetsZero
|
||||
import com.tangem.wallet.R
|
||||
|
||||
@Composable
|
||||
|
|
@ -24,31 +24,42 @@ internal fun SettingsScreensScaffold(
|
|||
modifier: Modifier = Modifier,
|
||||
@StringRes titleRes: Int? = null,
|
||||
snackbarHostState: SnackbarHostState = remember { SnackbarHostState() },
|
||||
addBottomInsets: Boolean = true,
|
||||
fab: @Composable () -> Unit = {},
|
||||
) {
|
||||
val state = rememberScaffoldState(snackbarHostState = snackbarHostState)
|
||||
val backgroundColor = TangemTheme.colors.background.secondary
|
||||
|
||||
BackHandler(onBack = onBackClick)
|
||||
SystemBarsEffect {
|
||||
setSystemBarsColor(backgroundColor)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
scaffoldState = state,
|
||||
topBar = {
|
||||
EmptyTopBarWithNavigation(
|
||||
modifier = Modifier.statusBarsPadding(),
|
||||
onBackClick = onBackClick,
|
||||
backgroundColor = backgroundColor,
|
||||
)
|
||||
},
|
||||
modifier = modifier.systemBarsPadding(),
|
||||
modifier = modifier,
|
||||
contentWindowInsets = WindowInsetsZero,
|
||||
backgroundColor = backgroundColor,
|
||||
floatingActionButton = fab,
|
||||
content = { paddings ->
|
||||
floatingActionButton = {
|
||||
Box(modifier = Modifier.navigationBarsPadding()) {
|
||||
fab()
|
||||
}
|
||||
},
|
||||
content = { paddingValues ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(paddings)
|
||||
.run {
|
||||
if (addBottomInsets) {
|
||||
navigationBarsPadding()
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
.padding(paddingValues)
|
||||
.fillMaxSize(),
|
||||
) {
|
||||
if (titleRes != null) {
|
||||
|
|
@ -81,9 +92,11 @@ internal fun ScreenTitle(titleRes: Int, modifier: Modifier = Modifier) {
|
|||
@Composable
|
||||
internal fun EmptyTopBarWithNavigation(
|
||||
onBackClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
backgroundColor: Color = TangemTheme.colors.background.primary,
|
||||
) {
|
||||
TopAppBar(
|
||||
modifier = modifier,
|
||||
title = { },
|
||||
navigationIcon =
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.tap.features.details.ui.common.utils
|
||||
|
||||
import com.tangem.domain.common.CardTypesResolver
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
|
||||
internal fun isAccessCodeRecoveryAllowed(typeResolver: CardTypesResolver): Boolean = typeResolver.isWallet2()
|
||||
|
||||
internal fun isAccessCodeRecoveryEnabled(typeResolver: CardTypesResolver, card: CardDTO): Boolean =
|
||||
if (typeResolver.isWallet2()) {
|
||||
card.userSettings?.isUserCodeRecoveryAllowed ?: false
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.tap.features.details.ui.common.utils
|
||||
|
||||
import com.tangem.domain.common.CardTypesResolver
|
||||
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
||||
import com.tangem.wallet.R
|
||||
|
||||
internal fun getResetToFactoryDescription(
|
||||
isActiveBackupStatus: Boolean,
|
||||
typesResolver: CardTypesResolver,
|
||||
): TextReference {
|
||||
return if (!isActiveBackupStatus || typesResolver.isTangemTwins()) {
|
||||
TextReference.Res(R.string.reset_card_without_backup_to_factory_message)
|
||||
} else {
|
||||
TextReference.Res(R.string.reset_card_with_backup_to_factory_message)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package com.tangem.tap.features.details.ui.common.utils
|
||||
|
||||
import com.tangem.domain.common.CardTypesResolver
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.tap.features.details.redux.SecurityOption
|
||||
import java.util.EnumSet
|
||||
|
||||
internal fun getCurrentSecurityOption(card: CardDTO): SecurityOption = when {
|
||||
card.isAccessCodeSet -> SecurityOption.AccessCode
|
||||
card.isPasscodeSet == true -> SecurityOption.PassCode
|
||||
else -> SecurityOption.LongTap
|
||||
}
|
||||
|
||||
internal fun getAllowedSecurityOptions(
|
||||
card: CardDTO,
|
||||
cardTypesResolver: CardTypesResolver,
|
||||
currentSecurityOption: SecurityOption,
|
||||
): EnumSet<SecurityOption> = when {
|
||||
cardTypesResolver.isStart2Coin() || cardTypesResolver.isTangemNote() -> EnumSet.of(SecurityOption.LongTap)
|
||||
card.settings.isBackupAllowed -> EnumSet.of(currentSecurityOption)
|
||||
else -> prepareAllowedSecurityOptions(
|
||||
cardTypesResolver = cardTypesResolver,
|
||||
currentSecurityOption = currentSecurityOption,
|
||||
)
|
||||
}
|
||||
|
||||
private fun prepareAllowedSecurityOptions(
|
||||
cardTypesResolver: CardTypesResolver,
|
||||
currentSecurityOption: SecurityOption?,
|
||||
): EnumSet<SecurityOption> {
|
||||
val allowedSecurityOptions = EnumSet.of(SecurityOption.LongTap)
|
||||
|
||||
if (cardTypesResolver.isTangemTwins()) {
|
||||
allowedSecurityOptions.add(SecurityOption.PassCode)
|
||||
}
|
||||
if (currentSecurityOption == SecurityOption.AccessCode) {
|
||||
allowedSecurityOptions.add(SecurityOption.AccessCode)
|
||||
}
|
||||
if (currentSecurityOption == SecurityOption.PassCode) {
|
||||
allowedSecurityOptions.add(SecurityOption.PassCode)
|
||||
}
|
||||
|
||||
return allowedSecurityOptions
|
||||
}
|
||||
|
|
@ -3,13 +3,14 @@ package com.tangem.tap.features.details.ui.details
|
|||
import android.os.Bundle
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.tap.common.analytics.events.Settings
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.features.details.redux.DetailsState
|
||||
import com.tangem.tap.store
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
|
@ -45,7 +46,7 @@ internal class DetailsFragment : ComposeFragment(), StoreSubscriber<DetailsState
|
|||
DetailsScreen(
|
||||
modifier = modifier,
|
||||
state = detailsViewModel.detailsScreenState.value,
|
||||
onBackClick = { store.dispatch(NavigationAction.PopBackTo()) },
|
||||
onBackClick = { store.dispatchNavigationAction(AppRouter::pop) },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -60,14 +60,14 @@ internal sealed class SettingsItem(
|
|||
data class LinkMoreCards(
|
||||
override val onClick: () -> Unit,
|
||||
) : SettingsItem(
|
||||
iconResId = R.drawable.ic_more_cards,
|
||||
iconResId = R.drawable.ic_more_cards_24,
|
||||
title = resourceReference(R.string.details_row_title_create_backup),
|
||||
)
|
||||
|
||||
data class CardSettings(
|
||||
override val onClick: () -> Unit,
|
||||
) : SettingsItem(
|
||||
iconResId = R.drawable.ic_card_settings,
|
||||
iconResId = R.drawable.ic_card_settings_24,
|
||||
title = resourceReference(R.string.card_settings_title),
|
||||
)
|
||||
|
||||
|
|
@ -95,7 +95,7 @@ internal sealed class SettingsItem(
|
|||
data class ReferralProgram(
|
||||
override val onClick: () -> Unit,
|
||||
) : SettingsItem(
|
||||
iconResId = R.drawable.ic_add_friends,
|
||||
iconResId = R.drawable.ic_add_friends_24,
|
||||
title = resourceReference(R.string.details_referral_title),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@ package com.tangem.tap.features.details.ui.details
|
|||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
|
||||
import com.tangem.core.ui.event.StateEvent
|
||||
import com.tangem.core.ui.event.consumedEvent
|
||||
import com.tangem.core.ui.event.triggeredEvent
|
||||
|
|
@ -16,15 +16,14 @@ import com.tangem.domain.common.util.cardTypesResolver
|
|||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.tap.common.analytics.events.Settings
|
||||
import com.tangem.tap.common.extensions.addContext
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.feedback.FeedbackEmail
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.features.details.redux.DetailsState
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerSource
|
||||
import com.tangem.tap.features.home.LocaleRegionProvider
|
||||
import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE
|
||||
import com.tangem.tap.scope
|
||||
|
|
@ -40,7 +39,6 @@ import kotlinx.coroutines.flow.onEach
|
|||
import org.rekotlin.Store
|
||||
import timber.log.Timber
|
||||
|
||||
// TODO: change to Android ViewModel [REDACTED_JIRA]
|
||||
internal class DetailsViewModel(
|
||||
private val store: Store<AppState>,
|
||||
private val walletsRepository: WalletsRepository,
|
||||
|
|
@ -91,10 +89,6 @@ internal class DetailsViewModel(
|
|||
SettingsItem.AppSettings(::navigateToAppSettings)
|
||||
.let(::add)
|
||||
|
||||
// removed chat in task [REDACTED_TASK_KEY]
|
||||
// SettingsItem.Chat(::navigateToChat)
|
||||
// .let(::add)
|
||||
|
||||
SettingsItem.SendFeedback(::sendFeedback)
|
||||
.let(::add)
|
||||
|
||||
|
|
@ -128,30 +122,43 @@ internal class DetailsViewModel(
|
|||
}
|
||||
|
||||
private fun navigateToTesterMenu() {
|
||||
store.state.daggerGraphState.testerRouter?.startTesterScreen()
|
||||
store.dispatchNavigationAction {
|
||||
push(AppRoute.TesterMenu)
|
||||
}
|
||||
}
|
||||
|
||||
private fun navigateToToS() {
|
||||
store.dispatchOnMain(DisclaimerAction.Show(AppScreen.Details))
|
||||
store.dispatchOnMain(DisclaimerAction.Show(DisclaimerSource.Details))
|
||||
}
|
||||
|
||||
private fun navigateToReferralProgram() {
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.ReferralProgram))
|
||||
val userWallet = userWalletsListManager.selectedUserWalletSync
|
||||
?: error("Selected wallet must be not null")
|
||||
|
||||
store.dispatchNavigationAction { push(AppRoute.ReferralProgram(userWallet.walletId)) }
|
||||
}
|
||||
|
||||
private fun sendFeedback() {
|
||||
Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Settings))
|
||||
store.dispatchOnMain(GlobalAction.SendEmail(FeedbackEmail()))
|
||||
store.dispatchOnMain(
|
||||
GlobalAction.SendEmail(
|
||||
feedbackData = FeedbackEmail(),
|
||||
scanResponse = userWalletsListManager.selectedUserWalletSync?.scanResponse
|
||||
?: error("ScanResponse must be not null"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun navigateToAppSettings() {
|
||||
Analytics.send(Settings.ButtonAppSettings())
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.AppSettings))
|
||||
store.dispatchNavigationAction { push(AppRoute.AppSettings) }
|
||||
}
|
||||
|
||||
private fun navigateToCardSettings() {
|
||||
val userWalletId = userWalletsListManager.selectedUserWalletSync?.walletId
|
||||
?: error("UserWalletId must be not null")
|
||||
Analytics.send(Settings.ButtonCardSettings())
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.CardSettings))
|
||||
store.dispatchNavigationAction { push(AppRoute.CardSettings(userWalletId)) }
|
||||
}
|
||||
|
||||
private fun linkMoreCards() {
|
||||
|
|
@ -164,7 +171,7 @@ internal class DetailsViewModel(
|
|||
val scanResponse = selectedUserWallet.scanResponse
|
||||
Analytics.addContext(scanResponse)
|
||||
store.dispatch(GlobalAction.Onboarding.Start(scanResponse, canSkipBackup = false))
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet))
|
||||
store.dispatchNavigationAction { push(AppRoute.OnboardingWallet()) }
|
||||
}
|
||||
|
||||
private fun scanAndSaveUserWallet() {
|
||||
|
|
@ -174,12 +181,12 @@ internal class DetailsViewModel(
|
|||
|
||||
private fun navigateToWalletConnect() {
|
||||
Analytics.send(Settings.ButtonWalletConnect())
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.WalletConnectSessions))
|
||||
store.dispatchNavigationAction { push(AppRoute.WalletConnectSessions) }
|
||||
}
|
||||
|
||||
private fun handleSocialNetworkClick(link: SocialNetworkLink) {
|
||||
Analytics.send(Settings.ButtonSocialNetwork(link.network))
|
||||
store.dispatchOnMain(NavigationAction.OpenUrl(link.url))
|
||||
store.dispatchOpenUrl(link.url)
|
||||
}
|
||||
|
||||
private fun getSocialLinks(): ImmutableList<SocialNetworkLink> {
|
||||
|
|
|
|||
|
|
@ -1,56 +1,34 @@
|
|||
package com.tangem.tap.features.details.ui.resetcard
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import com.tangem.tap.features.details.redux.DetailsState
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.store
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.rekotlin.StoreSubscriber
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
internal class ResetCardFragment : ComposeFragment(), StoreSubscriber<DetailsState> {
|
||||
internal class ResetCardFragment : ComposeFragment() {
|
||||
|
||||
@Inject
|
||||
override lateinit var uiDependencies: UiDependencies
|
||||
|
||||
private val viewModel: ResetCardViewModel by viewModels()
|
||||
|
||||
private var screenState = MutableStateFlow<ResetCardScreenState>(ResetCardScreenState.InitialState)
|
||||
|
||||
@Composable
|
||||
override fun ScreenContent(modifier: Modifier) {
|
||||
val state = screenState.collectAsStateWithLifecycle().value
|
||||
val state by viewModel.screenState.collectAsStateWithLifecycle()
|
||||
|
||||
ResetCardScreen(
|
||||
state = state,
|
||||
onBackClick = { store.dispatch(NavigationAction.PopBackTo()) },
|
||||
onBackClick = { store.dispatchNavigationAction(AppRouter::pop) },
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
store.subscribe(this) { state ->
|
||||
state.skipRepeats { oldState, newState ->
|
||||
oldState.detailsState == newState.detailsState
|
||||
}.select { it.detailsState }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
store.unsubscribe(this)
|
||||
}
|
||||
|
||||
override fun newState(state: DetailsState) {
|
||||
if (activity == null || view == null) return
|
||||
screenState.value = viewModel.updateState(state.cardSettingsState)
|
||||
}
|
||||
}
|
||||
|
|
@ -20,24 +20,19 @@ import com.tangem.tap.features.details.ui.cardsettings.resolveReference
|
|||
import com.tangem.tap.features.details.ui.common.DetailsMainButton
|
||||
import com.tangem.tap.features.details.ui.common.SettingsScreensScaffold
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.tap.features.details.ui.resetcard.ResetCardScreenState.ResetCardScreenContent.Dialog as ResetCardDialog
|
||||
import com.tangem.tap.features.details.ui.resetcard.ResetCardScreenState.Dialog as ResetCardDialog
|
||||
|
||||
@Composable
|
||||
internal fun ResetCardScreen(state: ResetCardScreenState, onBackClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
SettingsScreensScaffold(
|
||||
modifier = modifier,
|
||||
content = {
|
||||
when (state) {
|
||||
is ResetCardScreenState.ResetCardScreenContent -> ResetCardView(state = state)
|
||||
ResetCardScreenState.InitialState -> {
|
||||
// do nothing for now, just white screen
|
||||
}
|
||||
}
|
||||
ResetCardView(state = state)
|
||||
},
|
||||
onBackClick = onBackClick,
|
||||
)
|
||||
|
||||
when (val dialog = (state as? ResetCardScreenState.ResetCardScreenContent)?.dialog) {
|
||||
when (val dialog = state.dialog) {
|
||||
is ResetCardDialog.StartReset,
|
||||
is ResetCardDialog.ContinueReset,
|
||||
is ResetCardDialog.InterruptedReset,
|
||||
|
|
@ -48,7 +43,7 @@ internal fun ResetCardScreen(state: ResetCardScreenState, onBackClick: () -> Uni
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun ResetCardView(state: ResetCardScreenState.ResetCardScreenContent) {
|
||||
private fun ResetCardView(state: ResetCardScreenState) {
|
||||
val scrollState = rememberScrollState()
|
||||
|
||||
Column(
|
||||
|
|
@ -110,7 +105,7 @@ private fun Description(text: TextReference) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun Conditions(state: ResetCardScreenState.ResetCardScreenContent) {
|
||||
private fun Conditions(state: ResetCardScreenState) {
|
||||
state.warningsToShow.forEach {
|
||||
when (it) {
|
||||
ResetCardScreenState.WarningsToReset.LOST_WALLET_ACCESS -> {
|
||||
|
|
@ -120,7 +115,6 @@ private fun Conditions(state: ResetCardScreenState.ResetCardScreenContent) {
|
|||
description = TextReference.Res(R.string.reset_card_to_factory_condition_1),
|
||||
)
|
||||
}
|
||||
|
||||
ResetCardScreenState.WarningsToReset.LOST_PASSWORD_RESTORE -> {
|
||||
ConditionCheckBox(
|
||||
checkedState = state.acceptCondition2Checked,
|
||||
|
|
@ -192,7 +186,7 @@ private fun ResetButton(enabled: Boolean, onResetButtonClick: () -> Unit) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun CommonResetDialog(dialog: ResetCardScreenState.ResetCardScreenContent.Dialog) {
|
||||
private fun CommonResetDialog(dialog: ResetCardScreenState.Dialog) {
|
||||
BasicDialog(
|
||||
title = stringResource(dialog.titleResId),
|
||||
message = stringResource(dialog.messageResId),
|
||||
|
|
@ -231,10 +225,13 @@ private fun ResetCardScreenSample(modifier: Modifier = Modifier) {
|
|||
.background(TangemTheme.colors.background.secondary),
|
||||
) {
|
||||
ResetCardScreen(
|
||||
state = ResetCardScreenState.ResetCardScreenContent(
|
||||
accepted = true,
|
||||
state = ResetCardScreenState(
|
||||
resetButtonEnabled = true,
|
||||
showResetPasswordButton = false,
|
||||
warningsToShow = listOf(ResetCardScreenState.WarningsToReset.LOST_WALLET_ACCESS),
|
||||
descriptionText = TextReference.Res(R.string.reset_card_with_backup_to_factory_message),
|
||||
acceptCondition1Checked = false,
|
||||
acceptCondition2Checked = false,
|
||||
onAcceptCondition1ToggleClick = {},
|
||||
onAcceptCondition2ToggleClick = {},
|
||||
onResetButtonClick = {},
|
||||
|
|
|
|||
|
|
@ -4,63 +4,57 @@ import androidx.annotation.StringRes
|
|||
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
||||
import com.tangem.wallet.R
|
||||
|
||||
internal sealed class ResetCardScreenState {
|
||||
internal data class ResetCardScreenState(
|
||||
val resetButtonEnabled: Boolean,
|
||||
val descriptionText: TextReference,
|
||||
val warningsToShow: List<WarningsToReset>,
|
||||
val showResetPasswordButton: Boolean,
|
||||
val acceptCondition1Checked: Boolean,
|
||||
val acceptCondition2Checked: Boolean,
|
||||
val onAcceptCondition1ToggleClick: (Boolean) -> Unit,
|
||||
val onAcceptCondition2ToggleClick: (Boolean) -> Unit,
|
||||
val onResetButtonClick: () -> Unit,
|
||||
val dialog: Dialog?,
|
||||
) {
|
||||
|
||||
object InitialState : ResetCardScreenState()
|
||||
sealed class Dialog(
|
||||
@StringRes val titleResId: Int,
|
||||
@StringRes val messageResId: Int,
|
||||
) {
|
||||
|
||||
data class ResetCardScreenContent(
|
||||
val accepted: Boolean = false,
|
||||
val descriptionText: TextReference,
|
||||
val warningsToShow: List<WarningsToReset>,
|
||||
val acceptCondition1Checked: Boolean = false,
|
||||
val acceptCondition2Checked: Boolean = false,
|
||||
val onAcceptCondition1ToggleClick: (Boolean) -> Unit,
|
||||
val onAcceptCondition2ToggleClick: (Boolean) -> Unit,
|
||||
val onResetButtonClick: () -> Unit,
|
||||
val dialog: Dialog? = null,
|
||||
) : ResetCardScreenState() {
|
||||
val resetButtonEnabled: Boolean
|
||||
get() = accepted
|
||||
abstract val onConfirmClick: () -> Unit
|
||||
abstract val onDismiss: () -> Unit
|
||||
|
||||
sealed class Dialog(
|
||||
@StringRes val titleResId: Int,
|
||||
@StringRes val messageResId: Int,
|
||||
data class StartReset(
|
||||
override val onConfirmClick: () -> Unit,
|
||||
override val onDismiss: () -> Unit,
|
||||
) : Dialog(
|
||||
titleResId = R.string.common_attention,
|
||||
messageResId = R.string.card_settings_action_sheet_title,
|
||||
)
|
||||
|
||||
data class ContinueReset(
|
||||
override val onConfirmClick: () -> Unit,
|
||||
override val onDismiss: () -> Unit,
|
||||
) : Dialog(
|
||||
titleResId = R.string.card_settings_continue_reset_alert_title,
|
||||
messageResId = R.string.card_settings_continue_reset_alert_message,
|
||||
)
|
||||
|
||||
data class InterruptedReset(
|
||||
override val onConfirmClick: () -> Unit,
|
||||
override val onDismiss: () -> Unit,
|
||||
) : Dialog(
|
||||
titleResId = R.string.card_settings_interrupted_reset_alert_title,
|
||||
messageResId = R.string.card_settings_interrupted_reset_alert_message,
|
||||
)
|
||||
|
||||
data class CompletedReset(override val onConfirmClick: () -> Unit) : Dialog(
|
||||
titleResId = R.string.card_settings_completed_reset_alert_title,
|
||||
messageResId = R.string.card_settings_completed_reset_alert_message,
|
||||
) {
|
||||
|
||||
abstract val onConfirmClick: () -> Unit
|
||||
abstract val onDismiss: () -> Unit
|
||||
|
||||
data class StartReset(
|
||||
override val onConfirmClick: () -> Unit,
|
||||
override val onDismiss: () -> Unit,
|
||||
) : Dialog(
|
||||
titleResId = R.string.common_attention,
|
||||
messageResId = R.string.card_settings_action_sheet_title,
|
||||
)
|
||||
|
||||
data class ContinueReset(
|
||||
override val onConfirmClick: () -> Unit,
|
||||
override val onDismiss: () -> Unit,
|
||||
) : Dialog(
|
||||
titleResId = R.string.card_settings_continue_reset_alert_title,
|
||||
messageResId = R.string.card_settings_continue_reset_alert_message,
|
||||
)
|
||||
|
||||
data class InterruptedReset(
|
||||
override val onConfirmClick: () -> Unit,
|
||||
override val onDismiss: () -> Unit,
|
||||
) : Dialog(
|
||||
titleResId = R.string.card_settings_interrupted_reset_alert_title,
|
||||
messageResId = R.string.card_settings_interrupted_reset_alert_message,
|
||||
)
|
||||
|
||||
data class CompletedReset(override val onConfirmClick: () -> Unit) : Dialog(
|
||||
titleResId = R.string.card_settings_completed_reset_alert_title,
|
||||
messageResId = R.string.card_settings_completed_reset_alert_message,
|
||||
) {
|
||||
|
||||
override val onDismiss: () -> Unit = {}
|
||||
}
|
||||
override val onDismiss: () -> Unit = {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,104 +1,170 @@
|
|||
package com.tangem.tap.features.details.ui.resetcard
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.bundle.unbundle
|
||||
import com.tangem.common.routing.utils.popTo
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.domain.card.DeleteSavedAccessCodesUseCase
|
||||
import com.tangem.domain.card.ResetCardUseCase
|
||||
import com.tangem.domain.card.ResetCardUserCodeParams
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.legacy.asLockable
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.DeleteWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
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.CardSettingsState
|
||||
import com.tangem.tap.features.details.redux.DetailsAction.ResetToFactory
|
||||
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
||||
import com.tangem.tap.features.details.ui.resetcard.featuretoggles.ResetCardFeatureToggles
|
||||
import com.tangem.tap.features.details.ui.utils.toResetCardDescriptionText
|
||||
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
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import com.tangem.tap.features.details.redux.CardSettingsState.Dialog as CardSettingsDialog
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@HiltViewModel
|
||||
internal class ResetCardViewModel @Inject constructor(
|
||||
private val resetCardFeatureToggles: ResetCardFeatureToggles,
|
||||
getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
||||
private val resetCardUseCase: ResetCardUseCase,
|
||||
private val deleteSavedAccessCodesUseCase: DeleteSavedAccessCodesUseCase,
|
||||
private val deleteWalletUseCase: DeleteWalletUseCase,
|
||||
private val userWalletsListManager: UserWalletsListManager,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val cardSettingsInteractor: CardSettingsInteractor,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel() {
|
||||
|
||||
private val firstCardScanResponse = store.state.detailsState.cardSettingsState?.scanResponse
|
||||
?: error("ScanResponse can't be null")
|
||||
// region Card-set specific data. All cards from single set have the same userWalletId and cardTypesResolver
|
||||
private val currentUserWalletId = savedStateHandle.get<Bundle>(AppRoute.ResetToFactory.USER_WALLET_ID)
|
||||
?.unbundle(UserWalletId.serializer())
|
||||
?: error("UserWalletId must be provided for ResetCardViewModel")
|
||||
|
||||
private val currentUserWalletId = createUserWalletId(firstCardScanResponse)
|
||||
// Use only for card-specific data
|
||||
private val userWallet = getUserWalletUseCase(userWalletId = currentUserWalletId)
|
||||
.getOrElse { error("Failed to get user wallet: $it") }
|
||||
|
||||
private val currentCardTypesResolver = userWallet.cardTypesResolver
|
||||
private val currentUserCodeParams = ResetCardUserCodeParams(
|
||||
isAccessCodeSet = userWallet.scanResponse.card.isAccessCodeSet,
|
||||
isPasscodeSet = userWallet.scanResponse.card.isPasscodeSet,
|
||||
)
|
||||
// endregion
|
||||
|
||||
// region Data of card that was scanned on CardSettings
|
||||
private val primaryCardId: String = savedStateHandle.get<String>(AppRoute.ResetToFactory.CARD_ID)
|
||||
?: error("CardId must be provided for ResetCardViewModel")
|
||||
|
||||
private val isActiveBackupPrimaryCard =
|
||||
savedStateHandle.get<Boolean>(AppRoute.ResetToFactory.IS_ACTIVE_BACKUP_STATUS)
|
||||
?: error("IsActiveBackupCard must be provided for ResetCardViewModel")
|
||||
|
||||
private val primaryBackupCardsCount = savedStateHandle.get<Int>(AppRoute.ResetToFactory.BACKUP_CARDS_COUNT)
|
||||
?: error("CardCount must be provided for ResetCardViewModel")
|
||||
// endregion
|
||||
|
||||
// TODO: move logic to separate domain entity
|
||||
private var resetBackupCardCount = 0
|
||||
|
||||
fun updateState(state: CardSettingsState?): ResetCardScreenState.ResetCardScreenContent {
|
||||
val descriptionText = state?.cardInfo
|
||||
?.toResetCardDescriptionText()
|
||||
?: TextReference.Str(value = "")
|
||||
val screenState: MutableStateFlow<ResetCardScreenState> = MutableStateFlow(
|
||||
value = getInitialState(),
|
||||
)
|
||||
|
||||
private fun getInitialState(): ResetCardScreenState {
|
||||
val shouldShowResetPasswordButton = shouldShowResetPasswordButton()
|
||||
val warningsToShow = buildList {
|
||||
add(ResetCardScreenState.WarningsToReset.LOST_WALLET_ACCESS)
|
||||
|
||||
if (state?.isShowPasswordResetRadioButton == true) {
|
||||
if (shouldShowResetPasswordButton) {
|
||||
add(ResetCardScreenState.WarningsToReset.LOST_PASSWORD_RESTORE)
|
||||
}
|
||||
}
|
||||
|
||||
return ResetCardScreenState.ResetCardScreenContent(
|
||||
accepted = state?.resetButtonEnabled ?: false,
|
||||
descriptionText = descriptionText,
|
||||
return ResetCardScreenState(
|
||||
resetButtonEnabled = false,
|
||||
descriptionText = getResetToFactoryDescription(
|
||||
isActiveBackupStatus = isActiveBackupPrimaryCard,
|
||||
typesResolver = currentCardTypesResolver,
|
||||
),
|
||||
warningsToShow = warningsToShow,
|
||||
acceptCondition1Checked = state?.condition1Checked ?: false,
|
||||
acceptCondition2Checked = state?.condition2Checked ?: false,
|
||||
onAcceptCondition1ToggleClick = { store.dispatch(ResetToFactory.AcceptCondition1(it)) },
|
||||
onAcceptCondition2ToggleClick = { store.dispatch(ResetToFactory.AcceptCondition2(it)) },
|
||||
onResetButtonClick = { showDialog(CardSettingsDialog.StartResetDialog) },
|
||||
dialog = state?.dialog?.let(::createDialog),
|
||||
showResetPasswordButton = shouldShowResetPasswordButton,
|
||||
acceptCondition1Checked = false,
|
||||
acceptCondition2Checked = false,
|
||||
onAcceptCondition1ToggleClick = ::toggleFirstCondition,
|
||||
onAcceptCondition2ToggleClick = ::toggleSecondCondition,
|
||||
onResetButtonClick = { showDialog(ResetCardDialog.StartResetDialog) },
|
||||
dialog = null,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createDialog(dialog: CardSettingsDialog): ResetCardScreenState.ResetCardScreenContent.Dialog {
|
||||
private fun shouldShowResetPasswordButton(): Boolean {
|
||||
val isTangemWallet = currentCardTypesResolver.isTangemWallet() || currentCardTypesResolver.isWallet2()
|
||||
|
||||
return isTangemWallet && isActiveBackupPrimaryCard
|
||||
}
|
||||
|
||||
private fun toggleFirstCondition(isAccepted: Boolean) {
|
||||
screenState.update { prevState ->
|
||||
val resetButtonEnabled = if (prevState.showResetPasswordButton) {
|
||||
isAccepted && prevState.acceptCondition2Checked
|
||||
} else {
|
||||
isAccepted
|
||||
}
|
||||
|
||||
prevState.copy(
|
||||
acceptCondition1Checked = isAccepted,
|
||||
resetButtonEnabled = resetButtonEnabled,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleSecondCondition(isAccepted: Boolean) {
|
||||
screenState.update { prevState ->
|
||||
val resetButtonEnabled = prevState.acceptCondition1Checked && isAccepted
|
||||
|
||||
prevState.copy(
|
||||
acceptCondition2Checked = isAccepted,
|
||||
resetButtonEnabled = resetButtonEnabled,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createDialog(dialog: ResetCardDialog): ResetCardScreenState.Dialog {
|
||||
return when (dialog) {
|
||||
CardSettingsDialog.StartResetDialog -> {
|
||||
ResetCardScreenState.ResetCardScreenContent.Dialog.StartReset(
|
||||
ResetCardDialog.StartResetDialog -> {
|
||||
ResetCardScreenState.Dialog.StartReset(
|
||||
onConfirmClick = ::onStartResetClick,
|
||||
onDismiss = ::dismissDialog,
|
||||
)
|
||||
}
|
||||
CardSettingsDialog.ContinueResetDialog -> {
|
||||
ResetCardScreenState.ResetCardScreenContent.Dialog.ContinueReset(
|
||||
ResetCardDialog.ContinueResetDialog -> {
|
||||
ResetCardScreenState.Dialog.ContinueReset(
|
||||
onConfirmClick = ::onContinueResetClick,
|
||||
onDismiss = ::onContinueResetDialogDismiss,
|
||||
)
|
||||
}
|
||||
CardSettingsDialog.InterruptedResetDialog -> {
|
||||
ResetCardScreenState.ResetCardScreenContent.Dialog.InterruptedReset(
|
||||
ResetCardDialog.InterruptedResetDialog -> {
|
||||
ResetCardScreenState.Dialog.InterruptedReset(
|
||||
onConfirmClick = ::onContinueResetClick,
|
||||
onDismiss = ::onInterruptedResetDialogDismiss,
|
||||
)
|
||||
}
|
||||
CardSettingsDialog.CompletedResetDialog -> {
|
||||
ResetCardScreenState.ResetCardScreenContent.Dialog.CompletedReset(
|
||||
ResetCardDialog.CompletedResetDialog -> {
|
||||
ResetCardScreenState.Dialog.CompletedReset(
|
||||
onConfirmClick = ::dismissAndFinishFullReset,
|
||||
)
|
||||
}
|
||||
|
|
@ -108,21 +174,23 @@ internal class ResetCardViewModel @Inject constructor(
|
|||
private fun onStartResetClick() {
|
||||
dismissDialog()
|
||||
|
||||
if (resetCardFeatureToggles.isFullResetEnabled) {
|
||||
makeFullReset()
|
||||
} else {
|
||||
store.dispatch(ResetToFactory.Proceed)
|
||||
}
|
||||
makeFullReset()
|
||||
}
|
||||
|
||||
private fun makeFullReset() {
|
||||
viewModelScope.launch {
|
||||
resetCardUseCase(card = firstCardScanResponse.card).onRight {
|
||||
deleteSavedAccessCodesUseCase(firstCardScanResponse.card.cardId)
|
||||
deleteWalletUseCase(currentUserWalletId)
|
||||
resetCardUseCase(cardId = primaryCardId, params = currentUserCodeParams).onRight {
|
||||
deleteSavedAccessCodesUseCase(cardId = primaryCardId)
|
||||
val hasUserWallets = deleteWalletUseCase(userWalletId = currentUserWalletId).getOrElse {
|
||||
Timber.e("Unable to delete user wallet: $it")
|
||||
return@launch
|
||||
}
|
||||
|
||||
if (hasUserWallets) {
|
||||
val newSelectedWallet = getSelectedWalletSyncUseCase().getOrElse {
|
||||
error("Failed to get selected wallet: $it")
|
||||
}
|
||||
|
||||
val newSelectedWallet = getSelectedWalletSyncUseCase().getOrNull()
|
||||
if (newSelectedWallet != null) {
|
||||
store.onUserWalletSelected(newSelectedWallet)
|
||||
}
|
||||
|
||||
|
|
@ -139,24 +207,26 @@ internal class ResetCardViewModel @Inject constructor(
|
|||
viewModelScope.launch {
|
||||
resetCardUseCase(
|
||||
cardNumber = resetBackupCardCount + 1,
|
||||
card = firstCardScanResponse.card,
|
||||
params = currentUserCodeParams,
|
||||
userWalletId = currentUserWalletId,
|
||||
)
|
||||
.onRight {
|
||||
resetBackupCardCount++
|
||||
.onRight { isResetCompleted ->
|
||||
if (isResetCompleted) {
|
||||
resetBackupCardCount++
|
||||
}
|
||||
|
||||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
|
||||
checkRemainingBackupCards()
|
||||
}
|
||||
.onLeft { showDialog(CardSettingsDialog.InterruptedResetDialog) }
|
||||
.onLeft { showDialog(ResetCardDialog.InterruptedResetDialog) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun onContinueResetDialogDismiss() {
|
||||
dismissDialog()
|
||||
|
||||
showDialog(CardSettingsDialog.InterruptedResetDialog)
|
||||
showDialog(ResetCardDialog.InterruptedResetDialog)
|
||||
}
|
||||
|
||||
private fun onInterruptedResetDialogDismiss() {
|
||||
|
|
@ -166,15 +236,15 @@ internal class ResetCardViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun checkRemainingBackupCards() {
|
||||
val backupCardsCount = firstCardScanResponse.getBackupCardsCount()
|
||||
val backupCardsCount = getBackupCardsCount()
|
||||
|
||||
when {
|
||||
backupCardsCount > resetBackupCardCount -> showDialog(CardSettingsDialog.ContinueResetDialog)
|
||||
backupCardsCount > resetBackupCardCount -> showDialog(ResetCardDialog.ContinueResetDialog)
|
||||
backupCardsCount == resetBackupCardCount -> {
|
||||
analyticsEventHandler.send(
|
||||
event = Settings.CardSettings.FactoryResetFinished(cardsCount = resetBackupCardCount + 1),
|
||||
)
|
||||
showDialog(CardSettingsDialog.CompletedResetDialog)
|
||||
showDialog(ResetCardDialog.CompletedResetDialog)
|
||||
}
|
||||
else -> finishFullReset()
|
||||
}
|
||||
|
|
@ -187,42 +257,33 @@ internal class ResetCardViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun finishFullReset() {
|
||||
cardSettingsInteractor.clear()
|
||||
|
||||
val newSelectedWallet = userWalletsListManager.selectedUserWalletSync
|
||||
|
||||
if (newSelectedWallet != null) {
|
||||
store.dispatch(NavigationAction.PopBackTo(AppScreen.Wallet))
|
||||
store.dispatchNavigationAction { popTo<AppRoute.Wallet>() }
|
||||
} else {
|
||||
val isLocked = runCatching { userWalletsListManager.asLockable()?.isLockedSync }.isSuccess
|
||||
if (isLocked && userWalletsListManager.hasUserWallets) {
|
||||
store.dispatch(NavigationAction.PopBackTo(AppScreen.Welcome))
|
||||
store.dispatchNavigationAction { popTo<AppRoute.Welcome>() }
|
||||
} else {
|
||||
store.dispatch(NavigationAction.PopBackTo(AppScreen.Home))
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Home) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showDialog(dialog: CardSettingsDialog) {
|
||||
store.dispatch(ResetToFactory.ShowDialog(dialog))
|
||||
private fun showDialog(dialog: ResetCardDialog) {
|
||||
screenState.update { it.copy(dialog = createDialog(dialog)) }
|
||||
}
|
||||
|
||||
private fun dismissDialog() {
|
||||
store.dispatch(ResetToFactory.DismissDialog)
|
||||
screenState.update { it.copy(dialog = null) }
|
||||
}
|
||||
|
||||
private fun ScanResponse.getBackupCardsCount(): Int {
|
||||
if (!cardTypesResolver.isMultiwalletAllowed()) return 0
|
||||
private fun getBackupCardsCount(): Int {
|
||||
if (!currentCardTypesResolver.isMultiwalletAllowed()) return 0
|
||||
|
||||
return when (val status = card.backupStatus) {
|
||||
is CardDTO.BackupStatus.Active -> status.cardCount
|
||||
is CardDTO.BackupStatus.CardLinked,
|
||||
is CardDTO.BackupStatus.NoBackup,
|
||||
null,
|
||||
-> 0
|
||||
}
|
||||
}
|
||||
|
||||
private fun createUserWalletId(scanResponse: ScanResponse): UserWalletId {
|
||||
return UserWalletIdBuilder.scanResponse(scanResponse).build()
|
||||
?: error("UserWalletId can't be null")
|
||||
return primaryBackupCardsCount
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
package com.tangem.tap.features.details.ui.resetcard.di
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
import com.tangem.tap.features.details.ui.resetcard.featuretoggles.DefaultResetCardFeatureToggles
|
||||
import com.tangem.tap.features.details.ui.resetcard.featuretoggles.ResetCardFeatureToggles
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object ResetCardModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideResetCardFeatureToggles(featureTogglesManager: FeatureTogglesManager): ResetCardFeatureToggles {
|
||||
return DefaultResetCardFeatureToggles(featureTogglesManager)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
package com.tangem.tap.features.details.ui.resetcard.featuretoggles
|
||||
|
||||
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
|
||||
|
||||
internal class DefaultResetCardFeatureToggles(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) : ResetCardFeatureToggles {
|
||||
|
||||
override val isFullResetEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(name = "FULL_RESET_ENABLED")
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
package com.tangem.tap.features.details.ui.resetcard.featuretoggles
|
||||
|
||||
interface ResetCardFeatureToggles {
|
||||
|
||||
val isFullResetEnabled: Boolean
|
||||
}
|
||||
|
|
@ -1,54 +1,34 @@
|
|||
package com.tangem.tap.features.details.ui.securitymode
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
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.features.details.redux.DetailsState
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.store
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.rekotlin.StoreSubscriber
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
internal class SecurityModeFragment : ComposeFragment(), StoreSubscriber<DetailsState> {
|
||||
internal class SecurityModeFragment : ComposeFragment() {
|
||||
|
||||
@Inject
|
||||
override lateinit var uiDependencies: UiDependencies
|
||||
|
||||
private val viewModel = SecurityModeViewModel(store)
|
||||
|
||||
private var screenState: MutableState<SecurityModeScreenState> =
|
||||
mutableStateOf(viewModel.updateState(store.state.detailsState.cardSettingsState?.manageSecurityState))
|
||||
private val viewModel: SecurityModeViewModel by viewModels()
|
||||
|
||||
@Composable
|
||||
override fun ScreenContent(modifier: Modifier) {
|
||||
val state by viewModel.screenState.collectAsStateWithLifecycle()
|
||||
|
||||
SecurityModeScreen(
|
||||
modifier = modifier,
|
||||
state = screenState.value,
|
||||
onBackClick = { store.dispatch(NavigationAction.PopBackTo()) },
|
||||
state = state,
|
||||
onBackClick = { store.dispatchNavigationAction(AppRouter::pop) },
|
||||
)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
store.subscribe(this) { state ->
|
||||
state.skipRepeats { oldState, newState ->
|
||||
oldState.detailsState == newState.detailsState
|
||||
}.select { it.detailsState }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
store.unsubscribe(this)
|
||||
}
|
||||
|
||||
override fun newState(state: DetailsState) {
|
||||
if (activity == null || view == null) return
|
||||
screenState.value = viewModel.updateState(state.cardSettingsState?.manageSecurityState)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,29 +1,109 @@
|
|||
package com.tangem.tap.features.details.ui.securitymode
|
||||
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.details.redux.DetailsAction
|
||||
import com.tangem.tap.features.details.redux.ManageSecurityState
|
||||
import android.os.Bundle
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
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.redux.SecurityOption
|
||||
import org.rekotlin.Store
|
||||
import com.tangem.tap.features.details.ui.common.utils.getAllowedSecurityOptions
|
||||
import com.tangem.tap.features.details.ui.common.utils.getCurrentSecurityOption
|
||||
import com.tangem.tap.store
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class SecurityModeViewModel(val store: Store<AppState>) {
|
||||
@HiltViewModel
|
||||
internal class SecurityModeViewModel @Inject constructor(
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val tangemSdkManager: TangemSdkManager,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel() {
|
||||
|
||||
private val userWalletId = savedStateHandle.get<Bundle>(AppRoute.DetailsSecurity.USER_WALLET_ID_KEY)
|
||||
?.unbundle(UserWalletId.serializer())
|
||||
?: error("UserWalletId is required for SecurityModeViewModel")
|
||||
|
||||
val screenState = MutableStateFlow(
|
||||
value = getInitialState(),
|
||||
)
|
||||
|
||||
private fun getInitialState(): SecurityModeScreenState {
|
||||
val userWallet = getUserWallet()
|
||||
val scanResponse = userWallet.scanResponse
|
||||
val card = scanResponse.card
|
||||
val cardTypesResolver = scanResponse.cardTypesResolver
|
||||
|
||||
val currentSecurityOption = getCurrentSecurityOption(card)
|
||||
val allowedSecurityOptions = getAllowedSecurityOptions(card, cardTypesResolver, currentSecurityOption)
|
||||
|
||||
fun updateState(state: ManageSecurityState?): SecurityModeScreenState {
|
||||
if (state == null) {
|
||||
return SecurityModeScreenState(
|
||||
availableOptions = emptyList(),
|
||||
selectedSecurityMode = SecurityOption.LongTap,
|
||||
isSaveChangesEnabled = false,
|
||||
onNewModeSelected = {},
|
||||
onSaveChangesClicked = {},
|
||||
)
|
||||
}
|
||||
return SecurityModeScreenState(
|
||||
availableOptions = state.allowedOptions.toList(),
|
||||
selectedSecurityMode = state.selectedOption,
|
||||
isSaveChangesEnabled = state.selectedOption != state.currentOption,
|
||||
onNewModeSelected = { store.dispatch(DetailsAction.ManageSecurity.SelectOption(it)) },
|
||||
onSaveChangesClicked = { store.dispatch(DetailsAction.ManageSecurity.SaveChanges) },
|
||||
availableOptions = allowedSecurityOptions.toList(),
|
||||
selectedSecurityMode = currentSecurityOption,
|
||||
isSaveChangesEnabled = false,
|
||||
onNewModeSelected = ::selectOption,
|
||||
onSaveChangesClicked = ::saveChanges,
|
||||
)
|
||||
}
|
||||
|
||||
private fun selectOption(securityOption: SecurityOption) {
|
||||
screenState.update { state ->
|
||||
state.copy(
|
||||
selectedSecurityMode = securityOption,
|
||||
isSaveChangesEnabled = securityOption != getCurrentSecurityOption(getUserWallet().scanResponse.card),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveChanges() {
|
||||
val userWallet = getUserWallet()
|
||||
val cardId = userWallet.cardId
|
||||
val selectedOption = screenState.value.selectedSecurityMode
|
||||
|
||||
viewModelScope.launch {
|
||||
val result = when (selectedOption) {
|
||||
SecurityOption.LongTap -> tangemSdkManager.setLongTap(cardId)
|
||||
SecurityOption.PassCode -> tangemSdkManager.setPasscode(cardId)
|
||||
SecurityOption.AccessCode -> tangemSdkManager.setAccessCode(cardId)
|
||||
}
|
||||
|
||||
val paramValue = AnalyticsParam.SecurityMode.from(selectedOption)
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
Analytics.send(Settings.CardSettings.SecurityModeChanged(paramValue))
|
||||
|
||||
store.dispatchNavigationAction(AppRouter::pop)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
val error = result.error
|
||||
if (error is TangemSdkError && error !is TangemSdkError.UserCancelled) {
|
||||
Analytics.send(Settings.CardSettings.SecurityModeChanged(paramValue, error))
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getUserWallet(): UserWallet {
|
||||
return getUserWalletUseCase(userWalletId).getOrElse {
|
||||
error("Unable to get user wallet $userWalletId: $it")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
package com.tangem.tap.features.details.ui.utils
|
||||
|
||||
import com.tangem.tap.features.details.redux.CardInfo
|
||||
import com.tangem.tap.features.details.ui.cardsettings.TextReference
|
||||
import com.tangem.wallet.R
|
||||
|
||||
internal fun CardInfo.toResetCardDescriptionText(): TextReference {
|
||||
return if (!this.hasBackup || this.isTwin) {
|
||||
TextReference.Res(R.string.reset_card_without_backup_to_factory_message)
|
||||
} else {
|
||||
TextReference.Res(R.string.reset_card_with_backup_to_factory_message)
|
||||
}
|
||||
}
|
||||
|
|
@ -6,11 +6,12 @@ import androidx.compose.runtime.MutableState
|
|||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import com.tangem.tap.common.analytics.events.WalletConnect
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectState
|
||||
import com.tangem.tap.store
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
|
@ -41,7 +42,7 @@ internal class WalletConnectFragment : ComposeFragment(), StoreSubscriber<Wallet
|
|||
modifier = modifier,
|
||||
state = state,
|
||||
onBackClick = {
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
store.dispatchNavigationAction(AppRouter::pop)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,10 @@ package com.tangem.tap.features.details.ui.walletconnect.dialogs
|
|||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.os.bundleOf
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.feature.qrscanning.QrScanningRouter
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.domain.qrscanning.models.SourceType
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectAction
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -22,14 +20,9 @@ object ClipboardOrScanQrDialog {
|
|||
store.dispatch(WalletConnectAction.OpenSession(wcUri))
|
||||
}
|
||||
setNegativeButton(context.getText(R.string.wallet_connect_scan_new_code)) { _, _ ->
|
||||
store.dispatch(
|
||||
NavigationAction.NavigateTo(
|
||||
screen = AppScreen.QrScanning,
|
||||
bundle = bundleOf(
|
||||
QrScanningRouter.SOURCE_KEY to SourceType.WALLET_CONNECT,
|
||||
),
|
||||
),
|
||||
)
|
||||
store.dispatchNavigationAction {
|
||||
push(AppRoute.QrScanning(source = SourceType.WALLET_CONNECT))
|
||||
}
|
||||
}
|
||||
setOnDismissListener {
|
||||
store.dispatch(GlobalAction.HideDialog)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package com.tangem.tap.features.disclaimer.redux
|
||||
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.features.disclaimer.Disclaimer
|
||||
import org.rekotlin.Action
|
||||
|
|
@ -10,7 +9,7 @@ sealed class DisclaimerAction : Action {
|
|||
data class SetDisclaimer(val disclaimer: Disclaimer) : DisclaimerAction()
|
||||
|
||||
data class Show(
|
||||
val fromScreen: AppScreen,
|
||||
val from: DisclaimerSource,
|
||||
val callback: DisclaimerCallback? = null,
|
||||
) : DisclaimerAction()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
package com.tangem.tap.features.disclaimer.redux
|
||||
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.mainScope
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -25,17 +26,19 @@ private fun handleDisclaimerMiddleware(action: Action, appState: AppState) {
|
|||
|
||||
when (action) {
|
||||
is DisclaimerAction.Show -> {
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer))
|
||||
store.dispatchNavigationAction {
|
||||
push(AppRoute.Disclaimer(isTosAccepted = action.from == DisclaimerSource.Details))
|
||||
}
|
||||
}
|
||||
is DisclaimerAction.AcceptDisclaimer -> {
|
||||
mainScope.launch {
|
||||
state.disclaimer.accept()
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
store.dispatchNavigationAction(AppRouter::pop)
|
||||
state.callback?.onAccept?.invoke()
|
||||
}
|
||||
}
|
||||
is DisclaimerAction.OnBackPressed -> {
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
store.dispatchNavigationAction(AppRouter::pop)
|
||||
state.callback?.onDismiss?.invoke()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ private fun internalReduce(action: Action, state: AppState): DisclaimerState {
|
|||
disclaimer = action.disclaimer,
|
||||
)
|
||||
is DisclaimerAction.Show -> disclaimerState.copy(
|
||||
showedFromScreen = action.fromScreen,
|
||||
showedFrom = action.from,
|
||||
callback = action.callback,
|
||||
)
|
||||
is DisclaimerAction.AcceptDisclaimer, is DisclaimerAction.OnBackPressed -> disclaimerState.copy(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
package com.tangem.tap.features.disclaimer.redux
|
||||
|
||||
enum class DisclaimerSource {
|
||||
Home, Details
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.tap.features.disclaimer.redux
|
||||
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.features.disclaimer.Disclaimer
|
||||
import com.tangem.tap.features.disclaimer.DummyDisclaimer
|
||||
|
|
@ -9,7 +9,7 @@ import org.rekotlin.StateType
|
|||
|
||||
data class DisclaimerState(
|
||||
val disclaimer: Disclaimer = DummyDisclaimer(),
|
||||
val showedFromScreen: AppScreen = AppScreen.Home,
|
||||
val showedFrom: DisclaimerSource = DisclaimerSource.Home,
|
||||
val callback: DisclaimerCallback? = null,
|
||||
val progressState: ProgressState? = null,
|
||||
) : StateType
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import android.webkit.WebView
|
|||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.transition.TransitionInflater
|
||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
|
||||
import com.tangem.core.ui.extensions.setStatusBarColor
|
||||
import com.tangem.tap.common.entities.ProgressState
|
||||
import com.tangem.tap.common.extensions.beginDelayedTransition
|
||||
|
|
@ -18,6 +18,7 @@ import com.tangem.tap.features.BaseFragment
|
|||
import com.tangem.tap.features.addBackPressHandler
|
||||
import com.tangem.tap.features.disclaimer.Disclaimer
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerAction
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerSource
|
||||
import com.tangem.tap.features.disclaimer.redux.DisclaimerState
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.wallet.R
|
||||
|
|
@ -76,17 +77,14 @@ class DisclaimerFragment : BaseFragment(R.layout.fragment_disclaimer), StoreSubs
|
|||
|
||||
override fun configureTransitions() {
|
||||
val inflater = TransitionInflater.from(requireContext())
|
||||
when (store.state.disclaimerState.showedFromScreen) {
|
||||
AppScreen.Home -> {
|
||||
when (store.state.disclaimerState.showedFrom) {
|
||||
DisclaimerSource.Home -> {
|
||||
enterTransition = inflater.inflateTransition(android.R.transition.slide_bottom)
|
||||
exitTransition = inflater.inflateTransition(android.R.transition.slide_top)
|
||||
}
|
||||
AppScreen.Details -> {
|
||||
DisclaimerSource.Details -> {
|
||||
super.configureTransitions()
|
||||
}
|
||||
else -> {
|
||||
/* no-op */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,17 +6,16 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.components.SystemBarsEffect
|
||||
import com.tangem.core.ui.components.SystemBarsIconsDisposable
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import com.tangem.domain.tokens.TokensAction
|
||||
import com.tangem.tap.common.analytics.events.IntroductionProcess
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.home.compose.StoriesScreen
|
||||
import com.tangem.tap.features.home.redux.HomeAction
|
||||
|
|
@ -43,15 +42,12 @@ class HomeFragment : ComposeFragment(), StoreSubscriber<HomeState> {
|
|||
@Composable
|
||||
override fun ScreenContent(modifier: Modifier) {
|
||||
BackHandler(onBack = requireActivity()::finish)
|
||||
SystemBarsEffect {
|
||||
setSystemBarsColor(color = Color.Transparent, darkIcons = false)
|
||||
}
|
||||
SystemBarsIconsDisposable(darkIcons = false)
|
||||
ScreenContent()
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
activity?.window?.let { WindowCompat.setDecorFitsSystemWindows(it, false) }
|
||||
|
||||
store.subscribe(subscriber = this) { state ->
|
||||
state
|
||||
|
|
@ -86,7 +82,7 @@ class HomeFragment : ComposeFragment(), StoreSubscriber<HomeState> {
|
|||
},
|
||||
onSearchTokensClick = {
|
||||
Analytics.send(IntroductionProcess.ButtonTokensList())
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.ManageTokens))
|
||||
store.dispatchNavigationAction { push(AppRoute.ManageTokens) }
|
||||
store.dispatch(TokensAction.SetArgs.ReadAccess)
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -6,11 +6,10 @@ import com.tangem.common.doOnFailure
|
|||
import com.tangem.common.doOnResult
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||
|
|
@ -126,7 +125,7 @@ private fun proceedWithScanResponse(scanResponse: ScanResponse) = scope.launch {
|
|||
scope.launch { store.onUserWalletSelected(userWallet = userWallet) }
|
||||
}
|
||||
.doOnResult {
|
||||
navigateTo(AppScreen.Wallet)
|
||||
navigateTo(AppRoute.Wallet)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -150,8 +149,8 @@ private fun sendSignedInCardAnalyticsEvent(scanResponse: ScanResponse) {
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun navigateTo(appScreen: AppScreen) {
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(appScreen))
|
||||
private suspend fun navigateTo(route: AppRoute) {
|
||||
store.dispatchNavigationAction { push(route) }
|
||||
delay(HIDE_PROGRESS_DELAY)
|
||||
store.dispatch(HomeAction.ScanInProgress(scanInProgress = false))
|
||||
}
|
||||
|
|
@ -3,11 +3,10 @@ package com.tangem.tap.features.main
|
|||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.tangem.blockchainsdk.BlockchainSDKFactory
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.core.navigation.ReduxNavController
|
||||
import com.tangem.domain.appcurrency.FetchAppCurrenciesUseCase
|
||||
import com.tangem.domain.balancehiding.BalanceHidingSettings
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
|
|
@ -16,9 +15,11 @@ import com.tangem.domain.balancehiding.UpdateBalanceHidingSettingsUseCase
|
|||
import com.tangem.domain.feedback.FeedbackManagerFeatureToggles
|
||||
import com.tangem.domain.settings.DeleteDeprecatedLogsUseCase
|
||||
import com.tangem.domain.settings.IncrementAppLaunchCounterUseCase
|
||||
import com.tangem.domain.staking.FetchStakingTokensUseCase
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.features.send.api.featuretoggles.SendFeatureToggles
|
||||
import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles
|
||||
import com.tangem.tap.common.extensions.setContext
|
||||
import com.tangem.tap.features.main.model.MainScreenState
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -34,7 +35,7 @@ import javax.inject.Inject
|
|||
internal class MainViewModel @Inject constructor(
|
||||
private val updateBalanceHidingSettingsUseCase: UpdateBalanceHidingSettingsUseCase,
|
||||
private val listenToFlipsUseCase: ListenToFlipsUseCase,
|
||||
private val reduxNavController: ReduxNavController,
|
||||
private val router: AppRouter,
|
||||
private val fetchAppCurrenciesUseCase: FetchAppCurrenciesUseCase,
|
||||
private val deleteDeprecatedLogsUseCase: DeleteDeprecatedLogsUseCase,
|
||||
private val incrementAppLaunchCounterUseCase: IncrementAppLaunchCounterUseCase,
|
||||
|
|
@ -44,6 +45,8 @@ internal class MainViewModel @Inject constructor(
|
|||
private val sendFeatureToggles: SendFeatureToggles,
|
||||
private val feedbackManagerFeatureToggles: FeedbackManagerFeatureToggles,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val stakingFeatureToggles: StakingFeatureToggles,
|
||||
private val fetchStakingTokensUseCase: FetchStakingTokensUseCase,
|
||||
getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
||||
) : ViewModel(), MainIntents {
|
||||
|
||||
|
|
@ -70,6 +73,10 @@ internal class MainViewModel @Inject constructor(
|
|||
displayBalancesHidingStatusToast()
|
||||
displayHiddenBalancesModalNotification()
|
||||
|
||||
if (stakingFeatureToggles.isStakingEnabled) {
|
||||
fetchStakingTokens()
|
||||
}
|
||||
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
deleteDeprecatedLogsUseCase()
|
||||
}
|
||||
|
|
@ -115,6 +122,14 @@ internal class MainViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun fetchStakingTokens() {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
fetchStakingTokensUseCase()
|
||||
.onLeft { Timber.e(it.toString(), "Unable to fetch the staking tokens list") }
|
||||
.onRight { Timber.d("Staking token list was fetched successfully") }
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateSendFeatureToggle() {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
sendFeatureToggles.fetchNewSendEnabled()
|
||||
|
|
@ -136,7 +151,7 @@ internal class MainViewModel @Inject constructor(
|
|||
if (state.value.modalNotification?.isShow != true && !it.isUpdateFromToast) {
|
||||
listenToFlipsUseCase.changeUpdateEnabled(false)
|
||||
stateHolder.updateWithHiddenBalancesNotification()
|
||||
reduxNavController.navigate(NavigationAction.NavigateTo(AppScreen.ModalNotification))
|
||||
router.push(AppRoute.ModalNotification)
|
||||
}
|
||||
}
|
||||
.launchIn(viewModelScope)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.tap.features.onboarding
|
||||
|
||||
import com.tangem.common.extensions.VoidCallback
|
||||
import com.tangem.core.navigation.StateDialog
|
||||
import com.tangem.domain.redux.StateDialog
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@ package com.tangem.tap.features.onboarding
|
|||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.util.twinsIsTwinned
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
|
|
@ -32,42 +32,43 @@ import timber.log.Timber
|
|||
*/
|
||||
object OnboardingHelper {
|
||||
suspend fun isOnboardingCase(response: ScanResponse): Boolean {
|
||||
val onboardingManager = store.state.globalState.onboardingState.onboardingManager
|
||||
val onboardingManager =
|
||||
store.state.globalState.onboardingState.onboardingManager ?: OnboardingManager(response)
|
||||
val cardId = response.card.cardId
|
||||
return when {
|
||||
response.cardTypesResolver.isTangemTwins() -> {
|
||||
if (!response.twinsIsTwinned()) {
|
||||
true
|
||||
} else {
|
||||
onboardingManager?.isActivationInProgress(cardId) ?: false
|
||||
onboardingManager.isActivationInProgress(cardId) ?: false
|
||||
}
|
||||
}
|
||||
|
||||
response.cardTypesResolver.isWallet2() || response.cardTypesResolver.isShibaWallet() -> {
|
||||
val emptyWallets = response.card.wallets.isEmpty()
|
||||
val activationInProgress = onboardingManager?.isActivationInProgress(cardId)
|
||||
val activationInProgress = onboardingManager.isActivationInProgress(cardId)
|
||||
val isNoBackup = response.card.backupStatus == CardDTO.BackupStatus.NoBackup &&
|
||||
!DemoHelper.isDemoCard(response)
|
||||
emptyWallets || activationInProgress == true || isNoBackup
|
||||
emptyWallets || activationInProgress || isNoBackup
|
||||
}
|
||||
|
||||
response.card.wallets.isNotEmpty() -> onboardingManager?.isActivationInProgress(cardId) ?: false
|
||||
response.card.wallets.isNotEmpty() -> onboardingManager.isActivationInProgress(cardId) ?: false
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
|
||||
fun whereToNavigate(scanResponse: ScanResponse): AppScreen {
|
||||
fun whereToNavigate(scanResponse: ScanResponse): AppRoute {
|
||||
return when (val type = scanResponse.productType) {
|
||||
ProductType.Note -> AppScreen.OnboardingNote
|
||||
ProductType.Note -> AppRoute.OnboardingNote
|
||||
ProductType.Wallet,
|
||||
ProductType.Wallet2,
|
||||
ProductType.Ring,
|
||||
-> if (scanResponse.card.settings.isBackupAllowed) {
|
||||
AppScreen.OnboardingWallet
|
||||
AppRoute.OnboardingWallet()
|
||||
} else {
|
||||
AppScreen.OnboardingOther
|
||||
AppRoute.OnboardingOther
|
||||
}
|
||||
ProductType.Twins -> AppScreen.OnboardingTwins
|
||||
ProductType.Twins -> AppRoute.OnboardingTwins
|
||||
ProductType.Start2Coin,
|
||||
ProductType.Visa,
|
||||
-> throw UnsupportedOperationException("Onboarding for ${type.name} cards is not supported")
|
||||
|
|
@ -111,14 +112,14 @@ object OnboardingHelper {
|
|||
backupCardsIds = backupCardsIds?.toSet(),
|
||||
),
|
||||
)
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet))
|
||||
store.dispatchNavigationAction { push(AppRoute.Wallet) }
|
||||
delay(timeMillis = 1_800)
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.SaveWallet))
|
||||
store.dispatchNavigationAction { push(AppRoute.SaveWallet) }
|
||||
}
|
||||
// If device has no biometry and save wallet screen has been shown, then go through old scenario
|
||||
else -> {
|
||||
proceedWithScanResponse(scanResponse, backupCardsIds, hasBackupError)
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.Wallet))
|
||||
store.dispatchNavigationAction { push(AppRoute.Wallet) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,15 +7,20 @@ import androidx.core.view.MenuProvider
|
|||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.feedback.SupportInfo
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.wallet.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class OnboardingMenuProvider : MenuProvider {
|
||||
class OnboardingMenuProvider(
|
||||
private val scanResponseProvider: Provider<ScanResponse>,
|
||||
) : MenuProvider {
|
||||
|
||||
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||
menuInflater.inflate(R.menu.menu_onboarding, menu)
|
||||
}
|
||||
|
|
@ -24,7 +29,12 @@ class OnboardingMenuProvider : MenuProvider {
|
|||
R.id.menu_item_chat_support -> {
|
||||
Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Intro))
|
||||
// changed on email support [REDACTED_TASK_KEY]
|
||||
store.dispatch(GlobalAction.SendEmail(SupportInfo()))
|
||||
store.dispatch(
|
||||
GlobalAction.SendEmail(
|
||||
feedbackData = SupportInfo(),
|
||||
scanResponse = scanResponseProvider(),
|
||||
),
|
||||
)
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import com.tangem.tap.common.extensions.show
|
|||
import com.tangem.tap.common.transitions.HomeToOnboardingTransition
|
||||
import com.tangem.tap.features.BaseStoreFragment
|
||||
import com.tangem.tap.features.onboarding.OnboardingMenuProvider
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.FragmentOnboardingMainBinding
|
||||
import com.tangem.wallet.databinding.ViewOnboardingProgressBinding
|
||||
|
|
@ -32,7 +34,13 @@ abstract class BaseOnboardingFragment<T> : BaseStoreFragment(R.layout.fragment_o
|
|||
(activity as? AppCompatActivity)?.setSupportActionBar(binding.toolbar)
|
||||
}
|
||||
|
||||
override fun loadToolbarMenu(): MenuProvider? = OnboardingMenuProvider()
|
||||
override fun loadToolbarMenu(): MenuProvider = OnboardingMenuProvider(
|
||||
scanResponseProvider = Provider {
|
||||
store.state.globalState.onboardingState.onboardingManager?.scanResponse
|
||||
?: store.state.detailsState.scanResponse
|
||||
?: error("ScanResponse must be not null")
|
||||
},
|
||||
)
|
||||
|
||||
protected fun showConfetti(show: Boolean) = with(binding.vConfetti) {
|
||||
lavConfetti.show(show)
|
||||
|
|
|
|||
|
|
@ -11,12 +11,14 @@ import coil.load
|
|||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.ShareElement
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.extensions.getDrawableCompat
|
||||
import com.tangem.tap.common.extensions.stripZeroPlainString
|
||||
import com.tangem.tap.common.toggleWidget.RefreshBalanceWidget
|
||||
import com.tangem.tap.common.transitions.InternalNoteLayoutTransition
|
||||
import com.tangem.tap.features.addBackPressHandler
|
||||
import com.tangem.tap.features.onboarding.OnboardingWalletBalance
|
||||
import com.tangem.tap.features.onboarding.products.BaseOnboardingFragment
|
||||
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteAction
|
||||
import com.tangem.tap.features.onboarding.products.note.redux.OnboardingNoteState
|
||||
|
|
@ -134,7 +136,7 @@ class OnboardingNoteFragment : BaseOnboardingFragment<OnboardingNoteState>() {
|
|||
}
|
||||
|
||||
private fun setupTopUpWalletState(state: OnboardingNoteState) = with(mainBinding.onboardingActionContainer) {
|
||||
if (state.isBuyAllowed) {
|
||||
if (availableForBuy(state.scanResponse, state.walletBalance)) {
|
||||
btnMainAction.setText(R.string.onboarding_top_up_button_but_crypto)
|
||||
btnMainAction.icon = null
|
||||
btnMainAction.setOnClickListener {
|
||||
|
|
@ -217,6 +219,11 @@ class OnboardingNoteFragment : BaseOnboardingFragment<OnboardingNoteState>() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun availableForBuy(scanResponse: ScanResponse?, walletBalance: OnboardingWalletBalance): Boolean {
|
||||
scanResponse ?: return false
|
||||
return store.state.globalState.exchangeManager.availableForBuy(scanResponse, walletBalance.currency)
|
||||
}
|
||||
|
||||
override fun handleOnBackPressed() {
|
||||
store.dispatch(OnboardingNoteAction.OnBackPressed)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@ package com.tangem.tap.features.onboarding.products.note.redux
|
|||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
|
||||
import com.tangem.data.tokens.utils.CryptoCurrencyFactory
|
||||
import com.tangem.domain.common.extensions.makePrimaryWalletManager
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
|
|
@ -204,7 +205,7 @@ private fun handleNoteAction(appState: () -> AppState?, action: Action, dispatch
|
|||
OnboardingDialog.InterruptOnboarding(
|
||||
onOk = {
|
||||
OnboardingHelper.onInterrupted()
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
store.dispatchNavigationAction(AppRouter::pop)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ private fun internalReduce(action: Action, appState: AppState): OnboardingNoteSt
|
|||
|
||||
when (action) {
|
||||
is GlobalAction.Onboarding.Start -> {
|
||||
state = OnboardingNoteState()
|
||||
state = OnboardingNoteState(scanResponse = action.scanResponse)
|
||||
}
|
||||
is OnboardingNoteAction.SetArtworkUrl -> {
|
||||
state = state.copy(cardArtworkUrl = action.artworkUrl)
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
package com.tangem.tap.features.onboarding.products.note.redux
|
||||
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.features.onboarding.OnboardingWalletBalance
|
||||
import com.tangem.tap.store
|
||||
import org.rekotlin.StateType
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -20,14 +19,11 @@ data class OnboardingNoteState(
|
|||
val currentStep: OnboardingNoteStep = OnboardingNoteStep.None,
|
||||
val steps: List<OnboardingNoteStep> = OnboardingNoteStep.values().toList(),
|
||||
val showConfetti: Boolean = false,
|
||||
val scanResponse: ScanResponse? = null,
|
||||
) : StateType {
|
||||
|
||||
val progress: Int
|
||||
get() = steps.indexOf(currentStep)
|
||||
|
||||
val isBuyAllowed: Boolean by ReadOnlyProperty<Any, Boolean> { _, _ ->
|
||||
store.state.globalState.exchangeManager.availableForBuy(walletBalance.currency)
|
||||
}
|
||||
}
|
||||
|
||||
enum class OnboardingNoteStep {
|
||||
|
|
|
|||
|
|
@ -3,9 +3,10 @@ package com.tangem.tap.features.onboarding.products.twins.redux
|
|||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.utils.popTo
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
|
||||
import com.tangem.data.tokens.utils.CryptoCurrencyFactory
|
||||
import com.tangem.domain.common.extensions.makePrimaryWalletManager
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
|
|
@ -39,12 +40,13 @@ import kotlinx.coroutines.runBlocking
|
|||
import org.rekotlin.Action
|
||||
import org.rekotlin.DispatchFunction
|
||||
import org.rekotlin.Middleware
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
object TwinCardsMiddleware {
|
||||
val handler = twinsWalletMiddleware
|
||||
}
|
||||
|
||||
private val twinsWalletMiddleware: Middleware<AppState> = { dispatch, state ->
|
||||
private val twinsWalletMiddleware: Middleware<AppState> = { dispatch, _ ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
handle(action, dispatch)
|
||||
|
|
@ -63,16 +65,16 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
|
|||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
|
||||
fun getScanResponse(): ScanResponse {
|
||||
return when (twinCardsState.mode) {
|
||||
CreateTwinWalletMode.CreateWallet -> onboardingManager?.scanResponse
|
||||
CreateTwinWalletMode.RecreateWallet -> globalState.scanResponse
|
||||
return when (val mode = twinCardsState.mode) {
|
||||
is CreateTwinWalletMode.CreateWallet -> onboardingManager?.scanResponse
|
||||
is CreateTwinWalletMode.RecreateWallet -> mode.scanResponse
|
||||
} ?: throw NullPointerException("ScanResponse can't be NULL")
|
||||
}
|
||||
|
||||
fun updateScanResponse(response: ScanResponse) {
|
||||
when (twinCardsState.mode) {
|
||||
CreateTwinWalletMode.CreateWallet -> onboardingManager?.scanResponse = response
|
||||
CreateTwinWalletMode.RecreateWallet -> store.dispatchOnMain(GlobalAction.SaveScanResponse(response))
|
||||
is CreateTwinWalletMode.CreateWallet -> onboardingManager?.scanResponse = response
|
||||
is CreateTwinWalletMode.RecreateWallet -> store.dispatchOnMain(GlobalAction.SaveScanResponse(response))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -111,7 +113,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
|
|||
}
|
||||
|
||||
when (twinCardsState.mode) {
|
||||
CreateTwinWalletMode.CreateWallet -> {
|
||||
is CreateTwinWalletMode.CreateWallet -> {
|
||||
mainScope.launch {
|
||||
val wasTwinsOnboardingShown = store.inject(DaggerGraphState::wasTwinsOnboardingShownUseCase)
|
||||
.invokeSync()
|
||||
|
|
@ -130,7 +132,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
|
|||
store.dispatch(dispatchAction)
|
||||
}
|
||||
}
|
||||
CreateTwinWalletMode.RecreateWallet -> {
|
||||
is CreateTwinWalletMode.RecreateWallet -> {
|
||||
store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.Warning))
|
||||
}
|
||||
}
|
||||
|
|
@ -224,10 +226,10 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
|
|||
delay(DELAY_SDK_DIALOG_CLOSE)
|
||||
withMainContext {
|
||||
when (twinCardsState.mode) {
|
||||
CreateTwinWalletMode.CreateWallet -> {
|
||||
is CreateTwinWalletMode.CreateWallet -> {
|
||||
store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.TopUpWallet))
|
||||
}
|
||||
CreateTwinWalletMode.RecreateWallet -> {
|
||||
is CreateTwinWalletMode.RecreateWallet -> {
|
||||
store.dispatch(TwinCardsAction.SetStepOfScreen(TwinCardsStep.Done))
|
||||
}
|
||||
}
|
||||
|
|
@ -317,18 +319,18 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
|
|||
TwinCardsAction.Done -> {
|
||||
val scanResponse = getScanResponse()
|
||||
when (twinCardsState.mode) {
|
||||
CreateTwinWalletMode.CreateWallet -> {
|
||||
is CreateTwinWalletMode.CreateWallet -> {
|
||||
store.dispatchOnMain(GlobalAction.Onboarding.Stop)
|
||||
OnboardingHelper.trySaveWalletAndNavigateToWalletScreen(scanResponse)
|
||||
}
|
||||
CreateTwinWalletMode.RecreateWallet -> {
|
||||
is CreateTwinWalletMode.RecreateWallet -> {
|
||||
scope.launch {
|
||||
val walletsRepository = store.inject(DaggerGraphState::walletsRepository)
|
||||
|
||||
if (walletsRepository.shouldSaveUserWalletsSync()) {
|
||||
OnboardingHelper.trySaveWalletAndNavigateToWalletScreen(scanResponse)
|
||||
} else {
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home))
|
||||
store.dispatchNavigationAction { popTo<AppRoute.Home>() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -352,7 +354,7 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
|
|||
store.dispatch(TwinCardsAction.CardsManager.Release)
|
||||
|
||||
action.shouldResetTwinCardsWidget(shouldReturnCardBack) {
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo(getPopBackScreen()))
|
||||
store.dispatchNavigationAction { popTo(routeClass = getPopBackScreen()) }
|
||||
}
|
||||
}
|
||||
store.dispatchDialogShow(OnboardingDialog.InterruptOnboarding(onOkCallback))
|
||||
|
|
@ -362,18 +364,19 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
|
|||
}
|
||||
}
|
||||
|
||||
private fun getPopBackScreen(): AppScreen {
|
||||
private fun getPopBackScreen(): KClass<out AppRoute> {
|
||||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
|
||||
return if (userWalletsListManager.hasUserWallets) {
|
||||
val isLocked = runCatching { userWalletsListManager.asLockable()?.isLockedSync }
|
||||
.fold(onSuccess = { true }, onFailure = { false })
|
||||
|
||||
if (isLocked) {
|
||||
AppScreen.Welcome
|
||||
AppRoute.Welcome::class
|
||||
} else {
|
||||
AppScreen.Wallet
|
||||
AppRoute.Wallet::class
|
||||
}
|
||||
} else {
|
||||
AppScreen.Home
|
||||
AppRoute.Home::class
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
package com.tangem.tap.features.onboarding.products.twins.redux
|
||||
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.getTwinCardNumber
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import org.rekotlin.Action
|
||||
|
||||
|
|
@ -35,7 +35,10 @@ private fun internalReduce(action: Action, state: AppState): TwinCardsState {
|
|||
)
|
||||
}
|
||||
is TwinCardsAction.SetStepOfScreen -> {
|
||||
state = state.copy(currentStep = action.step)
|
||||
state = state.copy(
|
||||
currentStep = action.step,
|
||||
welcomeOnlyScanResponse = (action.step as? TwinCardsStep.WelcomeOnly)?.scanResponse,
|
||||
)
|
||||
}
|
||||
is TwinCardsAction.SetUserUnderstand -> {
|
||||
state = state.copy(userWasUnderstandIfWalletRecreate = action.isUnderstand)
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
package com.tangem.tap.features.onboarding.products.twins.redux
|
||||
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.common.TwinCardNumber
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.domain.twins.TwinCardsManager
|
||||
import com.tangem.tap.features.onboarding.OnboardingWalletBalance
|
||||
import com.tangem.tap.store
|
||||
import org.rekotlin.StateType
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -29,11 +27,12 @@ data class TwinCardsState(
|
|||
val balanceNonCriticalError: TapError? = null,
|
||||
val balanceCriticalError: TapError? = null,
|
||||
val showConfetti: Boolean = false,
|
||||
val welcomeOnlyScanResponse: ScanResponse? = null,
|
||||
) : StateType {
|
||||
|
||||
val steps: List<TwinCardsStep>
|
||||
get() = when (mode) {
|
||||
CreateTwinWalletMode.CreateWallet -> listOf(
|
||||
is CreateTwinWalletMode.CreateWallet -> listOf(
|
||||
TwinCardsStep.None,
|
||||
TwinCardsStep.CreateFirstWallet,
|
||||
TwinCardsStep.CreateSecondWallet,
|
||||
|
|
@ -41,7 +40,7 @@ data class TwinCardsState(
|
|||
TwinCardsStep.TopUpWallet,
|
||||
TwinCardsStep.Done,
|
||||
)
|
||||
CreateTwinWalletMode.RecreateWallet -> listOf(
|
||||
is CreateTwinWalletMode.RecreateWallet -> listOf(
|
||||
TwinCardsStep.None,
|
||||
TwinCardsStep.CreateFirstWallet,
|
||||
TwinCardsStep.CreateSecondWallet,
|
||||
|
|
@ -55,13 +54,15 @@ data class TwinCardsState(
|
|||
|
||||
val twinningInProgress: Boolean
|
||||
get() = currentStep == TwinCardsStep.CreateSecondWallet || currentStep == TwinCardsStep.CreateThirdWallet
|
||||
|
||||
val isBuyAllowed: Boolean by ReadOnlyProperty<Any, Boolean> { _, _ ->
|
||||
store.state.globalState.exchangeManager.availableForBuy(walletBalance.currency)
|
||||
}
|
||||
}
|
||||
|
||||
enum class CreateTwinWalletMode { CreateWallet, RecreateWallet }
|
||||
sealed class CreateTwinWalletMode {
|
||||
data object CreateWallet : CreateTwinWalletMode()
|
||||
|
||||
data class RecreateWallet(
|
||||
val scanResponse: ScanResponse,
|
||||
) : CreateTwinWalletMode()
|
||||
}
|
||||
|
||||
sealed class TwinCardsStep {
|
||||
object None : TwinCardsStep()
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import android.view.View
|
|||
import android.view.animation.OvershootInterpolator
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.constraintlayout.widget.ConstraintSet
|
||||
import androidx.core.view.MenuProvider
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.transition.TransitionManager
|
||||
import coil.load
|
||||
|
|
@ -25,12 +26,15 @@ import com.tangem.tap.common.toggleWidget.RefreshBalanceWidget
|
|||
import com.tangem.tap.common.transitions.InternalNoteLayoutTransition
|
||||
import com.tangem.tap.domain.twins.TwinsCardWidget
|
||||
import com.tangem.tap.features.addBackPressHandler
|
||||
import com.tangem.tap.features.onboarding.OnboardingMenuProvider
|
||||
import com.tangem.tap.features.onboarding.OnboardingWalletBalance
|
||||
import com.tangem.tap.features.onboarding.products.BaseOnboardingFragment
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.CreateTwinWalletMode
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsAction
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsState
|
||||
import com.tangem.tap.features.onboarding.products.twins.redux.TwinCardsStep
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.LayoutOnboardingContainerTopBinding
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
|
@ -52,15 +56,19 @@ internal class OnboardingTwinsFragment : BaseOnboardingFragment<TwinCardsState>(
|
|||
|
||||
override fun configureTransitions() {
|
||||
when (store.state.twinCardsState.mode) {
|
||||
CreateTwinWalletMode.CreateWallet -> {
|
||||
is CreateTwinWalletMode.CreateWallet -> {
|
||||
super.configureTransitions()
|
||||
}
|
||||
CreateTwinWalletMode.RecreateWallet -> {
|
||||
is CreateTwinWalletMode.RecreateWallet -> {
|
||||
configureDefaultTransactions()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun loadToolbarMenu(): MenuProvider = OnboardingMenuProvider(
|
||||
scanResponseProvider = Provider { getActualScanResponse() },
|
||||
)
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
|
@ -330,7 +338,7 @@ internal class OnboardingTwinsFragment : BaseOnboardingFragment<TwinCardsState>(
|
|||
else -> {}
|
||||
}
|
||||
|
||||
if (state.isBuyAllowed) {
|
||||
if (availableForBuy(getActualScanResponse(), state.walletBalance)) {
|
||||
btnMainAction.setText(R.string.onboarding_top_up_button_but_crypto)
|
||||
btnMainAction.setOnClickListener {
|
||||
store.dispatch(TwinCardsAction.TopUp)
|
||||
|
|
@ -383,8 +391,8 @@ internal class OnboardingTwinsFragment : BaseOnboardingFragment<TwinCardsState>(
|
|||
tvBody.setText(R.string.onboarding_done_body)
|
||||
|
||||
val layout = when (state.mode) {
|
||||
CreateTwinWalletMode.CreateWallet -> R.layout.lp_onboarding_done_activation_twins
|
||||
CreateTwinWalletMode.RecreateWallet -> R.layout.lp_onboarding_done
|
||||
is CreateTwinWalletMode.CreateWallet -> R.layout.lp_onboarding_done_activation_twins
|
||||
is CreateTwinWalletMode.RecreateWallet -> R.layout.lp_onboarding_done
|
||||
}
|
||||
updateConstraints(state.currentStep, layout)
|
||||
}
|
||||
|
|
@ -412,6 +420,18 @@ internal class OnboardingTwinsFragment : BaseOnboardingFragment<TwinCardsState>(
|
|||
}
|
||||
}
|
||||
|
||||
private fun availableForBuy(scanResponse: ScanResponse?, walletBalance: OnboardingWalletBalance): Boolean {
|
||||
scanResponse ?: return false
|
||||
return store.state.globalState.exchangeManager.availableForBuy(scanResponse, walletBalance.currency)
|
||||
}
|
||||
|
||||
private fun getActualScanResponse(): ScanResponse {
|
||||
return store.state.twinCardsState.welcomeOnlyScanResponse
|
||||
?: store.state.globalState.onboardingState.onboardingManager?.scanResponse
|
||||
?: store.state.detailsState.scanResponse
|
||||
?: error("ScanResponse must be not null")
|
||||
}
|
||||
|
||||
override fun handleOnBackPressed() {
|
||||
store.dispatch(
|
||||
TwinCardsAction.OnBackPressed { should, popAction ->
|
||||
|
|
|
|||
|
|
@ -7,16 +7,16 @@ import com.tangem.common.core.TangemSdkError
|
|||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.extensions.ifNotNull
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.common.services.Result
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.models.Artwork
|
||||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||
import com.tangem.domain.wallets.models.Artwork
|
||||
import com.tangem.feature.onboarding.data.model.CreateWalletResponse
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseSource
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.BackupValidator
|
||||
|
|
@ -25,10 +25,7 @@ import com.tangem.operations.backup.BackupService
|
|||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse
|
||||
|
|
@ -164,7 +161,7 @@ private fun handleWalletAction(action: Action) {
|
|||
store.dispatch(GlobalAction.Onboarding.Stop)
|
||||
|
||||
if (scanResponse == null) {
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
store.dispatchNavigationAction(AppRouter::pop)
|
||||
store.dispatch(HomeAction.ReadCard(scope = action.scope))
|
||||
} else {
|
||||
val backupState = store.state.onboardingWalletState.backupState
|
||||
|
|
@ -529,7 +526,8 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction)
|
|||
addedBackupCardsCount = backupService.addedBackupCardsCount,
|
||||
),
|
||||
)
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.OnboardingWallet))
|
||||
|
||||
store.dispatchNavigationAction { push(AppRoute.OnboardingWallet()) }
|
||||
}
|
||||
is BackupAction.SkipBackup -> {
|
||||
Analytics.send(Onboarding.Backup.Skipped())
|
||||
|
|
@ -614,7 +612,7 @@ private fun handleOnBackPressed(state: OnboardingWalletState) {
|
|||
}
|
||||
BackupStep.Finished -> {
|
||||
OnboardingHelper.onInterrupted()
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
store.dispatchNavigationAction(AppRouter::pop)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -625,7 +623,7 @@ private fun showInterruptOnboardingDialog() {
|
|||
onOk = {
|
||||
OnboardingHelper.onInterrupted()
|
||||
store.dispatch(BackupAction.DiscardBackup)
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
store.dispatchNavigationAction(AppRouter::pop)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.tangem.tap.features.onboarding.products.wallet.redux
|
|||
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import com.tangem.core.navigation.StateDialog
|
||||
import com.tangem.domain.redux.StateDialog
|
||||
import org.rekotlin.StateType
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
package com.tangem.tap.features.onboarding.products.wallet.ui
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.windowsize.rememberWindowSize
|
||||
import com.tangem.feature.onboarding.api.OnboardingSeedPhraseScreen
|
||||
import com.tangem.feature.onboarding.api.OnboardingSeedPhraseApi
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseScreen
|
||||
|
|
@ -16,6 +20,7 @@ import com.tangem.wallet.R
|
|||
*/
|
||||
internal class OnboardingSeedPhraseStateHandler(
|
||||
private val onboardingSeedPhraseApi: OnboardingSeedPhraseApi = OnboardingSeedPhraseScreen(),
|
||||
private val activity: Activity,
|
||||
) {
|
||||
|
||||
fun newState(
|
||||
|
|
@ -49,11 +54,16 @@ internal class OnboardingSeedPhraseStateHandler(
|
|||
val subScreen = viewModel.currentScreen.collectAsState().value
|
||||
setMainScreenToolbarTitle(walletFragment, subScreen)
|
||||
|
||||
onboardingSeedPhraseApi.ScreenContent(
|
||||
uiState = viewModel.uiState,
|
||||
subScreen = subScreen,
|
||||
progress = viewModel.progress.collectAsState(0).value.toFloat() / onboardingWalletMaxProgress,
|
||||
)
|
||||
TangemTheme(
|
||||
isDark = isSystemInDarkTheme(),
|
||||
windowSize = rememberWindowSize(activity = activity),
|
||||
) {
|
||||
onboardingSeedPhraseApi.ScreenContent(
|
||||
uiState = viewModel.uiState,
|
||||
subScreen = subScreen,
|
||||
progress = viewModel.progress.collectAsState(0).value.toFloat() / onboardingWalletMaxProgress,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,13 +21,13 @@ import com.google.android.material.tabs.TabLayoutMediator
|
|||
import com.tangem.common.CardIdFormatter
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.core.CardIdDisplayFormat
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.core.ui.extensions.setStatusBarColor
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.feature.onboarding.data.model.CreateWalletResponse
|
||||
import com.tangem.feature.onboarding.navigation.OnboardingRouter
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseSource
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseMediator
|
||||
import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseRouter
|
||||
|
|
@ -46,6 +46,7 @@ import com.tangem.tap.features.onboarding.products.wallet.redux.*
|
|||
import com.tangem.tap.features.onboarding.products.wallet.ui.dialogs.AccessCodeDialog
|
||||
import com.tangem.tap.mainScope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.wallet.R
|
||||
import com.tangem.wallet.databinding.FragmentOnboardingWalletBinding
|
||||
import com.tangem.wallet.databinding.LayoutOnboardingSeedPhraseBinding
|
||||
|
|
@ -66,9 +67,10 @@ class OnboardingWalletFragment :
|
|||
|
||||
internal val bindingSeedPhrase: LayoutOnboardingSeedPhraseBinding by lazy { binding.onboardingSeedPhraseContainer }
|
||||
|
||||
private val canSkipBackup by lazy { arguments?.getBoolean(OnboardingRouter.CAN_SKIP_BACKUP) ?: true }
|
||||
private val canSkipBackup by lazy { arguments?.getBoolean(AppRoute.OnboardingWallet.CAN_SKIP_BACKUP_KEY) ?: true }
|
||||
|
||||
private lateinit var seedPhraseStateHandler: OnboardingSeedPhraseStateHandler
|
||||
|
||||
private val seedPhraseStateHandler: OnboardingSeedPhraseStateHandler = OnboardingSeedPhraseStateHandler()
|
||||
private val seedPhraseViewModel by viewModels<SeedPhraseViewModel>()
|
||||
|
||||
private lateinit var cardsWidget: WalletCardsWidget
|
||||
|
|
@ -79,6 +81,7 @@ class OnboardingWalletFragment :
|
|||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
seedPhraseStateHandler = OnboardingSeedPhraseStateHandler(activity = requireActivity())
|
||||
|
||||
val newSeedPhraseRouter = makeSeedPhraseRouter()
|
||||
seedPhraseRouter = newSeedPhraseRouter
|
||||
|
|
@ -113,7 +116,12 @@ class OnboardingWalletFragment :
|
|||
)
|
||||
}
|
||||
|
||||
override fun loadToolbarMenu(): MenuProvider = OnboardingMenuProvider()
|
||||
override fun loadToolbarMenu(): MenuProvider = OnboardingMenuProvider(
|
||||
scanResponseProvider = Provider {
|
||||
store.state.globalState.onboardingState.onboardingManager?.scanResponse
|
||||
?: error("ScanResponse must be not null")
|
||||
},
|
||||
)
|
||||
|
||||
private fun reInitCardsWidgetIfNeeded(backupCardsCounts: Int) = with(binding) {
|
||||
val viewBackupCount = flCardsContainer.childCount - 1
|
||||
|
|
@ -505,7 +513,13 @@ class OnboardingWalletFragment :
|
|||
onOpenChat = {
|
||||
Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Intro))
|
||||
// changed on email support [REDACTED_TASK_KEY]
|
||||
store.dispatch(GlobalAction.SendEmail(SupportInfo()))
|
||||
store.dispatch(
|
||||
GlobalAction.SendEmail(
|
||||
feedbackData = SupportInfo(),
|
||||
scanResponse = store.state.globalState.onboardingState.onboardingManager?.scanResponse
|
||||
?: error("ScanResponse must be not null"),
|
||||
),
|
||||
)
|
||||
},
|
||||
onOpenUriClick = { uri ->
|
||||
store.dispatchOpenUrl(uri.toString())
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ internal object AttestationFailedDialog {
|
|||
return MaterialAlertDialogBuilder(context, R.style.CustomMaterialDialog).apply {
|
||||
setTitle(R.string.common_error)
|
||||
setMessage(R.string.issuer_signature_loading_failed)
|
||||
setPositiveButton(R.string.ok) { dialog, _ ->
|
||||
setPositiveButton(R.string.common_ok) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
}
|
||||
setOnDismissListener {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,13 @@ object WalletActivationErrorDialog {
|
|||
setNegativeButton(R.string.common_support) { _, _ ->
|
||||
// changed on email support [REDACTED_TASK_KEY]
|
||||
Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Intro))
|
||||
store.dispatch(GlobalAction.SendEmail(SupportInfo()))
|
||||
store.dispatch(
|
||||
GlobalAction.SendEmail(
|
||||
feedbackData = SupportInfo(),
|
||||
scanResponse = store.state.globalState.onboardingState.onboardingManager?.scanResponse
|
||||
?: error("ScanResponse must be not null"),
|
||||
),
|
||||
)
|
||||
}
|
||||
setOnDismissListener { store.dispatchDialogHide() }
|
||||
setCancelable(false)
|
||||
|
|
|
|||
|
|
@ -3,29 +3,22 @@ package com.tangem.tap.features.saveWallet.redux
|
|||
import com.tangem.common.*
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.utils.popTo
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.MainScreen
|
||||
import com.tangem.tap.common.analytics.events.Onboarding
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.extensions.onUserWalletSelected
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.mainScope
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.scope
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.saveIn
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Middleware
|
||||
import org.rekotlin.Store
|
||||
import timber.log.Timber
|
||||
|
||||
internal class SaveWalletMiddleware {
|
||||
|
|
@ -83,12 +76,12 @@ internal class SaveWalletMiddleware {
|
|||
Timber.e(error, "Unable to save user wallet")
|
||||
}
|
||||
.doOnSuccess { mainScope.launch { store.onUserWalletSelected(userWallet) } }
|
||||
.doOnResult { store.navigateToWallet() }
|
||||
.doOnResult { navigateToWallet() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun enrollBiometrics() {
|
||||
store.dispatchOnMain(NavigationAction.OpenBiometricsSettings)
|
||||
activityResultCaller.openSystemBiometrySettings()
|
||||
}
|
||||
|
||||
private fun allowToUseBiometrics(state: SaveWalletState) = scope.launch {
|
||||
|
|
@ -131,7 +124,7 @@ internal class SaveWalletMiddleware {
|
|||
)
|
||||
|
||||
store.dispatchWithMain(SaveWalletAction.AllowToUseBiometrics.Success)
|
||||
store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Wallet))
|
||||
store.dispatchNavigationAction { popTo<AppRoute.Wallet>() }
|
||||
}
|
||||
|
||||
private fun dismiss(state: SaveWalletState) {
|
||||
|
|
@ -160,13 +153,9 @@ internal class SaveWalletMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun Store<AppState>.navigateToWallet() {
|
||||
dispatchWithMain(
|
||||
if (store.state.navigationState.backStack.contains(AppScreen.Wallet)) {
|
||||
NavigationAction.PopBackTo(AppScreen.Wallet)
|
||||
} else {
|
||||
NavigationAction.NavigateTo(AppScreen.Wallet)
|
||||
},
|
||||
)
|
||||
private fun navigateToWallet() {
|
||||
store.dispatchNavigationAction {
|
||||
replaceAll(AppRoute.Wallet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,13 +2,7 @@ package com.tangem.tap.features.saveWallet.ui.components
|
|||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.Text
|
||||
|
|
@ -20,16 +14,10 @@ import androidx.compose.ui.res.painterResource
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.components.SpacerH16
|
||||
import com.tangem.core.ui.components.SpacerH32
|
||||
import com.tangem.core.ui.components.SpacerH4
|
||||
import com.tangem.core.ui.components.SpacerHHalf
|
||||
import com.tangem.core.ui.components.SpacerW24
|
||||
import com.tangem.core.ui.components.SpacerW8
|
||||
import com.tangem.core.ui.components.*
|
||||
import com.tangem.core.ui.components.atoms.Hand
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.wallet.R
|
||||
|
||||
@Composable
|
||||
|
|
@ -37,10 +25,7 @@ internal fun SaveWalletScreenContent(showProgress: Boolean, onSaveWalletClick: (
|
|||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Header(onCloseClick = onCloseClick)
|
||||
SpacerHHalf()
|
||||
Title(
|
||||
modifier = Modifier
|
||||
.widthIn(max = TangemTheme.dimens.size200),
|
||||
)
|
||||
Title(modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing22))
|
||||
SpacerH32()
|
||||
Description(
|
||||
modifier = Modifier
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@ import com.tangem.blockchain.common.FeePaidCurrency
|
|||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.core.navigation.StateDialog
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.redux.StateDialog
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.tap.common.analytics.events.Token.Send.AddressEntered
|
||||
import com.tangem.tap.common.redux.ToastNotificationAction
|
||||
|
|
@ -185,12 +186,16 @@ sealed class SendAction : SendScreenAction {
|
|||
) : Dialog()
|
||||
|
||||
sealed class SendTransactionFails : Dialog() {
|
||||
data class CardSdkError(val error: TangemSdkError) : Dialog()
|
||||
data class BlockchainSdkError(val error: com.tangem.blockchain.common.BlockchainSdkError) : Dialog()
|
||||
data class CardSdkError(val error: TangemSdkError, val scanResponse: ScanResponse) : Dialog()
|
||||
data class BlockchainSdkError(
|
||||
val error: com.tangem.blockchain.common.BlockchainSdkError,
|
||||
val scanResponse: ScanResponse,
|
||||
) : Dialog()
|
||||
}
|
||||
|
||||
data class RequestFeeError(
|
||||
val error: com.tangem.blockchain.common.BlockchainSdkError,
|
||||
val scanResponse: ScanResponse,
|
||||
val onRetry: () -> Unit,
|
||||
) : Dialog()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
package com.tangem.tap.features.send.redux.middlewares
|
||||
|
||||
import com.tangem.blockchain.common.*
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.BlockchainSdkError
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.common.extensions.isZero
|
||||
|
|
@ -92,6 +95,7 @@ class RequestFeeMiddleware {
|
|||
dispatch(
|
||||
SendAction.Dialog.RequestFeeError(
|
||||
error = blockchainSdkError,
|
||||
scanResponse = scanResponse,
|
||||
onRetry = { dispatch(FeeAction.RequestFee) },
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -14,10 +14,10 @@ import com.tangem.blockchain.common.transaction.Fee
|
|||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.blockchainsdk.utils.minimalAmount
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
import com.tangem.domain.demo.DemoTransactionSender
|
||||
|
|
@ -270,7 +270,7 @@ private fun sendTransaction(
|
|||
),
|
||||
)
|
||||
Analytics.sendSelectedCurrencyEvent(mainCurrencyType)
|
||||
dispatch(NavigationAction.PopBackTo())
|
||||
store.dispatchNavigationAction(AppRouter::pop)
|
||||
}
|
||||
}
|
||||
is Result.Failure -> {
|
||||
|
|
@ -288,11 +288,25 @@ private fun sendTransaction(
|
|||
val tangemSdkError = error.tangemError as? TangemSdkError ?: return@withMainContext
|
||||
if (tangemSdkError is TangemSdkError.UserCancelled) return@withMainContext
|
||||
|
||||
dispatch(SendAction.Dialog.SendTransactionFails.CardSdkError(tangemSdkError))
|
||||
dispatch(
|
||||
SendAction.Dialog.SendTransactionFails.CardSdkError(
|
||||
error = tangemSdkError,
|
||||
scanResponse = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
.selectedUserWalletSync?.scanResponse
|
||||
?: error("ScanResponse must be not null"),
|
||||
),
|
||||
)
|
||||
}
|
||||
is BlockchainSdkError.CreateAccountUnderfunded -> {
|
||||
// from XLM, XRP, Polkadot
|
||||
dispatch(SendAction.Dialog.SendTransactionFails.BlockchainSdkError(error))
|
||||
dispatch(
|
||||
SendAction.Dialog.SendTransactionFails.BlockchainSdkError(
|
||||
error = error,
|
||||
scanResponse = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
.selectedUserWalletSync?.scanResponse
|
||||
?: error("ScanResponse must be not null"),
|
||||
),
|
||||
)
|
||||
}
|
||||
is BlockchainSdkError.Kaspa.UtxoAmountError -> {
|
||||
dispatch(
|
||||
|
|
@ -326,12 +340,19 @@ private fun sendTransaction(
|
|||
AppDialog.SimpleOkDialogRes(
|
||||
headerId = R.string.common_done,
|
||||
messageId = R.string.alert_demo_feature_disabled,
|
||||
onOk = { dispatch(NavigationAction.PopBackTo()) },
|
||||
onOk = { store.dispatchNavigationAction(AppRouter::pop) },
|
||||
),
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
dispatch(SendAction.Dialog.SendTransactionFails.BlockchainSdkError(error))
|
||||
dispatch(
|
||||
SendAction.Dialog.SendTransactionFails.BlockchainSdkError(
|
||||
error = error,
|
||||
scanResponse = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
.selectedUserWalletSync?.scanResponse
|
||||
?: error("ScanResponse must be not null"),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package com.tangem.tap.features.send.redux.reducers
|
||||
|
||||
import com.tangem.common.Strings.STARS
|
||||
import com.tangem.common.extensions.isZero
|
||||
import com.tangem.tap.common.extensions.stripZeroPlainString
|
||||
import com.tangem.tap.features.send.redux.AmountAction
|
||||
|
|
@ -11,6 +10,7 @@ import com.tangem.tap.features.send.redux.states.AmountState
|
|||
import com.tangem.tap.features.send.redux.states.InputViewValue
|
||||
import com.tangem.tap.features.send.redux.states.MainCurrencyType
|
||||
import com.tangem.tap.features.send.redux.states.SendState
|
||||
import com.tangem.utils.StringsSigns.STARS
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import com.tangem.tap.features.send.redux.ReceiptAction.RefreshReceipt
|
|||
import com.tangem.tap.features.send.redux.SendScreenAction
|
||||
import com.tangem.tap.features.send.redux.states.*
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.StringsSigns.LOWER_SIGN
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
|
|
@ -401,11 +402,7 @@ class ReceiptReducer : SendInternalReducer {
|
|||
}
|
||||
|
||||
private fun String.addPrecisionSign(): String {
|
||||
val result = if (feeState.feeIsApproximate) "$CAN_BE_LOWER_SIGN $this" else this
|
||||
val result = if (feeState.feeIsApproximate) "$LOWER_SIGN $this" else this
|
||||
return result.trim()
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val CAN_BE_LOWER_SIGN = "<"
|
||||
}
|
||||
}
|
||||
|
|
@ -4,8 +4,8 @@ import com.tangem.blockchain.common.Amount
|
|||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.common.extensions.isZero
|
||||
import com.tangem.core.navigation.StateDialog
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.redux.StateDialog
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.tap.common.CurrencyConverter
|
||||
import com.tangem.tap.common.entities.IndeterminateProgressButton
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import android.text.method.DigitsKeyListener
|
|||
import android.view.View
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.EditText
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.postDelayed
|
||||
import androidx.core.widget.addTextChangedListener
|
||||
import androidx.fragment.app.viewModels
|
||||
|
|
@ -20,19 +19,18 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
import com.google.android.material.textfield.TextInputEditText
|
||||
import com.tangem.Message
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.qrscanning.models.SourceType
|
||||
import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase
|
||||
import com.tangem.domain.qrscanning.usecases.ParseQrCodeUseCase
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.feature.qrscanning.QrScanningRouter
|
||||
import com.tangem.sdk.extensions.hideSoftKeyboard
|
||||
import com.tangem.tap.common.KeyboardObserver
|
||||
import com.tangem.tap.common.analytics.events.Token
|
||||
import com.tangem.tap.common.extensions.dispatchOnMain
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.common.extensions.getFromClipboard
|
||||
import com.tangem.tap.common.extensions.setOnImeActionListener
|
||||
import com.tangem.tap.common.recyclerView.SpaceItemDecoration
|
||||
|
|
@ -162,14 +160,9 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
|||
imvQrCode.setOnClickListener {
|
||||
Analytics.send(Token.Send.ButtonQRCode())
|
||||
|
||||
store.dispatchOnMain(
|
||||
NavigationAction.NavigateTo(
|
||||
screen = AppScreen.QrScanning,
|
||||
bundle = bundleOf(
|
||||
QrScanningRouter.SOURCE_KEY to SourceType.SEND,
|
||||
),
|
||||
),
|
||||
)
|
||||
store.dispatchNavigationAction {
|
||||
push(AppRoute.QrScanning(source = SourceType.SEND))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -370,7 +363,7 @@ class SendFragment : BaseStoreFragment(R.layout.fragment_send) {
|
|||
override fun handleOnBackPressed() {
|
||||
val externalTransactionData = store.state.sendState.externalTransactionData
|
||||
if (externalTransactionData == null) {
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
store.dispatchNavigationAction(AppRouter::pop)
|
||||
} else {
|
||||
store.dispatch(TradeCryptoAction.FinishSelling(externalTransactionData.transactionId))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.tap.features.send.ui
|
|||
|
||||
import androidx.lifecycle.*
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.qrscanning.models.SourceType
|
||||
import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase
|
||||
|
|
@ -12,7 +13,6 @@ import com.tangem.domain.tokens.model.CryptoCurrency
|
|||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.features.send.api.navigation.SendRouter
|
||||
import com.tangem.tap.common.analytics.events.Token
|
||||
import com.tangem.tap.di.DelayedWork
|
||||
import com.tangem.tap.features.send.redux.AddressActionUi
|
||||
|
|
@ -52,7 +52,7 @@ internal class SendViewModel @Inject constructor(
|
|||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
private val cryptoCurrency: CryptoCurrency? = savedStateHandle[SendRouter.CRYPTO_CURRENCY_KEY]
|
||||
private val cryptoCurrency: CryptoCurrency? = savedStateHandle[AppRoute.Send.CRYPTO_CURRENCY_KEY]
|
||||
|
||||
override fun onCreate(owner: LifecycleOwner) {
|
||||
getBalanceHidingSettingsUseCase()
|
||||
|
|
|
|||
|
|
@ -23,7 +23,12 @@ object RequestFeeErrorDialog {
|
|||
setMessage(context.getString(R.string.alert_failed_to_send_transaction_message, errorMessage))
|
||||
setNegativeButton(R.string.details_row_title_contact_to_support) { _, _ ->
|
||||
Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Send))
|
||||
store.dispatch(GlobalAction.SendEmail(SendTransactionFailedEmail(errorMessage)))
|
||||
store.dispatch(
|
||||
GlobalAction.SendEmail(
|
||||
feedbackData = SendTransactionFailedEmail(errorMessage),
|
||||
scanResponse = dialog.scanResponse,
|
||||
),
|
||||
)
|
||||
}
|
||||
setPositiveButton(R.string.common_retry) { _, _ -> dialog.onRetry() }
|
||||
setNeutralButton(R.string.common_cancel) { _, _ -> }
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import com.tangem.common.module.ModuleMessageConverter
|
|||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.sdk.extensions.localizedDescription
|
||||
import com.tangem.tap.common.extensions.stripZeroPlainString
|
||||
import com.tangem.tap.common.feedback.SendTransactionFailedEmail
|
||||
|
|
@ -21,21 +22,21 @@ import com.tangem.wallet.R
|
|||
*/
|
||||
object SendTransactionFailsDialog {
|
||||
fun create(context: Context, dialog: SendAction.Dialog.SendTransactionFails.CardSdkError): AlertDialog {
|
||||
return create(context, dialog.error.localizedDescription(context))
|
||||
return create(context, dialog.error.localizedDescription(context), dialog.scanResponse)
|
||||
}
|
||||
|
||||
fun create(context: Context, dialog: SendAction.Dialog.SendTransactionFails.BlockchainSdkError): AlertDialog {
|
||||
val errorConverter = BlockchainSdkErrorConverter(context)
|
||||
return create(context, errorConverter.convert(dialog.error))
|
||||
return create(context, errorConverter.convert(dialog.error), dialog.scanResponse)
|
||||
}
|
||||
|
||||
private fun create(context: Context, errorMessage: String): AlertDialog {
|
||||
private fun create(context: Context, errorMessage: String, scanResponse: ScanResponse): AlertDialog {
|
||||
return AlertDialog.Builder(context).apply {
|
||||
setTitle(R.string.alert_failed_to_send_transaction_title)
|
||||
setMessage(context.getString(R.string.alert_failed_to_send_transaction_message, errorMessage))
|
||||
setNeutralButton(R.string.details_row_title_contact_to_support) { _, _ ->
|
||||
Analytics.send(Basic.ButtonSupport(AnalyticsParam.ScreensSources.Send))
|
||||
store.dispatch(GlobalAction.SendEmail(SendTransactionFailedEmail(errorMessage)))
|
||||
store.dispatch(GlobalAction.SendEmail(SendTransactionFailedEmail(errorMessage), scanResponse))
|
||||
}
|
||||
setPositiveButton(R.string.common_cancel) { _, _ -> }
|
||||
setOnDismissListener { store.dispatch(SendAction.Dialog.Hide) }
|
||||
|
|
|
|||
|
|
@ -1,13 +1,10 @@
|
|||
package com.tangem.tap.features.tokens.impl.presentation
|
||||
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.components.SystemBarsEffect
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import com.tangem.tap.features.tokens.impl.presentation.ui.TokensListScreen
|
||||
import com.tangem.tap.features.tokens.impl.presentation.viewmodels.TokensListViewModel
|
||||
|
|
@ -30,12 +27,7 @@ internal class TokensListFragment : ComposeFragment() {
|
|||
val viewModel = hiltViewModel<TokensListViewModel>().apply {
|
||||
LocalLifecycleOwner.current.lifecycle.addObserver(this)
|
||||
}
|
||||
val statusBarColor = TangemTheme.colors.background.secondary
|
||||
SystemBarsEffect {
|
||||
setSystemBarsColor(color = statusBarColor)
|
||||
}
|
||||
TokensListScreen(
|
||||
modifier = Modifier.systemBarsPadding(),
|
||||
stateHolder = viewModel.uiState,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
package com.tangem.tap.features.tokens.impl.presentation.router
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.common.extensions.dispatchNotification
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -18,11 +20,11 @@ import com.tangem.wallet.R
|
|||
internal class DefaultTokensListRouter : TokensListRouter {
|
||||
|
||||
override fun popBackStack() {
|
||||
store.dispatch(NavigationAction.PopBackTo())
|
||||
store.dispatchNavigationAction(AppRouter::pop)
|
||||
}
|
||||
|
||||
override fun openAddCustomTokenScreen() {
|
||||
store.dispatch(NavigationAction.NavigateTo(AppScreen.AddCustomToken))
|
||||
store.dispatchNavigationAction { push(AppRoute.AddCustomToken) }
|
||||
}
|
||||
|
||||
override fun showAddressCopiedNotification() {
|
||||
|
|
@ -58,7 +60,7 @@ internal class DefaultTokensListRouter : TokensListRouter {
|
|||
val alert = AppDialog.SimpleOkDialogRes(
|
||||
headerId = R.string.common_error,
|
||||
messageId = R.string.common_unknown_error,
|
||||
onOk = { store.dispatch(NavigationAction.PopBackTo()) },
|
||||
onOk = { store.dispatchNavigationAction(AppRouter::pop) },
|
||||
)
|
||||
store.dispatchDialogShow(alert)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import androidx.compose.material.Scaffold
|
|||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
|
|
@ -34,6 +33,8 @@ import androidx.paging.compose.LazyPagingItems
|
|||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import androidx.paging.compose.itemContentType
|
||||
import androidx.paging.compose.itemKey
|
||||
import com.tangem.core.ui.components.BottomFade
|
||||
import com.tangem.core.ui.components.NavigationBar3ButtonsScrim
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
|
@ -61,32 +62,45 @@ internal fun TokensListScreen(stateHolder: TokensListStateHolder, modifier: Modi
|
|||
|
||||
Scaffold(
|
||||
modifier = modifier,
|
||||
topBar = { TokensListToolbar(state = stateHolder.toolbarState) },
|
||||
topBar = {
|
||||
TokensListToolbar(state = stateHolder.toolbarState)
|
||||
},
|
||||
floatingActionButton = {
|
||||
if (stateHolder is TokensListStateHolder.ManageContent) {
|
||||
val density = LocalDensity.current
|
||||
val verticalPadding = TangemTheme.dimens.spacing32
|
||||
|
||||
SaveChangesButton(
|
||||
modifier = Modifier.onSizeChanged {
|
||||
with(density) { floatingButtonHeight = it.height.toDp() + verticalPadding }
|
||||
},
|
||||
modifier = Modifier
|
||||
.navigationBarsPadding()
|
||||
.onSizeChanged {
|
||||
with(density) { floatingButtonHeight = it.height.toDp() + verticalPadding }
|
||||
},
|
||||
showProgress = stateHolder.isSavingInProgress,
|
||||
onClick = stateHolder.onSaveButtonClick,
|
||||
)
|
||||
}
|
||||
},
|
||||
floatingActionButtonPosition = FabPosition.Center,
|
||||
backgroundColor = TangemTheme.colors.background.primary,
|
||||
) { scaffoldPadding ->
|
||||
backgroundColor = TangemTheme.colors.background.secondary,
|
||||
) { _ ->
|
||||
val tokens = stateHolder.tokens.collectAsLazyPagingItems()
|
||||
|
||||
TokensListContent(
|
||||
isDifferentAddressesBlockVisible = stateHolder.isDifferentAddressesBlockVisible,
|
||||
tokens = tokens,
|
||||
scaffoldPadding = scaffoldPadding,
|
||||
bottomMarginDp = floatingButtonHeight,
|
||||
)
|
||||
if (stateHolder !is TokensListStateHolder.ManageContent) {
|
||||
NavigationBar3ButtonsScrim()
|
||||
}
|
||||
|
||||
Box {
|
||||
TokensListContent(
|
||||
isDifferentAddressesBlockVisible = stateHolder.isDifferentAddressesBlockVisible,
|
||||
tokens = tokens,
|
||||
bottomMarginDp = floatingButtonHeight,
|
||||
)
|
||||
|
||||
if (stateHolder is TokensListStateHolder.ManageContent) {
|
||||
BottomFade(Modifier.align(Alignment.BottomCenter))
|
||||
}
|
||||
}
|
||||
|
||||
Crossfade(targetState = stateHolder.isLoading, label = "Update progress bar visibility") {
|
||||
if (it) {
|
||||
|
|
@ -112,23 +126,24 @@ private fun LoadingContent() {
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
private fun TokensListContent(
|
||||
isDifferentAddressesBlockVisible: Boolean,
|
||||
tokens: LazyPagingItems<TokenItemState>,
|
||||
scaffoldPadding: PaddingValues,
|
||||
bottomMarginDp: Dp,
|
||||
) {
|
||||
val state = rememberLazyListState()
|
||||
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.background(color = TangemTheme.colors.background.primary)
|
||||
.imePadding()
|
||||
.fillMaxSize()
|
||||
.padding(scaffoldPadding),
|
||||
.fillMaxSize(),
|
||||
state = state,
|
||||
contentPadding = PaddingValues(bottom = bottomMarginDp),
|
||||
contentPadding = PaddingValues(
|
||||
bottom = bottomMarginDp + bottomBarHeight,
|
||||
),
|
||||
) {
|
||||
item(
|
||||
key = "DifferentAddressesWarning$isDifferentAddressesBlockVisible",
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
package com.tangem.tap.features.tokens.impl.presentation.ui
|
||||
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
|
|
@ -16,6 +13,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
|
@ -42,8 +40,15 @@ internal fun TokensListToolbar(state: TokensListToolbarState) {
|
|||
} else {
|
||||
AppBarDefaults.TopAppBarElevation
|
||||
}
|
||||
val statusBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getTop(this).toDp() }
|
||||
|
||||
TopAppBar(
|
||||
backgroundColor = TangemTheme.colors.background.secondary,
|
||||
contentPadding = PaddingValues(
|
||||
start = TangemTheme.dimens.spacing4,
|
||||
end = TangemTheme.dimens.spacing4,
|
||||
top = statusBarHeight,
|
||||
),
|
||||
elevation = toolbarElevation,
|
||||
) {
|
||||
IconButton(onClick = state.onBackButtonClick) {
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ package com.tangem.tap.features.tokens.impl.presentation.viewmodels
|
|||
import arrow.core.Either
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.utils.popTo
|
||||
import com.tangem.data.tokens.utils.CryptoCurrencyFactory
|
||||
import com.tangem.domain.card.DerivePublicKeysUseCase
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
|
|
@ -16,7 +16,7 @@ import com.tangem.domain.wallets.models.UserWallet
|
|||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
|
||||
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.store
|
||||
|
|
@ -126,7 +126,7 @@ internal class TokensListMigration(
|
|||
val isNothingToDoWithBlockchain = blockchainsToAdd.isEmpty() && blockchainsToRemove.isEmpty()
|
||||
if (isNothingToDoWithTokens && isNothingToDoWithBlockchain) {
|
||||
store.dispatchDebugErrorNotification(message = "Nothing to save")
|
||||
store.dispatchWithMain(NavigationAction.PopBackTo(screen = AppScreen.Wallet))
|
||||
store.dispatchNavigationAction { popTo<AppRoute.Wallet>() }
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -135,6 +135,7 @@ internal class TokensListMigration(
|
|||
derivePublicKeysUseCase(userWalletId = currentUserWallet.walletId, currencies = currencyList)
|
||||
.onRight {
|
||||
addCryptoCurrenciesUseCase(userWalletId = currentUserWallet.walletId, currencies = currencyList)
|
||||
store.dispatchNavigationAction { popTo<AppRoute.Wallet>() }
|
||||
}
|
||||
.onLeft { Timber.e(it, "Failed to derive public keys") }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,8 +12,6 @@ import androidx.paging.*
|
|||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.core.ui.extensions.getActiveIconRes
|
||||
import com.tangem.core.ui.extensions.getGreyedOutIconRes
|
||||
import com.tangem.domain.card.DerivePublicKeysUseCase
|
||||
|
|
@ -26,7 +24,6 @@ import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase
|
|||
import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase
|
||||
import com.tangem.domain.tokens.TokenWithBlockchain
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||
import com.tangem.tap.common.extensions.fullNameWithoutTestnet
|
||||
import com.tangem.tap.common.extensions.getNetworkName
|
||||
import com.tangem.tap.features.customtoken.impl.presentation.models.SupportBlockchainType
|
||||
|
|
@ -326,7 +323,6 @@ internal class TokensListViewModel @Inject constructor(
|
|||
)
|
||||
|
||||
uiState = state.copy(isSavingInProgress = false)
|
||||
store.dispatchWithMain(NavigationAction.PopBackTo(screen = AppScreen.Wallet))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,17 @@
|
|||
package com.tangem.tap.features.wallet.redux.middlewares
|
||||
|
||||
import androidx.core.os.bundleOf
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
|
||||
import com.tangem.blockchain.common.AmountType
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.NetworkAddress
|
||||
import com.tangem.feature.swap.presentation.SwapFragment
|
||||
import com.tangem.features.send.api.navigation.SendRouter
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.tap.common.analytics.events.Token
|
||||
import com.tangem.tap.common.apptheme.MutableAppThemeModeHolder
|
||||
import com.tangem.tap.common.extensions.*
|
||||
|
|
@ -58,6 +57,11 @@ object TradeCryptoMiddleware {
|
|||
is TradeCryptoAction.Buy -> proceedBuyAction(state, action)
|
||||
is TradeCryptoAction.Sell -> proceedSellAction(action)
|
||||
is TradeCryptoAction.Swap -> openSwap(currency = action.cryptoCurrency)
|
||||
is TradeCryptoAction.Stake -> openStaking(
|
||||
userWalletId = action.userWalletId,
|
||||
cryptoCurrencyId = action.cryptoCurrencyId,
|
||||
yield = action.yield,
|
||||
)
|
||||
is TradeCryptoAction.SendToken -> {
|
||||
if (isSendRedesignedEnabled) {
|
||||
handleNewSendToken(action = action)
|
||||
|
|
@ -150,7 +154,7 @@ object TradeCryptoMiddleware {
|
|||
}
|
||||
|
||||
private fun openReceiptUrl(transactionId: String) {
|
||||
store.dispatchOnMain(NavigationAction.PopBackTo())
|
||||
store.dispatchNavigationAction(AppRouter::pop)
|
||||
store.state.globalState.exchangeManager.getSellCryptoReceiptUrl(
|
||||
action = CurrencyExchangeManager.Action.Sell,
|
||||
transactionId = transactionId,
|
||||
|
|
@ -158,11 +162,19 @@ object TradeCryptoMiddleware {
|
|||
}
|
||||
|
||||
private fun openSwap(currency: CryptoCurrency) {
|
||||
val bundle = bundleOf(
|
||||
SwapFragment.CURRENCY_BUNDLE_KEY to currency,
|
||||
)
|
||||
store.dispatchNavigationAction { push(AppRoute.Swap(currency = currency)) }
|
||||
}
|
||||
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(screen = AppScreen.Swap, bundle = bundle))
|
||||
private fun openStaking(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID, yield: Yield) {
|
||||
store.dispatchNavigationAction {
|
||||
push(
|
||||
AppRoute.Staking(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrencyId = cryptoCurrencyId,
|
||||
yield = yield,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSendToken(action: TradeCryptoAction.SendToken) {
|
||||
|
|
@ -214,11 +226,12 @@ object TradeCryptoMiddleware {
|
|||
)
|
||||
}
|
||||
|
||||
val bundle = bundleOf(
|
||||
SendRouter.CRYPTO_CURRENCY_KEY to currency,
|
||||
SendRouter.USER_WALLET_ID_KEY to action.userWallet.walletId.stringValue,
|
||||
val route = AppRoute.Send(
|
||||
currency = currency,
|
||||
userWalletId = action.userWallet.walletId,
|
||||
)
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(screen = AppScreen.Send, bundle = bundle))
|
||||
|
||||
store.dispatchNavigationAction { push(route) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -285,17 +298,17 @@ object TradeCryptoMiddleware {
|
|||
)
|
||||
}
|
||||
|
||||
val bundle = bundleOf(
|
||||
SendRouter.CRYPTO_CURRENCY_KEY to currency,
|
||||
SendRouter.USER_WALLET_ID_KEY to action.userWallet.walletId.stringValue,
|
||||
val route = AppRoute.Send(
|
||||
currency = currency,
|
||||
userWalletId = action.userWallet.walletId,
|
||||
)
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(screen = AppScreen.Send, bundle = bundle))
|
||||
store.dispatchNavigationAction { push(route) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleNewSendToken(action: TradeCryptoAction.SendToken) {
|
||||
handleNewSend(
|
||||
userWalletId = action.userWallet.walletId.stringValue,
|
||||
userWalletId = action.userWallet.walletId,
|
||||
txInfo = action.transactionInfo,
|
||||
currency = action.tokenCurrency,
|
||||
)
|
||||
|
|
@ -303,25 +316,26 @@ object TradeCryptoMiddleware {
|
|||
|
||||
private fun handleNewSendCoin(action: TradeCryptoAction.SendCoin) {
|
||||
handleNewSend(
|
||||
userWalletId = action.userWallet.walletId.stringValue,
|
||||
userWalletId = action.userWallet.walletId,
|
||||
txInfo = action.transactionInfo,
|
||||
currency = action.coinStatus.currency,
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleNewSend(
|
||||
userWalletId: String,
|
||||
userWalletId: UserWalletId,
|
||||
txInfo: TradeCryptoAction.TransactionInfo?,
|
||||
currency: CryptoCurrency,
|
||||
) {
|
||||
val bundle = bundleOf(
|
||||
SendRouter.CRYPTO_CURRENCY_KEY to currency,
|
||||
SendRouter.USER_WALLET_ID_KEY to userWalletId,
|
||||
SendRouter.TRANSACTION_ID_KEY to txInfo?.transactionId,
|
||||
SendRouter.DESTINATION_ADDRESS_KEY to txInfo?.destinationAddress,
|
||||
SendRouter.AMOUNT_KEY to txInfo?.amount,
|
||||
SendRouter.TAG_KEY to txInfo?.tag,
|
||||
val route = AppRoute.Send(
|
||||
currency = currency,
|
||||
userWalletId = userWalletId,
|
||||
transactionId = txInfo?.transactionId,
|
||||
destinationAddress = txInfo?.destinationAddress,
|
||||
amount = txInfo?.amount,
|
||||
tag = txInfo?.tag,
|
||||
)
|
||||
store.dispatchOnMain(NavigationAction.NavigateTo(screen = AppScreen.Send, bundle = bundle))
|
||||
|
||||
store.dispatchNavigationAction { push(route) }
|
||||
}
|
||||
}
|
||||
|
|
@ -6,11 +6,12 @@ import com.tangem.common.doOnFailure
|
|||
import com.tangem.common.doOnResult
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.common.flatMap
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.utils.popTo
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||
|
|
@ -98,7 +99,7 @@ internal class WelcomeMiddleware {
|
|||
signInType = Basic.SignedIn.SignInType.Biometric,
|
||||
)
|
||||
|
||||
store.dispatchWithMain(NavigationAction.NavigateTo(AppScreen.Wallet))
|
||||
store.dispatchNavigationAction { push(AppRoute.Wallet) }
|
||||
store.dispatchWithMain(WelcomeAction.ProceedWithBiometrics.Success)
|
||||
store.onUserWalletSelected(userWallet = selectedUserWallet)
|
||||
|
||||
|
|
@ -130,7 +131,7 @@ internal class WelcomeMiddleware {
|
|||
.doOnSuccess {
|
||||
sendSignedInAnalyticsEvent(scanResponse = scanResponse, signInType = Basic.SignedIn.SignInType.Card)
|
||||
|
||||
store.dispatchWithMain(NavigationAction.NavigateTo(AppScreen.Wallet))
|
||||
store.dispatchNavigationAction { push(AppRoute.Wallet) }
|
||||
store.dispatchWithMain(WelcomeAction.ProceedWithCard.Success)
|
||||
store.onUserWalletSelected(userWallet = userWallet)
|
||||
|
||||
|
|
@ -170,7 +171,7 @@ internal class WelcomeMiddleware {
|
|||
}
|
||||
.doOnResult {
|
||||
store.dispatchWithMain(WelcomeAction.CloseError)
|
||||
store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Home))
|
||||
store.dispatchNavigationAction { popTo<AppRoute.Home>() }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
|||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.components.SystemBarsEffect
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.screen.ComposeFragment
|
||||
import com.tangem.tap.common.analytics.events.SignIn
|
||||
|
|
@ -49,19 +48,14 @@ internal class WelcomeFragment : ComposeFragment() {
|
|||
val errorMessage by rememberUpdatedState(newValue = state.error?.resolveReference())
|
||||
val warning by rememberUpdatedState(newValue = state.warning)
|
||||
|
||||
val backgroundColor = TangemTheme.colors.background.primary
|
||||
SystemBarsEffect {
|
||||
setSystemBarsColor(backgroundColor)
|
||||
}
|
||||
|
||||
BackHandler {
|
||||
requireActivity().finish()
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.systemBarsPadding()
|
||||
.background(backgroundColor),
|
||||
.background(TangemTheme.colors.background.primary)
|
||||
.systemBarsPadding(),
|
||||
) {
|
||||
WelcomeScreenContent(
|
||||
showUnlockProgress = state.showUnlockWithBiometricsProgress,
|
||||
|
|
@ -88,8 +82,4 @@ internal class WelcomeFragment : ComposeFragment() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal companion object {
|
||||
const val INITIAL_INTENT_KEY = "intent"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,11 @@
|
|||
package com.tangem.tap.features.welcome.ui
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.lifecycle.*
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.bundle.unbundle
|
||||
import com.tangem.common.routing.entity.SerializableIntent
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListError
|
||||
import com.tangem.tap.common.analytics.events.SignIn
|
||||
|
|
@ -26,7 +29,8 @@ internal class WelcomeViewModel @Inject constructor(
|
|||
StoreSubscriber<WelcomeState>,
|
||||
DefaultLifecycleObserver {
|
||||
|
||||
private val initialIntent: Intent? = savedStateHandle[WelcomeFragment.INITIAL_INTENT_KEY]
|
||||
private val initialIntent: SerializableIntent? = savedStateHandle.get<Bundle>(AppRoute.Welcome.INITIAL_INTENT_KEY)
|
||||
?.unbundle(SerializableIntent.serializer())
|
||||
|
||||
private val stateInternal = MutableStateFlow(WelcomeScreenState())
|
||||
val state: StateFlow<WelcomeScreenState> = stateInternal
|
||||
|
|
@ -38,7 +42,7 @@ internal class WelcomeViewModel @Inject constructor(
|
|||
initGlobalState()
|
||||
|
||||
val welcomeAction = if (initialIntent != null) {
|
||||
WelcomeAction.ProceedWithIntent(initialIntent)
|
||||
WelcomeAction.ProceedWithIntent(initialIntent.toIntent())
|
||||
} else {
|
||||
WelcomeAction.ProceedWithBiometrics()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue