Updated on 2026-08-14
This commit is contained in:
commit
d994b7840a
150 changed files with 971 additions and 4621 deletions
|
|
@ -40,9 +40,7 @@ import com.tangem.domain.settings.repositories.SettingsRepository
|
|||
import com.tangem.domain.walletconnect.usecase.initialize.WcInitializeUseCase
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
||||
import com.tangem.hot.sdk.TangemHotSdk
|
||||
import com.tangem.tap.common.analytics.handlers.BlockchainExceptionHandler
|
||||
|
|
@ -92,8 +90,6 @@ interface ApplicationEntryPoint {
|
|||
|
||||
fun getOneTimeEventFilter(): OneTimeEventFilter
|
||||
|
||||
fun getGeneralUserWalletsListManager(): UserWalletsListManager
|
||||
|
||||
fun getWasTwinsOnboardingShownUseCase(): WasTwinsOnboardingShownUseCase
|
||||
|
||||
fun getSaveTwinsOnboardingShownUseCase(): SaveTwinsOnboardingShownUseCase
|
||||
|
|
@ -151,8 +147,6 @@ interface ApplicationEntryPoint {
|
|||
|
||||
fun getTangemHotSdk(): TangemHotSdk
|
||||
|
||||
fun getHotWalletFeatureToggles(): HotWalletFeatureToggles
|
||||
|
||||
fun getWcInitializeUseCase(): WcInitializeUseCase
|
||||
|
||||
fun getTrackingContextProxy(): TrackingContextProxy
|
||||
|
|
|
|||
|
|
@ -6,9 +6,6 @@ import androidx.work.CoroutineWorker
|
|||
import androidx.work.WorkerParameters
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.legacy.asLockable
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import timber.log.Timber
|
||||
|
|
@ -18,21 +15,12 @@ class LockTimerWorker @AssistedInject constructor(
|
|||
@Assisted context: Context,
|
||||
@Assisted params: WorkerParameters,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val userWalletsListManager: UserWalletsListManager,
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
) : CoroutineWorker(context, params) {
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
Timber.i("onStart job")
|
||||
if (hotWalletFeatureToggles.isHotWalletEnabled) {
|
||||
userWalletsListRepository.lockAllWallets()
|
||||
.onRight {
|
||||
settingsRepository.setShouldOpenWelcomeScreenOnResume(value = true)
|
||||
}
|
||||
} else {
|
||||
val userWalletsListManagerLockable = userWalletsListManager.asLockable() ?: return Result.failure()
|
||||
userWalletsListManagerLockable.lock()
|
||||
userWalletsListRepository.lockAllWallets().onRight {
|
||||
settingsRepository.setShouldOpenWelcomeScreenOnResume(value = true)
|
||||
}
|
||||
Timber.i("onStart job complete")
|
||||
|
|
|
|||
|
|
@ -9,10 +9,7 @@ import androidx.work.WorkManager
|
|||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.legacy.asLockable
|
||||
import com.tangem.domain.wallets.usecase.ClearAllHotWalletContextualUnlockUseCase
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import com.tangem.tap.LockTimerWorker.Companion.TAG
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
|
@ -22,16 +19,13 @@ import kotlinx.coroutines.launch
|
|||
import timber.log.Timber
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class LockUserWalletsTimer(
|
||||
private val context: Context,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val duration: Duration = with(Duration) { 5.minutes },
|
||||
private val userWalletsListManager: UserWalletsListManager,
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
private val coroutineScope: CoroutineScope,
|
||||
private val clearAllHotWalletContextualUnlockUseCase: ClearAllHotWalletContextualUnlockUseCase,
|
||||
) : LifecycleOwner by context as LifecycleOwner,
|
||||
|
|
@ -59,9 +53,7 @@ internal class LockUserWalletsTimer(
|
|||
)
|
||||
|
||||
if (shouldOpenWelcomeScreenOnResume) {
|
||||
if (hotWalletFeatureToggles.isHotWalletEnabled) {
|
||||
clearAllHotWalletContextualUnlockUseCase.invoke()
|
||||
}
|
||||
clearAllHotWalletContextualUnlockUseCase.invoke()
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Welcome()) }
|
||||
settingsRepository.setShouldOpenWelcomeScreenOnResume(value = false)
|
||||
}
|
||||
|
|
@ -115,38 +107,18 @@ internal class LockUserWalletsTimer(
|
|||
* This job used only when app is foreground when background use [JobScheduler]
|
||||
*/
|
||||
private fun createDelayJob(): Job = coroutineScope.launch {
|
||||
val startTime = System.currentTimeMillis()
|
||||
|
||||
delay(duration)
|
||||
|
||||
if (hotWalletFeatureToggles.isHotWalletEnabled) {
|
||||
val userWallets = userWalletsListRepository.userWalletsSync()
|
||||
if (userWallets.isNotEmpty()) {
|
||||
userWalletsListRepository.lockAllWallets()
|
||||
.onLeft {
|
||||
start()
|
||||
}
|
||||
.onRight {
|
||||
clearAllHotWalletContextualUnlockUseCase.invoke()
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Welcome()) }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val userWalletsListManager = userWalletsListManager.asLockable() ?: return@launch
|
||||
|
||||
if (userWalletsListManager.hasUserWallets) {
|
||||
val currentTime = System.currentTimeMillis()
|
||||
|
||||
Timber.i(
|
||||
"""
|
||||
Finished
|
||||
|- Millis passed: ${currentTime - startTime}
|
||||
""".trimIndent(),
|
||||
)
|
||||
|
||||
userWalletsListManager.lock()
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Welcome()) }
|
||||
}
|
||||
val userWallets = userWalletsListRepository.userWalletsSync()
|
||||
if (userWallets.isNotEmpty()) {
|
||||
userWalletsListRepository.lockAllWallets()
|
||||
.onLeft {
|
||||
start()
|
||||
}
|
||||
.onRight {
|
||||
clearAllHotWalletContextualUnlockUseCase.invoke()
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Welcome()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -43,9 +43,7 @@ import com.tangem.domain.settings.SetGoogleServicesAvailabilityUseCase
|
|||
import com.tangem.domain.settings.ShouldInitiallyAskPermissionUseCase
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.staking.SendUnsubmittedHashesUseCase
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.usecase.ClearAllHotWalletContextualUnlockUseCase
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import com.tangem.features.tangempay.TangemPayFeatureToggles
|
||||
import com.tangem.features.tester.api.TesterMenuLauncher
|
||||
import com.tangem.google.GoogleServicesHelper
|
||||
|
|
@ -114,9 +112,6 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
|||
@Inject
|
||||
lateinit var analyticsEventsHandler: AnalyticsEventHandler
|
||||
|
||||
@Inject
|
||||
lateinit var userWalletsListManager: UserWalletsListManager
|
||||
|
||||
@Inject
|
||||
@RootAppComponentContext
|
||||
internal lateinit var rootComponentContext: AppComponentContext
|
||||
|
|
@ -163,9 +158,6 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
|||
@Inject
|
||||
internal lateinit var userWalletsListRepository: UserWalletsListRepository
|
||||
|
||||
@Inject
|
||||
internal lateinit var hotWalletFeatureToggles: HotWalletFeatureToggles
|
||||
|
||||
@Inject
|
||||
internal lateinit var clearAllHotWalletContextualUnlockUseCase: ClearAllHotWalletContextualUnlockUseCase
|
||||
|
||||
|
|
@ -258,10 +250,8 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
|||
lockUserWalletsTimer = LockUserWalletsTimer(
|
||||
context = this,
|
||||
settingsRepository = settingsRepository,
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
coroutineScope = mainScope,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
hotWalletFeatureToggles = hotWalletFeatureToggles,
|
||||
clearAllHotWalletContextualUnlockUseCase = clearAllHotWalletContextualUnlockUseCase,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -58,7 +58,6 @@ import com.tangem.domain.onboarding.repository.OnboardingRepository
|
|||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
||||
import com.tangem.operations.attestation.api.TangemApiServiceSettings
|
||||
|
|
@ -146,9 +145,6 @@ open class TangemApplication : Application(), ImageLoaderFactory, Configuration.
|
|||
private val oneTimeEventFilter: OneTimeEventFilter
|
||||
get() = entryPoint.getOneTimeEventFilter()
|
||||
|
||||
private val generalUserWalletsListManager: UserWalletsListManager
|
||||
get() = entryPoint.getGeneralUserWalletsListManager()
|
||||
|
||||
private val wasTwinsOnboardingShownUseCase: WasTwinsOnboardingShownUseCase
|
||||
get() = entryPoint.getWasTwinsOnboardingShownUseCase()
|
||||
|
||||
|
|
@ -238,9 +234,6 @@ open class TangemApplication : Application(), ImageLoaderFactory, Configuration.
|
|||
private val tangemHotSdk
|
||||
get() = entryPoint.getTangemHotSdk()
|
||||
|
||||
private val hotWalletFeatureToggles
|
||||
get() = entryPoint.getHotWalletFeatureToggles()
|
||||
|
||||
private val wcInitializeUseCase
|
||||
get() = entryPoint.getWcInitializeUseCase()
|
||||
|
||||
|
|
@ -382,7 +375,6 @@ open class TangemApplication : Application(), ImageLoaderFactory, Configuration.
|
|||
appThemeModeRepository = appThemeModeRepository,
|
||||
balanceHidingRepository = balanceHidingRepository,
|
||||
walletsRepository = walletsRepository,
|
||||
generalUserWalletsListManager = generalUserWalletsListManager,
|
||||
wasTwinsOnboardingShownUseCase = wasTwinsOnboardingShownUseCase,
|
||||
saveTwinsOnboardingShownUseCase = saveTwinsOnboardingShownUseCase,
|
||||
cardRepository = cardRepository,
|
||||
|
|
@ -407,7 +399,6 @@ open class TangemApplication : Application(), ImageLoaderFactory, Configuration.
|
|||
userTokensResponseStore = userTokensResponseStore,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
tangemHotSdk = tangemHotSdk,
|
||||
hotWalletFeatureToggles = hotWalletFeatureToggles,
|
||||
trackingContextProxy = trackingContextProxy,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.tangem.tap.common.redux
|
|||
|
||||
import com.tangem.tap.common.redux.global.globalReducer
|
||||
import com.tangem.tap.features.details.redux.DetailsReducer
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeReducer
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphReducer
|
||||
import org.rekotlin.Action
|
||||
|
||||
|
|
@ -12,7 +11,6 @@ fun appReducer(action: Action, state: AppState): AppState {
|
|||
return AppState(
|
||||
globalState = globalReducer(action, state),
|
||||
detailsState = DetailsReducer.reduce(action, state),
|
||||
welcomeState = WelcomeReducer.reduce(action, state),
|
||||
daggerGraphState = DaggerGraphReducer.reduce(action, state),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ import com.tangem.tap.features.details.redux.DetailsMiddleware
|
|||
import com.tangem.tap.features.details.redux.DetailsState
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupMiddleware
|
||||
import com.tangem.tap.features.wallet.redux.middlewares.TradeCryptoMiddleware
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeMiddleware
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeState
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphMiddleware
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import org.rekotlin.Middleware
|
||||
|
|
@ -17,7 +15,6 @@ import org.rekotlin.StateType
|
|||
data class AppState(
|
||||
val globalState: GlobalState = GlobalState(),
|
||||
val detailsState: DetailsState = DetailsState(),
|
||||
val welcomeState: WelcomeState = WelcomeState(),
|
||||
val daggerGraphState: DaggerGraphState = DaggerGraphState(),
|
||||
) : StateType {
|
||||
|
||||
|
|
@ -28,7 +25,6 @@ data class AppState(
|
|||
GlobalMiddleware.handler,
|
||||
DetailsMiddleware().detailsMiddleware,
|
||||
BackupMiddleware().backupMiddleware,
|
||||
WelcomeMiddleware().middleware,
|
||||
LockUserWalletsTimerMiddleware().middleware,
|
||||
AccessCodeRequestPolicyMiddleware().middleware,
|
||||
DaggerGraphMiddleware.daggerGraphMiddleware,
|
||||
|
|
|
|||
|
|
@ -27,8 +27,6 @@ internal object LegacyMiddleware {
|
|||
{ action ->
|
||||
when (action) {
|
||||
is LegacyAction.PrepareDetailsScreen -> {
|
||||
val walletsRepository = store.inject(DaggerGraphState::walletsRepository)
|
||||
|
||||
selectedUserWallet()
|
||||
.distinctUntilChanged { old, new ->
|
||||
if (old is UserWallet.Cold && new is UserWallet.Cold) {
|
||||
|
|
@ -39,9 +37,7 @@ internal object LegacyMiddleware {
|
|||
}
|
||||
}
|
||||
.onEach { selectedUserWallet ->
|
||||
val initializedAppSettingsStateContent = initializeAppSettingsState(
|
||||
shouldSaveUserWallets = walletsRepository.shouldSaveUserWalletsSync(),
|
||||
)
|
||||
val initializedAppSettingsStateContent = initializeAppSettingsState()
|
||||
store.dispatchWithMain(
|
||||
DetailsAction.PrepareScreen(
|
||||
scanResponse = (selectedUserWallet as? UserWallet.Cold)?.scanResponse,
|
||||
|
|
@ -60,24 +56,15 @@ internal object LegacyMiddleware {
|
|||
}
|
||||
|
||||
private fun selectedUserWallet(): Flow<UserWallet> {
|
||||
val hotWalletFeatureToggles = store.inject(DaggerGraphState::hotWalletFeatureToggles)
|
||||
return if (hotWalletFeatureToggles.isHotWalletEnabled) {
|
||||
store.inject(DaggerGraphState::userWalletsListRepository).selectedUserWallet.filterNotNull()
|
||||
} else {
|
||||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
userWalletsListManager.selectedUserWallet
|
||||
}
|
||||
return store.inject(DaggerGraphState::userWalletsListRepository).selectedUserWallet.filterNotNull()
|
||||
}
|
||||
|
||||
/**
|
||||
* LEGACY: We need to initialize [AppSettingsState] async to avoid drawing blocking
|
||||
* previously it was initialized in runBlocking and blocked details screen
|
||||
*/
|
||||
private suspend fun initializeAppSettingsState(shouldSaveUserWallets: Boolean): AppSettingsState {
|
||||
private suspend fun initializeAppSettingsState(): AppSettingsState {
|
||||
return AppSettingsState(
|
||||
isBiometricsAvailable = tangemSdkManager.checkCanUseBiometry(),
|
||||
saveWallets = shouldSaveUserWallets,
|
||||
saveAccessCodes = store.inject(DaggerGraphState::settingsRepository).shouldSaveAccessCodes(),
|
||||
selectedAppCurrency = store.state.globalState.appCurrency,
|
||||
selectedThemeMode = store.inject(DaggerGraphState::appThemeModeRepository).getAppThemeMode().firstOrNull()
|
||||
?: AppThemeMode.DEFAULT,
|
||||
|
|
|
|||
|
|
@ -1,39 +0,0 @@
|
|||
package com.tangem.tap.data
|
||||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
// FIXME: Workaround, remove it once the normal UserWalletsStore has been implemented
|
||||
// [REDACTED_JIRA]
|
||||
internal class RuntimeUserWalletsStore(
|
||||
private val userWalletsListManager: UserWalletsListManager,
|
||||
) : UserWalletsStore {
|
||||
|
||||
override val selectedUserWalletOrNull: UserWallet?
|
||||
get() = userWalletsListManager.selectedUserWalletSync
|
||||
|
||||
override val userWallets: Flow<List<UserWallet>>
|
||||
get() = userWalletsListManager.userWallets
|
||||
|
||||
override val userWalletsSync: List<UserWallet>
|
||||
get() = userWalletsListManager.userWalletsSync
|
||||
|
||||
override fun getSyncOrNull(key: UserWalletId): UserWallet? {
|
||||
return userWalletsListManager.userWalletsSync.firstOrNull { it.walletId == key }
|
||||
}
|
||||
|
||||
override fun getSyncStrict(key: UserWalletId): UserWallet {
|
||||
return requireNotNull(getSyncOrNull(key)) { "Unable to find user wallet with provided ID: $key" }
|
||||
}
|
||||
|
||||
override suspend fun update(
|
||||
userWalletId: UserWalletId,
|
||||
update: suspend (UserWallet) -> UserWallet,
|
||||
): CompletionResult<UserWallet> {
|
||||
return userWalletsListManager.update(userWalletId, update)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,9 +2,6 @@ package com.tangem.tap.di.data
|
|||
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import com.tangem.tap.data.RuntimeUserWalletsStore
|
||||
import com.tangem.tap.data.UserWalletsStoreRepositoryProxy
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -18,15 +15,7 @@ internal object UserWalletsStoreModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideUserWalletsStore(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
): UserWalletsStore {
|
||||
return if (hotWalletFeatureToggles.isHotWalletEnabled) {
|
||||
UserWalletsStoreRepositoryProxy(userWalletsListRepository)
|
||||
} else {
|
||||
RuntimeUserWalletsStore(userWalletsListManager = userWalletsListManager)
|
||||
}
|
||||
fun provideUserWalletsStore(userWalletsListRepository: UserWalletsListRepository): UserWalletsStore {
|
||||
return UserWalletsStoreRepositoryProxy(userWalletsListRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -9,9 +9,7 @@ import com.tangem.domain.demo.IsDemoCardUseCase
|
|||
import com.tangem.domain.demo.models.DemoConfig
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.derivations.DerivationsRepository
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.usecase.*
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import com.tangem.sdk.api.TangemSdkManager
|
||||
import com.tangem.tap.domain.card.DefaultDeleteSavedAccessCodesUseCase
|
||||
import com.tangem.tap.domain.card.DefaultResetCardUseCase
|
||||
|
|
@ -42,16 +40,8 @@ internal object CardDomainModule {
|
|||
}
|
||||
|
||||
@Provides
|
||||
fun provideIsNeedToBackupUseCase(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
): IsNeedToBackupUseCase {
|
||||
return IsNeedToBackupUseCase(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
useNewRepository = hotWalletFeatureToggles.isHotWalletEnabled,
|
||||
)
|
||||
fun provideIsNeedToBackupUseCase(userWalletsListRepository: UserWalletsListRepository): IsNeedToBackupUseCase {
|
||||
return IsNeedToBackupUseCase(userWalletsListRepository = userWalletsListRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -3,9 +3,7 @@ package com.tangem.tap.di.domain
|
|||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.domain.card.ScanCardProcessor
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import com.tangem.tap.domain.scanCard.CardScanningFeatureToggles
|
||||
import com.tangem.tap.domain.scanCard.DefaultScanCardProcessor
|
||||
import com.tangem.tap.domain.scanCard.LegacyScanProcessor
|
||||
|
|
@ -34,14 +32,8 @@ internal object CardLegacyDomainModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun providesWalletNameGenerateUseCase(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
): GenerateWalletNameUseCase {
|
||||
return GenerateWalletNameUseCase(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
useNewRepository = hotWalletFeatureToggles.isHotWalletEnabled,
|
||||
)
|
||||
return GenerateWalletNameUseCase(userWalletsListRepository = userWalletsListRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -13,8 +13,6 @@ import com.tangem.domain.staking.multi.MultiStakingBalanceFetcher
|
|||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.derivations.DerivationsRepository
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -107,15 +105,11 @@ object MarketsDomainModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideFilterNetworksUseCase(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
): FilterAvailableNetworksForWalletUseCase {
|
||||
return FilterAvailableNetworksForWalletUseCase(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
shouldUseNewRepository = hotWalletFeatureToggles.isHotWalletEnabled,
|
||||
excludedBlockchains = excludedBlockchains,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import com.tangem.domain.wallets.delegate.DefaultUserWalletsSyncDelegate
|
|||
import com.tangem.domain.wallets.delegate.UserWalletsSyncDelegate
|
||||
import com.tangem.domain.wallets.derivations.DerivationsRepository
|
||||
import com.tangem.domain.wallets.hot.HotWalletAccessor
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.repository.WalletNamesMigrationRepository
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.domain.wallets.usecase.*
|
||||
|
|
@ -22,7 +21,6 @@ import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
|
|||
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyUpdateUseCase
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.IsWalletNFTEnabledSyncUseCase
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletNameMigrationUseCase
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import com.tangem.operations.attestation.CardArtworksProvider
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
|
|
@ -38,105 +36,61 @@ internal object WalletsDomainModule {
|
|||
|
||||
@Provides
|
||||
fun providesUserWalletsSyncDelegate(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): UserWalletsSyncDelegate {
|
||||
return DefaultUserWalletsSyncDelegate(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
useNewRepository = hotWalletFeatureToggles.isHotWalletEnabled,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
return DefaultUserWalletsSyncDelegate(userWalletsListRepository = userWalletsListRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesGetWalletsUseCase(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
): GetWalletsUseCase {
|
||||
return GetWalletsUseCase(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
useNewListRepository = hotWalletFeatureToggles.isHotWalletEnabled,
|
||||
)
|
||||
fun providesGetWalletsUseCase(userWalletsListRepository: UserWalletsListRepository): GetWalletsUseCase {
|
||||
return GetWalletsUseCase(userWalletsListRepository = userWalletsListRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesWalletNameMigrationUseCase(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
walletNamesMigrationRepository: WalletNamesMigrationRepository,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
): WalletNameMigrationUseCase {
|
||||
return WalletNameMigrationUseCase(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
walletNamesMigrationRepository = walletNamesMigrationRepository,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
useNewListRepository = hotWalletFeatureToggles.isHotWalletEnabled,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesGetUserWalletUseCase(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
): GetUserWalletUseCase {
|
||||
return GetUserWalletUseCase(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
useNewListRepository = hotWalletFeatureToggles.isHotWalletEnabled,
|
||||
)
|
||||
fun providesGetUserWalletUseCase(userWalletsListRepository: UserWalletsListRepository): GetUserWalletUseCase {
|
||||
return GetUserWalletUseCase(userWalletsListRepository = userWalletsListRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesGetSelectedWalletSyncUseCase(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
): GetSelectedWalletSyncUseCase {
|
||||
return GetSelectedWalletSyncUseCase(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
useNewRepository = hotWalletFeatureToggles.isHotWalletEnabled,
|
||||
)
|
||||
return GetSelectedWalletSyncUseCase(userWalletsListRepository = userWalletsListRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesGetSelectedWalletUseCase(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
): GetSelectedWalletUseCase {
|
||||
return GetSelectedWalletUseCase(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
useNewRepository = hotWalletFeatureToggles.isHotWalletEnabled,
|
||||
)
|
||||
return GetSelectedWalletUseCase(userWalletsListRepository = userWalletsListRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesSaveWalletUseCase(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
walletsRepository: WalletsRepository,
|
||||
analyticsEventHandler: AnalyticsEventHandler,
|
||||
): SaveWalletUseCase {
|
||||
return SaveWalletUseCase(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
walletsRepository = walletsRepository,
|
||||
useNewRepository = hotWalletFeatureToggles.isHotWalletEnabled,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
)
|
||||
}
|
||||
|
|
@ -144,15 +98,9 @@ internal object WalletsDomainModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun providesIsWalletAlreadySavedUseCase(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
): IsWalletAlreadySavedUseCase {
|
||||
return IsWalletAlreadySavedUseCase(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
useNewRepository = hotWalletFeatureToggles.isHotWalletEnabled,
|
||||
)
|
||||
return IsWalletAlreadySavedUseCase(userWalletsListRepository = userWalletsListRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
@ -167,12 +115,6 @@ internal object WalletsDomainModule {
|
|||
return GetExploreUrlUseCase(walletsManagersFacade = walletsManagersFacade)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesUnlockWalletsUseCase(userWalletsListManager: UserWalletsListManager): UnlockWalletsUseCase {
|
||||
return UnlockWalletsUseCase(userWalletsListManager = userWalletsListManager)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesUnlockWalletUseCase(
|
||||
|
|
@ -200,31 +142,19 @@ internal object WalletsDomainModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun providesSelectWalletUseCase(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
reduxStateHolder: ReduxStateHolder,
|
||||
): SelectWalletUseCase {
|
||||
return SelectWalletUseCase(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
useNewRepository = hotWalletFeatureToggles.isHotWalletEnabled,
|
||||
reduxStateHolder = reduxStateHolder,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesUpdateWalletUseCase(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
): UpdateWalletUseCase {
|
||||
return UpdateWalletUseCase(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
useNewRepository = hotWalletFeatureToggles.isHotWalletEnabled,
|
||||
)
|
||||
fun providesUpdateWalletUseCase(userWalletsListRepository: UserWalletsListRepository): UpdateWalletUseCase {
|
||||
return UpdateWalletUseCase(userWalletsListRepository = userWalletsListRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
@ -241,44 +171,14 @@ internal object WalletsDomainModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesGetWalletsSyncUseCase(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
): GetWalletNamesUseCase {
|
||||
return GetWalletNamesUseCase(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
useNewRepository = hotWalletFeatureToggles.isHotWalletEnabled,
|
||||
)
|
||||
fun providesGetWalletsSyncUseCase(userWalletsListRepository: UserWalletsListRepository): GetWalletNamesUseCase {
|
||||
return GetWalletNamesUseCase(userWalletsListRepository = userWalletsListRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesDeleteWalletUseCase(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
): DeleteWalletUseCase {
|
||||
return DeleteWalletUseCase(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
useNewRepository = hotWalletFeatureToggles.isHotWalletEnabled,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesShouldSaveUserWalletsSyncUseCase(
|
||||
walletsRepository: WalletsRepository,
|
||||
): ShouldSaveUserWalletsSyncUseCase {
|
||||
return ShouldSaveUserWalletsSyncUseCase(walletsRepository = walletsRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesShouldSaveUserWalletsUseCase(walletsRepository: WalletsRepository): ShouldSaveUserWalletsUseCase {
|
||||
return ShouldSaveUserWalletsUseCase(walletsRepository = walletsRepository)
|
||||
fun providesDeleteWalletUseCase(userWalletsListRepository: UserWalletsListRepository): DeleteWalletUseCase {
|
||||
return DeleteWalletUseCase(userWalletsListRepository = userWalletsListRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
@ -350,15 +250,9 @@ internal object WalletsDomainModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun providesGetSavedWalletChangesIdUseCase(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
): GetSavedWalletsCountUseCase {
|
||||
return GetSavedWalletsCountUseCase(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
useNewRepository = hotWalletFeatureToggles.isHotWalletEnabled,
|
||||
)
|
||||
return GetSavedWalletsCountUseCase(userWalletsListRepository = userWalletsListRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
@ -482,15 +376,11 @@ internal object WalletsDomainModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideGetWalletsForAutomaticallyPushEnablingUseCase(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
dispatcherProvider: CoroutineDispatcherProvider,
|
||||
): GetWalletsForAutomaticallyPushEnablingUseCase {
|
||||
return GetWalletsForAutomaticallyPushEnablingUseCase(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
shouldUseNewListRepository = hotWalletFeatureToggles.isHotWalletEnabled,
|
||||
dispatchers = dispatcherProvider,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,19 +16,14 @@ import com.tangem.domain.visa.model.VisaActivationRemoteState
|
|||
import com.tangem.domain.visa.model.VisaCardActivationStatus
|
||||
import com.tangem.domain.wallets.hot.HotWalletAccessCodeAttemptsRepository
|
||||
import com.tangem.domain.wallets.hot.HotWalletPasswordRequester
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.hot.sdk.TangemHotSdk
|
||||
import com.tangem.sdk.storage.AndroidSecureStorage
|
||||
import com.tangem.sdk.storage.AndroidSecureStorageV2
|
||||
import com.tangem.sdk.storage.createEncryptedSharedPreferences
|
||||
import com.tangem.tap.domain.userWalletList.implementation.BiometricUserWalletsListManager
|
||||
import com.tangem.tap.domain.userWalletList.implementation.GeneralUserWalletsListManager
|
||||
import com.tangem.tap.domain.userWalletList.implementation.RuntimeUserWalletsListManager
|
||||
import com.tangem.tap.domain.userWalletList.repository.DefaultUserWalletsListRepository
|
||||
import com.tangem.tap.domain.userWalletList.repository.DelegatedKeystoreManager
|
||||
import com.tangem.tap.domain.userWalletList.repository.UserWalletEncryptionKeysRepository
|
||||
import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysStoreDecorator
|
||||
import com.tangem.tap.domain.userWalletList.repository.implementation.BiometricUserWalletsKeysRepository
|
||||
import com.tangem.tap.domain.userWalletList.repository.implementation.DefaultSelectedUserWalletRepository
|
||||
import com.tangem.tap.domain.userWalletList.repository.implementation.DefaultUserWalletsPublicInformationRepository
|
||||
import com.tangem.tap.domain.userWalletList.repository.implementation.DefaultUserWalletsSensitiveInformationRepository
|
||||
|
|
@ -47,77 +42,6 @@ import javax.inject.Singleton
|
|||
@InstallIn(SingletonComponent::class)
|
||||
internal object UserWalletsListManagerModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Deprecated("Use UserWalletsListRepository instead")
|
||||
fun provideGeneralUserWalletsListManager(
|
||||
@ApplicationContext applicationContext: Context,
|
||||
appPreferencesStore: AppPreferencesStore,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
analyticsEventHandler: AnalyticsEventHandler,
|
||||
): UserWalletsListManager {
|
||||
return GeneralUserWalletsListManager(
|
||||
runtimeUserWalletsListManager = RuntimeUserWalletsListManager(),
|
||||
biometricUserWalletsListManager = createBiometricUserWalletsListManager(
|
||||
applicationContext = applicationContext,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
dispatchers = dispatchers,
|
||||
),
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
||||
@Deprecated("Use UserWalletsListRepository instead")
|
||||
private fun createBiometricUserWalletsListManager(
|
||||
applicationContext: Context,
|
||||
analyticsEventHandler: AnalyticsEventHandler,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): UserWalletsListManager {
|
||||
val moshi = buildMoshi()
|
||||
val secureStorage = buildSecureStorage(applicationContext = applicationContext)
|
||||
|
||||
val authenticatedStorage = AuthenticatedStorage(
|
||||
secureStorage = UserWalletsKeysStoreDecorator(
|
||||
featureStorage = secureStorage,
|
||||
cardSdkStorageProvider = Provider { tangemSdkManager.secureStorage },
|
||||
),
|
||||
keystoreManager = DelegatedKeystoreManager(
|
||||
keystoreManagerProvider = Provider { tangemSdkManager.keystoreManager },
|
||||
),
|
||||
)
|
||||
|
||||
val keysRepository = BiometricUserWalletsKeysRepository(
|
||||
moshi = moshi,
|
||||
secureStorage = secureStorage,
|
||||
authenticatedStorage = authenticatedStorage,
|
||||
analyticsEventHandler = analyticsEventHandler,
|
||||
)
|
||||
|
||||
val publicInformationRepository = DefaultUserWalletsPublicInformationRepository(
|
||||
moshi = moshi,
|
||||
secureStorage = secureStorage,
|
||||
)
|
||||
|
||||
val sensitiveInformationRepository = DefaultUserWalletsSensitiveInformationRepository(
|
||||
moshi = moshi,
|
||||
secureStorage = secureStorage,
|
||||
)
|
||||
|
||||
val selectedUserWalletRepository = DefaultSelectedUserWalletRepository(
|
||||
secureStorage = secureStorage,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
|
||||
return BiometricUserWalletsListManager(
|
||||
keysRepository = keysRepository,
|
||||
publicInformationRepository = publicInformationRepository,
|
||||
sensitiveInformationRepository = sensitiveInformationRepository,
|
||||
selectedUserWalletRepository = selectedUserWalletRepository,
|
||||
dispatcherProvider = dispatchers,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideUserWalletsListRepository(
|
||||
|
|
@ -183,7 +107,7 @@ internal object UserWalletsListManagerModule {
|
|||
)
|
||||
}
|
||||
|
||||
fun buildMoshi(): Moshi {
|
||||
private fun buildMoshi(): Moshi {
|
||||
return Moshi.Builder()
|
||||
.add(WalletDerivedKeysMapAdapter())
|
||||
.add(ScanResponseDerivedKeysMapAdapter())
|
||||
|
|
@ -200,7 +124,7 @@ internal object UserWalletsListManagerModule {
|
|||
.build()
|
||||
}
|
||||
|
||||
fun buildSecureStorage(@ApplicationContext applicationContext: Context): SecureStorage {
|
||||
private fun buildSecureStorage(@ApplicationContext applicationContext: Context): SecureStorage {
|
||||
return AndroidSecureStorage(
|
||||
preferences = SecureStorage.createEncryptedSharedPreferences(
|
||||
context = applicationContext,
|
||||
|
|
|
|||
|
|
@ -1,434 +0,0 @@
|
|||
package com.tangem.tap.domain.userWalletList.implementation
|
||||
|
||||
import com.tangem.common.*
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isLocked
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListError
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager.Lockable.UnlockType
|
||||
import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey
|
||||
import com.tangem.tap.domain.userWalletList.repository.SelectedUserWalletRepository
|
||||
import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysRepository
|
||||
import com.tangem.tap.domain.userWalletList.repository.UserWalletsPublicInformationRepository
|
||||
import com.tangem.tap.domain.userWalletList.repository.UserWalletsSensitiveInformationRepository
|
||||
import com.tangem.tap.domain.userWalletList.utils.encryptionKey
|
||||
import com.tangem.tap.domain.userWalletList.utils.lockAll
|
||||
import com.tangem.tap.domain.userWalletList.utils.toUserWallets
|
||||
import com.tangem.tap.domain.userWalletList.utils.updateWith
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
|
||||
@Suppress("LargeClass")
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
internal class BiometricUserWalletsListManager(
|
||||
private val keysRepository: UserWalletsKeysRepository,
|
||||
private val publicInformationRepository: UserWalletsPublicInformationRepository,
|
||||
private val sensitiveInformationRepository: UserWalletsSensitiveInformationRepository,
|
||||
private val selectedUserWalletRepository: SelectedUserWalletRepository,
|
||||
private val dispatcherProvider: CoroutineDispatcherProvider,
|
||||
) : UserWalletsListManager.Lockable {
|
||||
private val state = MutableStateFlow(State())
|
||||
|
||||
private var hasSavedWallets: Boolean? = null
|
||||
private val savedWalletMutex = Mutex()
|
||||
|
||||
override val isLockable: Boolean = true
|
||||
|
||||
override val userWallets: Flow<List<UserWallet>>
|
||||
get() = state
|
||||
.mapLatest { it.userWallets }
|
||||
.distinctUntilChanged()
|
||||
|
||||
override val userWalletsSync: List<UserWallet>
|
||||
get() = state.value.userWallets
|
||||
|
||||
@Deprecated("You should provide the selected wallet via routing parameters due to the scalability of the features")
|
||||
override val selectedUserWallet: Flow<UserWallet>
|
||||
get() = state
|
||||
.mapLatest { state ->
|
||||
findSelectedUserWallet(state.userWallets)
|
||||
}
|
||||
.filterNotNull()
|
||||
.distinctUntilChanged()
|
||||
|
||||
@Deprecated("You should provide the selected wallet via routing parameters due to the scalability of the features")
|
||||
override val selectedUserWalletSync: UserWallet?
|
||||
get() = findSelectedUserWallet()
|
||||
|
||||
override val lockedState: Flow<Boolean>
|
||||
get() = state
|
||||
.mapLatest { it.isLocked }
|
||||
.distinctUntilChanged()
|
||||
|
||||
override val isLocked: Boolean
|
||||
get() = state.value.isLocked
|
||||
|
||||
override val hasUserWallets: Boolean
|
||||
get() {
|
||||
return runBlocking {
|
||||
// workaround to avoid calling hasSavedEncryptionKeys many times because of performance
|
||||
savedWalletMutex.withLock {
|
||||
Timber.i("Checking if user has saved wallets")
|
||||
val hasSavedWalletsLocal = hasSavedWallets
|
||||
if (hasSavedWalletsLocal == null || !hasSavedWalletsLocal) {
|
||||
val hasKeys = keysRepository.hasSavedEncryptionKeys()
|
||||
hasSavedWallets = hasKeys
|
||||
hasKeys
|
||||
} else {
|
||||
Timber.i("User has saved wallets (from cache)")
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val walletsCount: Int
|
||||
get() = state.value.userWallets.size
|
||||
|
||||
override val savedWalletsCount: Flow<Int>
|
||||
get() = state
|
||||
.mapLatest { walletsCount }
|
||||
.distinctUntilChanged()
|
||||
|
||||
override suspend fun unlock(type: UnlockType): CompletionResult<UserWallet> {
|
||||
return withContext(dispatcherProvider.io) {
|
||||
unlockAndSetSelectedUserWallet(type)
|
||||
.mapFailure { error ->
|
||||
Timber.e(error, "Unable to unlock user wallets")
|
||||
if (error is UserWalletsListError) {
|
||||
error
|
||||
} else {
|
||||
UserWalletsListError.UnableToUnlockUserWallets(error)
|
||||
}
|
||||
}
|
||||
.map { selectedUserWallet ->
|
||||
if (selectedUserWallet == null || selectedUserWallet.isLocked) {
|
||||
Timber.e("Unable to find selected user wallet")
|
||||
throw UserWalletsListError.NoUserWalletSelected
|
||||
} else {
|
||||
selectedUserWallet
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun lock() {
|
||||
state.update { prevState ->
|
||||
prevState.copy(
|
||||
encryptionKeys = emptyList(),
|
||||
userWallets = prevState.userWallets.lockAll(),
|
||||
isLocked = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun select(userWalletId: UserWalletId): CompletionResult<UserWallet> = catching {
|
||||
if (state.value.selectedUserWalletId == userWalletId) {
|
||||
return@catching requireNotNull(findSelectedUserWallet()) {
|
||||
"Wallet is not found"
|
||||
}
|
||||
}
|
||||
|
||||
selectedUserWalletRepository.set(userWalletId)
|
||||
|
||||
val newState = state.updateAndGet { prevState ->
|
||||
prevState.copy(
|
||||
selectedUserWalletId = userWalletId,
|
||||
)
|
||||
}
|
||||
|
||||
newState.userWallets.first { it.walletId == userWalletId }
|
||||
}
|
||||
|
||||
override suspend fun save(userWallet: UserWallet, canOverride: Boolean): CompletionResult<Unit> {
|
||||
return withContext(dispatcherProvider.io) {
|
||||
if (canOverride) {
|
||||
saveInternal(userWallet, changeSelectedUserWallet = true, canOverridePublicInfo = false)
|
||||
} else {
|
||||
val isWalletSaved = state.value.userWallets
|
||||
.any {
|
||||
it.walletId == userWallet.walletId
|
||||
}
|
||||
|
||||
if (isWalletSaved) {
|
||||
CompletionResult.Failure(UserWalletsListError.WalletAlreadySaved)
|
||||
} else {
|
||||
saveInternal(userWallet, changeSelectedUserWallet = true, canOverridePublicInfo = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun update(
|
||||
userWalletId: UserWalletId,
|
||||
update: suspend (UserWallet) -> UserWallet,
|
||||
): CompletionResult<UserWallet> {
|
||||
return withContext(dispatcherProvider.io) {
|
||||
get(userWalletId)
|
||||
.map { storedUserWallet ->
|
||||
update(storedUserWallet)
|
||||
}
|
||||
.flatMap { updatedUserWallet ->
|
||||
saveInternal(updatedUserWallet, changeSelectedUserWallet = false, canOverridePublicInfo = true)
|
||||
}
|
||||
.flatMap {
|
||||
get(userWalletId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun delete(userWalletIds: List<UserWalletId>): CompletionResult<Unit> {
|
||||
val idsToRemove = state.value.userWallets
|
||||
.takeIf { it.isNotEmpty() }
|
||||
?.filter { it.walletId in userWalletIds }
|
||||
?.map { it.walletId }
|
||||
|
||||
if (idsToRemove.isNullOrEmpty()) {
|
||||
return CompletionResult.Success(Unit)
|
||||
}
|
||||
|
||||
if (idsToRemove.size == state.value.userWallets.size) {
|
||||
return clear()
|
||||
}
|
||||
|
||||
return withContext(dispatcherProvider.io) {
|
||||
sensitiveInformationRepository.delete(idsToRemove)
|
||||
.flatMap { publicInformationRepository.delete(idsToRemove) }
|
||||
.map { keysRepository.delete(idsToRemove) }
|
||||
.map {
|
||||
state.update { prevState ->
|
||||
val remainingWallets = prevState.userWallets.filter { it.walletId !in idsToRemove }
|
||||
|
||||
val isSelectedWalletDeleted = prevState.selectedUserWalletId in idsToRemove
|
||||
val newSelectedUserWallet = findOrSetSelectedWallet(
|
||||
prevSelectedWalletId = prevState.selectedUserWalletId,
|
||||
prevSelectedWalletIndex = prevState.userWallets.indexOfFirst {
|
||||
it.walletId == prevState.selectedUserWalletId
|
||||
},
|
||||
userWallets = remainingWallets,
|
||||
ignorePrevSelectedWallet = isSelectedWalletDeleted,
|
||||
)
|
||||
|
||||
prevState.copy(
|
||||
encryptionKeys = prevState.encryptionKeys.filter { it.walletId !in idsToRemove },
|
||||
userWallets = remainingWallets,
|
||||
isLocked = remainingWallets.any { it.isLocked },
|
||||
selectedUserWalletId = newSelectedUserWallet?.walletId,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun clear(): CompletionResult<Unit> {
|
||||
savedWalletMutex.withLock {
|
||||
hasSavedWallets = null
|
||||
}
|
||||
return withContext(dispatcherProvider.io) {
|
||||
sensitiveInformationRepository.clear()
|
||||
.flatMap { publicInformationRepository.clear() }
|
||||
.map {
|
||||
keysRepository.clear()
|
||||
selectedUserWalletRepository.set(null)
|
||||
state.value = State()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun get(userWalletId: UserWalletId): CompletionResult<UserWallet> {
|
||||
return catching {
|
||||
state.value.userWallets.first { it.walletId == userWalletId }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun saveInternal(
|
||||
userWallet: UserWallet,
|
||||
changeSelectedUserWallet: Boolean,
|
||||
canOverridePublicInfo: Boolean,
|
||||
): CompletionResult<Unit> {
|
||||
val encryptionKey = userWallet.encryptionKey
|
||||
?.let { UserWalletEncryptionKey(userWallet.walletId, it) }
|
||||
?: return CompletionResult.Success(Unit) // No encryption key, no need to save
|
||||
|
||||
return keysRepository.save(encryptionKey)
|
||||
.flatMap { sensitiveInformationRepository.save(userWallet, encryptionKey = encryptionKey.encryptionKey) }
|
||||
.flatMap { publicInformationRepository.save(userWallet, canOverridePublicInfo) }
|
||||
.flatMap {
|
||||
loadUserWallets(
|
||||
encryptionKeys = state.value.encryptionKeys
|
||||
.plus(encryptionKey)
|
||||
.distinctBy(UserWalletEncryptionKey::walletId),
|
||||
)
|
||||
}
|
||||
.doOnSuccess { loadedState ->
|
||||
if (changeSelectedUserWallet) {
|
||||
selectedUserWalletRepository.set(userWallet.walletId)
|
||||
|
||||
state.value = loadedState.copy(
|
||||
selectedUserWalletId = userWallet.walletId,
|
||||
)
|
||||
} else {
|
||||
state.value = loadedState
|
||||
}
|
||||
}
|
||||
.map { /* Type erasing */ }
|
||||
}
|
||||
|
||||
private suspend fun unlockAndSetSelectedUserWallet(type: UnlockType): CompletionResult<UserWallet?> {
|
||||
return keysRepository.getAll()
|
||||
.flatMap { encryptionKeys ->
|
||||
loadUserWallets(
|
||||
encryptionKeys = state.value.encryptionKeys
|
||||
.plus(encryptionKeys)
|
||||
.distinctBy(UserWalletEncryptionKey::walletId),
|
||||
)
|
||||
}
|
||||
.map { loadedState ->
|
||||
when (type) {
|
||||
UnlockType.ALL -> {
|
||||
if (loadedState.isLocked) {
|
||||
Timber.e("Some user wallets remain locked")
|
||||
|
||||
state.value = loadedState
|
||||
|
||||
throw UserWalletsListError.NotAllUserWalletsUnlocked
|
||||
} else {
|
||||
val prevState = state.value
|
||||
|
||||
val selectedWallet = findOrSetSelectedWallet(
|
||||
prevSelectedWalletId = prevState.selectedUserWalletId,
|
||||
userWallets = loadedState.userWallets,
|
||||
prevSelectedWalletIndex = prevState.userWallets.indexOfFirst {
|
||||
it.walletId == prevState.selectedUserWalletId
|
||||
},
|
||||
)
|
||||
|
||||
state.value = loadedState.copy(selectedUserWalletId = selectedWallet?.walletId)
|
||||
|
||||
selectedWallet
|
||||
}
|
||||
}
|
||||
UnlockType.ANY -> {
|
||||
val prevState = state.value
|
||||
|
||||
val selectedWallet = findOrSetSelectedWallet(
|
||||
prevSelectedWalletId = state.value.selectedUserWalletId,
|
||||
prevSelectedWalletIndex = prevState.userWallets.indexOfFirst {
|
||||
it.walletId == prevState.selectedUserWalletId
|
||||
},
|
||||
userWallets = loadedState.userWallets,
|
||||
)
|
||||
|
||||
state.value = loadedState.copy(selectedUserWalletId = selectedWallet?.walletId)
|
||||
|
||||
selectedWallet
|
||||
}
|
||||
UnlockType.ALL_WITHOUT_SELECT -> {
|
||||
state.value = loadedState
|
||||
|
||||
findSelectedUserWallet()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadUserWallets(encryptionKeys: List<UserWalletEncryptionKey>): CompletionResult<State> {
|
||||
return publicInformationRepository.getAll()
|
||||
.map { it.toUserWallets() }
|
||||
.flatMap { userWallets ->
|
||||
sensitiveInformationRepository.getAll(encryptionKeys)
|
||||
.map { walletIdToSensitiveInformation ->
|
||||
userWallets.updateWith(walletIdToSensitiveInformation)
|
||||
}
|
||||
}
|
||||
.map { userWallets ->
|
||||
val prevState = state.value
|
||||
|
||||
if (userWallets.isNotEmpty()) {
|
||||
val newUserWallets = (userWallets + prevState.userWallets)
|
||||
.distinctBy(UserWallet::walletId)
|
||||
|
||||
prevState.copy(
|
||||
userWallets = newUserWallets,
|
||||
encryptionKeys = encryptionKeys,
|
||||
isLocked = newUserWallets.any(UserWallet::isLocked),
|
||||
)
|
||||
} else {
|
||||
prevState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun findOrSetSelectedWallet(
|
||||
prevSelectedWalletId: UserWalletId?,
|
||||
prevSelectedWalletIndex: Int,
|
||||
userWallets: List<UserWallet>,
|
||||
ignorePrevSelectedWallet: Boolean = false,
|
||||
): UserWallet? {
|
||||
var possibleSelectedUserWallet: UserWallet? = null
|
||||
|
||||
if (!ignorePrevSelectedWallet) {
|
||||
val selectedWalletId = prevSelectedWalletId ?: selectedUserWalletRepository.get()
|
||||
possibleSelectedUserWallet = findSelectedUserWallet(userWallets, selectedWalletId)
|
||||
}
|
||||
|
||||
if (possibleSelectedUserWallet == null || possibleSelectedUserWallet.isLocked) {
|
||||
possibleSelectedUserWallet =
|
||||
userWallets.findAvailableUserWallet(prevSelectedIndex = prevSelectedWalletIndex)
|
||||
}
|
||||
|
||||
selectedUserWalletRepository.set(possibleSelectedUserWallet?.walletId)
|
||||
|
||||
return possibleSelectedUserWallet
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the nearest available wallet that can be selected
|
||||
*
|
||||
* Example:
|
||||
* Number with *n* is previous selected wallet with index [prevSelectedIndex].
|
||||
*
|
||||
* 1. [*1*, 2, 3, 4] => delete 1 => [2, 3, 4] => find and select => [*2*, 3, 4]
|
||||
* 2. [1, *2*, 3, 4] => delete 2 => [1, 3, 4] => find and select => [1, *3*, 4]
|
||||
* 3. [1, 2, *3*, 4] => delete 3 => [1, 2, 4] => find and select => [1, 2, *4*]
|
||||
* 4. [1, 2, 3, *4*] => delete 4 => [1, 2, 3] => find and select => [1, 2, *3*]
|
||||
*
|
||||
* @receiver list of user wallets without deleted wallet
|
||||
*/
|
||||
private fun List<UserWallet>.findAvailableUserWallet(prevSelectedIndex: Int): UserWallet? {
|
||||
if (prevSelectedIndex == 0) return firstOrNull { !it.isLocked } ?: firstOrNull()
|
||||
|
||||
if (prevSelectedIndex in indices && !this[prevSelectedIndex].isLocked) return this[prevSelectedIndex]
|
||||
|
||||
for (offset in 1..size) {
|
||||
val rightIndex = prevSelectedIndex + offset
|
||||
if (rightIndex in indices && !this[rightIndex].isLocked) return this[rightIndex]
|
||||
|
||||
val leftIndex = prevSelectedIndex - offset
|
||||
if (leftIndex in indices && !this[leftIndex].isLocked) return this[leftIndex]
|
||||
}
|
||||
|
||||
return lastOrNull()
|
||||
}
|
||||
|
||||
private fun findSelectedUserWallet(
|
||||
userWallets: List<UserWallet> = state.value.userWallets,
|
||||
selectedUserWalletId: UserWalletId? = state.value.selectedUserWalletId,
|
||||
): UserWallet? {
|
||||
return userWallets.firstOrNull { it.walletId == selectedUserWalletId }
|
||||
}
|
||||
|
||||
private data class State(
|
||||
val encryptionKeys: List<UserWalletEncryptionKey> = emptyList(),
|
||||
val userWallets: List<UserWallet> = emptyList(),
|
||||
val selectedUserWalletId: UserWalletId? = null,
|
||||
val isLocked: Boolean = true,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,203 +0,0 @@
|
|||
package com.tangem.tap.domain.userWalletList.implementation
|
||||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.get
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.*
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* General implementation of [UserWalletsListManager] that helps to switch between Runtime and Biometric
|
||||
* implementations.
|
||||
*
|
||||
* @property runtimeUserWalletsListManager runtime user wallets list manager
|
||||
* @property biometricUserWalletsListManager biometric user wallets list manager
|
||||
* @property appPreferencesStore app preferences store
|
||||
* @property dispatchers coroutine dispatcher provider
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
internal class GeneralUserWalletsListManager(
|
||||
private val runtimeUserWalletsListManager: UserWalletsListManager,
|
||||
private val biometricUserWalletsListManager: UserWalletsListManager,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : UserWalletsListManager.Lockable {
|
||||
|
||||
private val applicationScope = CoroutineScope(dispatchers.io)
|
||||
private val implementation: MutableStateFlow<UserWalletsListManager?> = MutableStateFlow(value = null)
|
||||
|
||||
private val requireImplementation: UserWalletsListManager
|
||||
get() = requireNotNull(implementation.value) {
|
||||
"UserWalletsListManager is not initialized"
|
||||
}
|
||||
|
||||
init {
|
||||
subscribeOnCurrentManager()
|
||||
}
|
||||
|
||||
override val isLockable: Boolean
|
||||
get() = requireImplementation.isLockable
|
||||
|
||||
override val userWallets: Flow<List<UserWallet>>
|
||||
get() = implementation
|
||||
.transformLatest { impl ->
|
||||
if (impl != null) {
|
||||
emitAll(impl.userWallets)
|
||||
}
|
||||
}
|
||||
// To avoid returning empty flow to subscriber while implementation and userWallets are null
|
||||
// Flow is called first time when implementation is null and then when its assigned with implementation
|
||||
// that may have not user wallets (null or empty).
|
||||
// As a result subscription occurs on empty flow, than will not change if user wallets are available
|
||||
.filter { requireImplementation.hasUserWallets }
|
||||
|
||||
override val savedWalletsCount: Flow<Int>
|
||||
get() = implementation
|
||||
.transformLatest { impl ->
|
||||
if (impl != null) {
|
||||
emitAll(impl.savedWalletsCount)
|
||||
}
|
||||
}
|
||||
|
||||
override val userWalletsSync: List<UserWallet>
|
||||
get() = requireImplementation.userWalletsSync
|
||||
|
||||
override val selectedUserWallet: Flow<UserWallet>
|
||||
get() = implementation
|
||||
.transformLatest { impl ->
|
||||
if (impl != null) {
|
||||
emitAll(impl.selectedUserWallet)
|
||||
}
|
||||
}
|
||||
// To avoid returning empty flow to subscriber while implementation and userWallets are null
|
||||
// Flow is called first time when implementation is null and then when its assigned with implementation
|
||||
// that may have not user wallets (null or empty).
|
||||
// As a result subscription occurs on empty flow, than will not change if user wallets are available
|
||||
.filter { requireImplementation.hasUserWallets }
|
||||
|
||||
override val selectedUserWalletSync: UserWallet?
|
||||
get() = requireImplementation.selectedUserWalletSync
|
||||
|
||||
override val hasUserWallets: Boolean
|
||||
get() = requireImplementation.hasUserWallets
|
||||
|
||||
override val walletsCount: Int
|
||||
get() = requireImplementation.walletsCount
|
||||
|
||||
override val lockedState: Flow<Boolean>
|
||||
get() = implementation.transformLatest { impl ->
|
||||
if (impl == null) return@transformLatest
|
||||
|
||||
if (impl is UserWalletsListManager.Lockable) {
|
||||
emitAll(impl.lockedState)
|
||||
} else {
|
||||
error("RuntimeUserWalletsListManager is not lockable")
|
||||
}
|
||||
}
|
||||
|
||||
override val isLocked: Boolean
|
||||
get() {
|
||||
val impl = requireImplementation
|
||||
|
||||
return if (impl is UserWalletsListManager.Lockable) {
|
||||
impl.isLocked
|
||||
} else {
|
||||
error("RuntimeUserWalletsListManager is not lockable")
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun select(userWalletId: UserWalletId): CompletionResult<UserWallet> {
|
||||
return requireImplementation.select(userWalletId)
|
||||
}
|
||||
|
||||
override suspend fun save(userWallet: UserWallet, canOverride: Boolean): CompletionResult<Unit> {
|
||||
return requireImplementation.save(userWallet, canOverride)
|
||||
}
|
||||
|
||||
override suspend fun update(
|
||||
userWalletId: UserWalletId,
|
||||
update: suspend (UserWallet) -> UserWallet,
|
||||
): CompletionResult<UserWallet> {
|
||||
return requireImplementation.update(userWalletId, update)
|
||||
}
|
||||
|
||||
override suspend fun delete(userWalletIds: List<UserWalletId>): CompletionResult<Unit> {
|
||||
return requireImplementation.delete(userWalletIds)
|
||||
}
|
||||
|
||||
override suspend fun clear(): CompletionResult<Unit> {
|
||||
return requireImplementation.clear()
|
||||
}
|
||||
|
||||
override suspend fun get(userWalletId: UserWalletId): CompletionResult<UserWallet> {
|
||||
return requireImplementation.get(userWalletId)
|
||||
}
|
||||
|
||||
override suspend fun unlock(type: UserWalletsListManager.Lockable.UnlockType): CompletionResult<UserWallet> {
|
||||
val implementation = requireImplementation
|
||||
|
||||
return if (implementation is UserWalletsListManager.Lockable) {
|
||||
implementation.unlock(type)
|
||||
} else {
|
||||
error("RuntimeUserWalletsListManager is not lockable")
|
||||
}
|
||||
}
|
||||
|
||||
override fun lock() {
|
||||
val implementation = requireImplementation
|
||||
|
||||
return if (implementation is UserWalletsListManager.Lockable) {
|
||||
implementation.lock()
|
||||
} else {
|
||||
error("RuntimeUserWalletsListManager is not lockable")
|
||||
}
|
||||
}
|
||||
|
||||
private fun subscribeOnCurrentManager() {
|
||||
appPreferencesStore.get(key = PreferencesKeys.SAVE_USER_WALLETS_KEY, default = false)
|
||||
.distinctUntilChanged()
|
||||
.onEach { shouldSaveUserWallets ->
|
||||
val possibleManager = if (shouldSaveUserWallets) {
|
||||
biometricUserWalletsListManager
|
||||
} else {
|
||||
runtimeUserWalletsListManager
|
||||
}
|
||||
|
||||
if (possibleManager == implementation.value) {
|
||||
Timber.e("Switch to the same manager ${possibleManager::class.simpleName.orEmpty()}")
|
||||
}
|
||||
|
||||
Timber.i("Switch to ${possibleManager::class.simpleName.orEmpty()}")
|
||||
|
||||
val previousManager = implementation.value
|
||||
implementation.value = copySelectedUserWallet(
|
||||
sourceManager = previousManager,
|
||||
destinationManager = possibleManager,
|
||||
)
|
||||
|
||||
previousManager?.clear()
|
||||
}
|
||||
.flowOn(dispatchers.io)
|
||||
.launchIn(applicationScope)
|
||||
}
|
||||
|
||||
private suspend fun copySelectedUserWallet(
|
||||
sourceManager: UserWalletsListManager?,
|
||||
destinationManager: UserWalletsListManager,
|
||||
): UserWalletsListManager {
|
||||
sourceManager?.selectedUserWalletSync?.let { selectedWallet ->
|
||||
destinationManager.save(selectedWallet, canOverride = true)
|
||||
}
|
||||
|
||||
return destinationManager
|
||||
}
|
||||
}
|
||||
|
|
@ -1,115 +0,0 @@
|
|||
package com.tangem.tap.domain.userWalletList.implementation
|
||||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.catching
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListError
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
internal class RuntimeUserWalletsListManager : UserWalletsListManager {
|
||||
private val state = MutableStateFlow(State())
|
||||
|
||||
override val isLockable: Boolean = false
|
||||
|
||||
override val userWallets: Flow<List<UserWallet>>
|
||||
get() = state
|
||||
.mapLatest { listOfNotNull(it.userWallet) }
|
||||
.distinctUntilChanged()
|
||||
|
||||
override val selectedUserWallet: Flow<UserWallet>
|
||||
get() = state
|
||||
.mapLatest { it.userWallet }
|
||||
.filterNotNull()
|
||||
.distinctUntilChanged()
|
||||
|
||||
override val userWalletsSync: List<UserWallet>
|
||||
get() = listOfNotNull(state.value.userWallet)
|
||||
|
||||
override val selectedUserWalletSync: UserWallet?
|
||||
get() = state.value.userWallet
|
||||
|
||||
override val hasUserWallets: Boolean
|
||||
get() = state.value.userWallet != null
|
||||
|
||||
/**
|
||||
* only 1 wallet stored in runtime implementation
|
||||
*/
|
||||
override val walletsCount: Int
|
||||
get() = if (hasUserWallets) 1 else 0
|
||||
|
||||
override val savedWalletsCount: Flow<Int>
|
||||
get() = state
|
||||
.mapLatest { walletsCount }
|
||||
.distinctUntilChanged()
|
||||
|
||||
override suspend fun select(userWalletId: UserWalletId): CompletionResult<UserWallet> = catching {
|
||||
state.value.userWallet
|
||||
?.takeIf { it.walletId == userWalletId }
|
||||
?: walletNotFound()
|
||||
}
|
||||
|
||||
override suspend fun save(userWallet: UserWallet, canOverride: Boolean): CompletionResult<Unit> {
|
||||
return if (canOverride) {
|
||||
saveInternal(userWallet)
|
||||
} else {
|
||||
val isWalletSaved = state.value.userWallet?.walletId == userWallet.walletId
|
||||
|
||||
if (isWalletSaved) {
|
||||
CompletionResult.Failure(UserWalletsListError.WalletAlreadySaved)
|
||||
} else {
|
||||
saveInternal(userWallet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun update(
|
||||
userWalletId: UserWalletId,
|
||||
update: suspend (UserWallet) -> UserWallet,
|
||||
): CompletionResult<UserWallet> = catching {
|
||||
val wallet = state.value.userWallet
|
||||
?.takeIf { it.walletId == userWalletId }
|
||||
?: walletNotFound()
|
||||
|
||||
requireNotNull(
|
||||
state.updateAndGet { prevState ->
|
||||
prevState.copy(
|
||||
userWallet = update(wallet),
|
||||
)
|
||||
}.userWallet,
|
||||
) { "User wallet is null after update" }
|
||||
}
|
||||
|
||||
override suspend fun delete(userWalletIds: List<UserWalletId>): CompletionResult<Unit> = clear()
|
||||
|
||||
override suspend fun clear(): CompletionResult<Unit> = catching {
|
||||
state.update { prevState ->
|
||||
prevState.copy(
|
||||
userWallet = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun get(userWalletId: UserWalletId): CompletionResult<UserWallet> = catching {
|
||||
state.value.userWallet ?: walletNotFound()
|
||||
}
|
||||
|
||||
private fun saveInternal(userWallet: UserWallet): CompletionResult<Unit> = catching {
|
||||
state.update { prevState ->
|
||||
prevState.copy(
|
||||
userWallet = userWallet,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun walletNotFound(): Nothing {
|
||||
throw NoSuchElementException("User wallet not found")
|
||||
}
|
||||
|
||||
private data class State(
|
||||
val userWallet: UserWallet? = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
package com.tangem.tap.domain.userWalletList.repository.implementation
|
||||
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListError
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
object BiometricFailReasonConverter : Converter<TangemError, Basic.BiometryFailed.BiometricFailReason> {
|
||||
|
||||
override fun convert(value: TangemError): Basic.BiometryFailed.BiometricFailReason {
|
||||
// 1. Try handle TangemSdkError cases first if error has not been mapped
|
||||
when (value) {
|
||||
is TangemSdkError.AuthenticationCanceled ->
|
||||
return Basic.BiometryFailed.BiometricFailReason.AuthenticationCancelled
|
||||
is TangemSdkError.AuthenticationAlreadyInProgress ->
|
||||
return Basic.BiometryFailed.BiometricFailReason.AuthenticationAlreadyInProgress
|
||||
}
|
||||
|
||||
// 2. For other errors, check if they are of type UserWalletsListError
|
||||
if (value !is UserWalletsListError) {
|
||||
return Basic.BiometryFailed.BiometricFailReason.Other(value.customMessage)
|
||||
}
|
||||
// 3. Map UserWalletsListError to BiometricFailReason
|
||||
return when (value) {
|
||||
UserWalletsListError.AllKeysInvalidated ->
|
||||
Basic.BiometryFailed.BiometricFailReason.AllKeysInvalidated
|
||||
UserWalletsListError.BiometricsAuthenticationDisabled ->
|
||||
Basic.BiometryFailed.BiometricFailReason.BiometricsAuthenticationDisabled
|
||||
is UserWalletsListError.BiometricsAuthenticationLockout ->
|
||||
if (value.isPermanent) {
|
||||
Basic.BiometryFailed.BiometricFailReason.AuthenticationLockoutPermanent
|
||||
} else {
|
||||
Basic.BiometryFailed.BiometricFailReason.AuthenticationLockout
|
||||
}
|
||||
else -> Basic.BiometryFailed.BiometricFailReason.Other(value.customMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,207 +0,0 @@
|
|||
package com.tangem.tap.domain.userWalletList.repository.implementation
|
||||
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.Types
|
||||
import com.tangem.common.*
|
||||
import com.tangem.common.authentication.storage.AuthenticatedStorage
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.services.secure.SecureStorage
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.Basic
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListError
|
||||
import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey
|
||||
import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysRepository
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
internal class BiometricUserWalletsKeysRepository(
|
||||
moshi: Moshi,
|
||||
private val authenticatedStorage: AuthenticatedStorage,
|
||||
private val secureStorage: SecureStorage,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
) : UserWalletsKeysRepository {
|
||||
|
||||
private val encryptionKeyAdapter: JsonAdapter<UserWalletEncryptionKey> = moshi.adapter(
|
||||
UserWalletEncryptionKey::class.java,
|
||||
)
|
||||
private val userWalletsIdsListAdapter: JsonAdapter<List<UserWalletId>> = moshi.adapter(
|
||||
Types.newParameterizedType(List::class.java, UserWalletId::class.java),
|
||||
)
|
||||
|
||||
override suspend fun getAll(): CompletionResult<List<UserWalletEncryptionKey>> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
getAllInternal()
|
||||
.mapFailure { error ->
|
||||
val mappedError = when (error) {
|
||||
is TangemSdkError.AuthenticationLockout ->
|
||||
UserWalletsListError.BiometricsAuthenticationLockout(isPermanent = false)
|
||||
is TangemSdkError.AuthenticationPermanentLockout ->
|
||||
UserWalletsListError.BiometricsAuthenticationLockout(isPermanent = true)
|
||||
is TangemSdkError.KeystoreInvalidated ->
|
||||
UserWalletsListError.AllKeysInvalidated
|
||||
is TangemSdkError.AuthenticationUnavailable ->
|
||||
UserWalletsListError.BiometricsAuthenticationDisabled
|
||||
else -> error
|
||||
}
|
||||
analyticsEventHandler.send(
|
||||
Basic.BiometryFailed(
|
||||
source = AnalyticsParam.ScreensSources.SignIn,
|
||||
reason = BiometricFailReasonConverter.convert(mappedError),
|
||||
),
|
||||
)
|
||||
mappedError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun save(encryptionKey: UserWalletEncryptionKey): CompletionResult<Unit> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
storeEncryptionKey(encryptionKey)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun delete(userWalletsIds: List<UserWalletId>) {
|
||||
return withContext(Dispatchers.IO) {
|
||||
userWalletsIds.forEach { userWalletId ->
|
||||
deleteEncryptionKey(userWalletId)
|
||||
}
|
||||
|
||||
deleteUserWalletsIds(userWalletsIds)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun clear() {
|
||||
return withContext(Dispatchers.IO) {
|
||||
getUserWalletsIds()
|
||||
.forEach { userWalletId ->
|
||||
deleteEncryptionKey(userWalletId)
|
||||
}
|
||||
|
||||
clearUserWalletsIds()
|
||||
}
|
||||
}
|
||||
|
||||
override fun hasSavedEncryptionKeys(): Boolean {
|
||||
return runBlocking {
|
||||
getUserWalletsIds().isNotEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getAllInternal(): CompletionResult<List<UserWalletEncryptionKey>> {
|
||||
return catching {
|
||||
val userWalletIds = getUserWalletsIds()
|
||||
|
||||
getEncryptionKeys(userWalletIds)
|
||||
}
|
||||
.doOnFailure { error ->
|
||||
when (error) {
|
||||
is TangemSdkError.KeystoreInvalidated -> {
|
||||
getUserWalletsIds().forEach { userWalletId ->
|
||||
deleteEncryptionKey(userWalletId)
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getEncryptionKeys(userWalletsIds: List<UserWalletId>): List<UserWalletEncryptionKey> {
|
||||
val keys = userWalletsIds.map { userWalletId ->
|
||||
StorageKey.UserWalletEncryptionKey(userWalletId).name
|
||||
}
|
||||
|
||||
return authenticatedStorage.get(keys).mapNotNull { (_, encodedData) ->
|
||||
encodedData.decodeToKey()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun storeEncryptionKey(encryptionKey: UserWalletEncryptionKey): CompletionResult<Unit> {
|
||||
return catching {
|
||||
authenticatedStorage.store(
|
||||
keyAlias = StorageKey.UserWalletEncryptionKey(encryptionKey.walletId).name,
|
||||
data = encryptionKey.encode(),
|
||||
)
|
||||
}
|
||||
.map { storeUserWalletId(encryptionKey.walletId) }
|
||||
}
|
||||
|
||||
private fun deleteEncryptionKey(userWalletId: UserWalletId) {
|
||||
authenticatedStorage.delete(StorageKey.UserWalletEncryptionKey(userWalletId).name)
|
||||
}
|
||||
|
||||
private suspend fun getUserWalletsIds(): List<UserWalletId> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
secureStorage.get(StorageKey.UserWalletIds.name)
|
||||
.decodeToUserWalletsIds()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun storeUserWalletId(userWalletId: UserWalletId) {
|
||||
val userWalletIds = (getUserWalletsIds() + userWalletId).distinct()
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
secureStorage.store(userWalletIds.encode(), StorageKey.UserWalletIds.name)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun deleteUserWalletsIds(userWalletsIds: List<UserWalletId>) {
|
||||
val remainingIds = getUserWalletsIds() - userWalletsIds.toSet()
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
secureStorage.store(remainingIds.encode(), StorageKey.UserWalletIds.name)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun clearUserWalletsIds() {
|
||||
withContext(Dispatchers.IO) {
|
||||
secureStorage.delete(StorageKey.UserWalletIds.name)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun UserWalletEncryptionKey.encode(): ByteArray {
|
||||
return withContext(Dispatchers.Default) {
|
||||
encryptionKeyAdapter.toJson(this@encode)
|
||||
.encodeToByteArray(throwOnInvalidSequence = true)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun ByteArray?.decodeToKey(): UserWalletEncryptionKey? {
|
||||
return withContext(Dispatchers.Default) {
|
||||
this@decodeToKey
|
||||
?.decodeToString(throwOnInvalidSequence = true)
|
||||
?.let(encryptionKeyAdapter::fromJson)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun List<UserWalletId>.encode(): ByteArray {
|
||||
return withContext(Dispatchers.Default) {
|
||||
userWalletsIdsListAdapter.toJson(this@encode)
|
||||
.encodeToByteArray(throwOnInvalidSequence = true)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun ByteArray?.decodeToUserWalletsIds(): List<UserWalletId> {
|
||||
return withContext(Dispatchers.Default) {
|
||||
this@decodeToUserWalletsIds
|
||||
?.decodeToString(throwOnInvalidSequence = true)
|
||||
?.let(userWalletsIdsListAdapter::fromJson)
|
||||
.orEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
private sealed interface StorageKey {
|
||||
val name: String
|
||||
|
||||
class UserWalletEncryptionKey(userWalletId: UserWalletId) : StorageKey {
|
||||
override val name: String = "user_wallet_encryption_key_${userWalletId.stringValue}"
|
||||
}
|
||||
|
||||
object UserWalletIds : StorageKey {
|
||||
override val name: String = "user_wallets_ids_with_saved_keys"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -104,8 +104,6 @@ internal fun List<UserWallet>.updateWith(
|
|||
}
|
||||
}
|
||||
|
||||
internal fun List<UserWallet>.lockAll(): List<UserWallet> = map(UserWallet::lock)
|
||||
|
||||
internal fun UserWallet.lock(): UserWallet = when (this) {
|
||||
is UserWallet.Cold -> {
|
||||
copy(
|
||||
|
|
|
|||
|
|
@ -3,15 +3,12 @@ package com.tangem.tap.features.details.redux
|
|||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository.LockMethod
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
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.dispatchWithMain
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
|
|
@ -43,7 +40,7 @@ class DetailsMiddleware {
|
|||
if (!DemoHelper.tryHandle(stateProvider)) {
|
||||
val detailsState = stateProvider()?.detailsState
|
||||
if (detailsState != null) {
|
||||
handleAction(detailsState, action)
|
||||
handleAction(action)
|
||||
}
|
||||
}
|
||||
next(action)
|
||||
|
|
@ -51,9 +48,9 @@ class DetailsMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleAction(state: DetailsState, action: Action) {
|
||||
private fun handleAction(action: Action) {
|
||||
when (action) {
|
||||
is DetailsAction.AppSettings -> appSettingsMiddleware.handle(state, action)
|
||||
is DetailsAction.AppSettings -> appSettingsMiddleware.handle(action)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -61,12 +58,10 @@ class DetailsMiddleware {
|
|||
|
||||
private val checkBiometricsStatusJobHolder = JobHolder()
|
||||
|
||||
fun handle(state: DetailsState, action: DetailsAction.AppSettings) {
|
||||
fun handle(action: DetailsAction.AppSettings) {
|
||||
when (action) {
|
||||
is DetailsAction.AppSettings.SwitchPrivacySetting -> {
|
||||
when (action.setting) {
|
||||
AppSetting.SaveWallets -> toggleSaveWallets(state, enable = action.enable)
|
||||
AppSetting.SaveAccessCode -> toggleSaveAccessCodes(state, enable = action.enable)
|
||||
AppSetting.RequireAccessCode -> toggleRequireAccessCode(enable = action.enable)
|
||||
AppSetting.BiometricAuthentication -> toggleBiometricsAuthentication(enable = action.enable)
|
||||
}
|
||||
|
|
@ -221,118 +216,6 @@ class DetailsMiddleware {
|
|||
}
|
||||
}
|
||||
|
||||
private fun toggleSaveWallets(state: DetailsState, enable: Boolean) = scope.launch {
|
||||
// Nothing to change
|
||||
val walletsRepository = store.inject(DaggerGraphState::walletsRepository)
|
||||
|
||||
if (walletsRepository.shouldSaveUserWalletsSync() == enable) {
|
||||
store.dispatchWithMain(DetailsAction.AppSettings.SwitchPrivacySetting.Success)
|
||||
return@launch
|
||||
}
|
||||
|
||||
toggleSaveWallets(state.scanResponse, enable)
|
||||
.doOnFailure {
|
||||
store.dispatchWithMain(
|
||||
DetailsAction.AppSettings.SwitchPrivacySetting.Failure(
|
||||
prevState = !enable,
|
||||
setting = AppSetting.SaveWallets,
|
||||
),
|
||||
)
|
||||
}
|
||||
.doOnSuccess {
|
||||
store.dispatchWithMain(DetailsAction.AppSettings.SwitchPrivacySetting.Success)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun toggleSaveWallets(scanResponse: ScanResponse?, enable: Boolean): CompletionResult<Unit> {
|
||||
return if (enable) {
|
||||
saveCurrentWallet(scanResponse, enableAccessCodesSaving = false)
|
||||
} else {
|
||||
deleteSavedWalletsAndAccessCodes()
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleSaveAccessCodes(state: DetailsState, enable: Boolean) = scope.launch {
|
||||
val shouldSaveAccessCodes = store.inject(DaggerGraphState::settingsRepository).shouldSaveAccessCodes()
|
||||
|
||||
// Nothing to change
|
||||
if (shouldSaveAccessCodes == enable) {
|
||||
store.dispatchWithMain(DetailsAction.AppSettings.SwitchPrivacySetting.Success)
|
||||
return@launch
|
||||
}
|
||||
|
||||
toggleSaveAccessCodes(state.scanResponse, state.appSettingsState.saveWallets, enable)
|
||||
.doOnFailure {
|
||||
store.dispatchWithMain(
|
||||
DetailsAction.AppSettings.SwitchPrivacySetting.Failure(
|
||||
prevState = !enable,
|
||||
setting = AppSetting.SaveAccessCode,
|
||||
),
|
||||
)
|
||||
}
|
||||
.doOnSuccess {
|
||||
store.dispatchWithMain(DetailsAction.AppSettings.SwitchPrivacySetting.Success)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun toggleSaveAccessCodes(
|
||||
scanResponse: ScanResponse?,
|
||||
isWalletsSavingEnabled: Boolean,
|
||||
enable: Boolean,
|
||||
): CompletionResult<Unit> {
|
||||
return if (enable) {
|
||||
if (!isWalletsSavingEnabled) {
|
||||
saveCurrentWallet(scanResponse, enableAccessCodesSaving = true)
|
||||
} else {
|
||||
saveAccessCodes(scanResponse)
|
||||
}
|
||||
} else {
|
||||
deleteSavedAccessCodes()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun saveCurrentWallet(
|
||||
scanResponse: ScanResponse?,
|
||||
enableAccessCodesSaving: Boolean,
|
||||
): CompletionResult<Unit> {
|
||||
store.inject(DaggerGraphState::walletsRepository).saveShouldSaveUserWallets(item = true)
|
||||
|
||||
return if (enableAccessCodesSaving) {
|
||||
saveAccessCodes(scanResponse)
|
||||
} else {
|
||||
CompletionResult.Success(Unit)
|
||||
}
|
||||
.doOnSuccess {
|
||||
Analytics.send(Settings.AppSettings.SaveWalletSwitcherChanged(AnalyticsParam.OnOffState.On))
|
||||
}
|
||||
.doOnFailure { error ->
|
||||
Timber.e(error, "Unable to save user wallet")
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun deleteSavedWalletsAndAccessCodes(): CompletionResult<Unit> {
|
||||
Analytics.send(Settings.AppSettings.SaveWalletSwitcherChanged(AnalyticsParam.OnOffState.Off))
|
||||
|
||||
deleteSavedAccessCodes()
|
||||
store.inject(DaggerGraphState::walletsRepository).saveShouldSaveUserWallets(item = false)
|
||||
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Home()) }
|
||||
|
||||
return CompletionResult.Success(Unit)
|
||||
}
|
||||
|
||||
private suspend fun saveAccessCodes(scanResponse: ScanResponse?): CompletionResult<Unit> {
|
||||
Analytics.send(Settings.AppSettings.SaveAccessCodeSwitcherChanged(AnalyticsParam.OnOffState.On))
|
||||
|
||||
store.inject(DaggerGraphState::settingsRepository).setShouldSaveAccessCodes(value = true)
|
||||
|
||||
store.inject(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy(
|
||||
isBiometricsRequestPolicy = scanResponse?.card?.isAccessCodeSet == true,
|
||||
)
|
||||
|
||||
return CompletionResult.Success(Unit)
|
||||
}
|
||||
|
||||
private suspend fun deleteSavedAccessCodes(): CompletionResult<Unit> {
|
||||
return tangemSdkManager.clearSavedUserCodes()
|
||||
.doOnSuccess {
|
||||
|
|
|
|||
|
|
@ -38,15 +38,6 @@ private fun handlePrivacyAction(action: DetailsAction.AppSettings, state: Detail
|
|||
return when (action) {
|
||||
is DetailsAction.AppSettings.SwitchPrivacySetting -> state.copy(
|
||||
appSettingsState = when (action.setting) {
|
||||
AppSetting.SaveWallets -> state.appSettingsState.copy(
|
||||
isInProgress = true,
|
||||
saveWallets = action.enable,
|
||||
)
|
||||
AppSetting.SaveAccessCode -> state.appSettingsState.copy(
|
||||
isInProgress = true,
|
||||
saveWallets = true, // User can't enable access codes saving without wallets saving
|
||||
saveAccessCodes = action.enable,
|
||||
)
|
||||
AppSetting.RequireAccessCode -> state.appSettingsState.copy(
|
||||
isInProgress = true,
|
||||
requireAccessCode = action.enable,
|
||||
|
|
@ -64,14 +55,6 @@ private fun handlePrivacyAction(action: DetailsAction.AppSettings, state: Detail
|
|||
)
|
||||
is DetailsAction.AppSettings.SwitchPrivacySetting.Failure -> state.copy(
|
||||
appSettingsState = when (action.setting) {
|
||||
AppSetting.SaveWallets -> state.appSettingsState.copy(
|
||||
isInProgress = false,
|
||||
saveWallets = action.prevState,
|
||||
)
|
||||
AppSetting.SaveAccessCode -> state.appSettingsState.copy(
|
||||
isInProgress = false,
|
||||
saveAccessCodes = action.prevState,
|
||||
)
|
||||
AppSetting.RequireAccessCode -> state.appSettingsState.copy(
|
||||
isInProgress = false,
|
||||
requireAccessCode = action.prevState,
|
||||
|
|
@ -105,9 +88,6 @@ private fun handlePrivacyAction(action: DetailsAction.AppSettings, state: Detail
|
|||
// state should be copied to avoid concurrent modifications from different sources
|
||||
is DetailsAction.AppSettings.Prepare -> state.copy(
|
||||
appSettingsState = state.appSettingsState.copy(
|
||||
saveWallets = action.state.saveWallets,
|
||||
saveAccessCodes = action.state.saveAccessCodes,
|
||||
isBiometricsAvailable = action.state.isBiometricsAvailable,
|
||||
isHidingEnabled = action.state.isHidingEnabled,
|
||||
selectedAppCurrency = action.state.selectedAppCurrency,
|
||||
selectedThemeMode = action.state.selectedThemeMode,
|
||||
|
|
|
|||
|
|
@ -13,12 +13,6 @@ data class DetailsState(
|
|||
|
||||
@Suppress("BooleanPropertyNaming")
|
||||
data class AppSettingsState(
|
||||
@Deprecated("Delete after hot wallet release")
|
||||
val saveWallets: Boolean = false,
|
||||
@Deprecated("Delete after hot wallet release")
|
||||
val saveAccessCodes: Boolean = false,
|
||||
@Deprecated("Delete after hot wallet release")
|
||||
val isBiometricsAvailable: Boolean = false,
|
||||
val requireAccessCode: Boolean = false,
|
||||
val useBiometricAuthentication: Boolean = false,
|
||||
val needEnrollBiometrics: Boolean = false,
|
||||
|
|
@ -32,5 +26,5 @@ data class AppSettingsState(
|
|||
enum class SecurityOption { LongTap, PassCode, AccessCode }
|
||||
|
||||
enum class AppSetting {
|
||||
SaveWallets, SaveAccessCode, RequireAccessCode, BiometricAuthentication,
|
||||
RequireAccessCode, BiometricAuthentication,
|
||||
}
|
||||
|
|
@ -9,26 +9,6 @@ import kotlinx.collections.immutable.toImmutableList
|
|||
|
||||
internal class AppSettingsDialogsFactory {
|
||||
|
||||
fun createDeleteSavedWalletsAlert(onDelete: () -> Unit, onDismiss: () -> Unit): Dialog.Alert {
|
||||
return Dialog.Alert(
|
||||
title = resourceReference(R.string.common_attention),
|
||||
description = resourceReference(R.string.app_settings_off_saved_wallet_alert_message),
|
||||
confirmText = resourceReference(R.string.common_delete),
|
||||
onConfirm = onDelete,
|
||||
onDismiss = onDismiss,
|
||||
)
|
||||
}
|
||||
|
||||
fun createDeleteSavedAccessCodesAlert(onDelete: () -> Unit, onDismiss: () -> Unit): Dialog.Alert {
|
||||
return Dialog.Alert(
|
||||
title = resourceReference(R.string.common_attention),
|
||||
description = resourceReference(R.string.app_settings_off_saved_access_code_alert_message),
|
||||
confirmText = resourceReference(R.string.common_delete),
|
||||
onConfirm = onDelete,
|
||||
onDismiss = onDismiss,
|
||||
)
|
||||
}
|
||||
|
||||
fun createThemeModeSelectorDialog(
|
||||
selectedModeIndex: Int,
|
||||
onSelect: (AppThemeMode) -> Unit,
|
||||
|
|
|
|||
|
|
@ -19,21 +19,6 @@ internal class AppSettingsItemsFactory {
|
|||
)
|
||||
}
|
||||
|
||||
fun createSaveWalletsSwitch(
|
||||
isChecked: Boolean,
|
||||
isEnabled: Boolean,
|
||||
onCheckedChange: (Boolean) -> Unit,
|
||||
): Item.Switch {
|
||||
return Item.Switch(
|
||||
id = ID_SAVE_WALLETS_SWITCH,
|
||||
title = resourceReference(R.string.app_settings_saved_wallet),
|
||||
description = resourceReference(R.string.app_settings_saved_wallet_footer),
|
||||
isEnabled = isEnabled,
|
||||
isChecked = isChecked,
|
||||
onCheckedChange = onCheckedChange,
|
||||
)
|
||||
}
|
||||
|
||||
fun createUseBiometricsSwitch(
|
||||
isChecked: Boolean,
|
||||
isEnabled: Boolean,
|
||||
|
|
@ -127,7 +112,6 @@ internal class AppSettingsItemsFactory {
|
|||
|
||||
companion object {
|
||||
const val ID_ENROLL_BIOMETRICS_CARD = "enroll_biometrics_card"
|
||||
const val ID_SAVE_WALLETS_SWITCH = "save_wallets_switch"
|
||||
const val ID_SAVE_ACCESS_CODES_SWITCH = "save_access_codes_switch"
|
||||
const val ID_FLIP_TO_HIDE_BALANCE_SWITCH = "flip_to_hide_balance_switch"
|
||||
const val ID_SELECT_APP_CURRENCY_BUTTON = "select_app_currency_button"
|
||||
|
|
|
|||
|
|
@ -97,7 +97,6 @@ private class AppSettingsScreenStateProvider : CollectionPreviewParameterProvide
|
|||
val items = persistentListOf(
|
||||
itemsFactory.createEnrollBiometricsCard {},
|
||||
itemsFactory.createSelectAppCurrencyButton(currentAppCurrencyName = "US Dollar") {},
|
||||
itemsFactory.createSaveWalletsSwitch(isChecked = true, isEnabled = true, { _ -> }),
|
||||
itemsFactory.createSaveAccessCodeSwitch(isChecked = false, isEnabled = true) { _ -> },
|
||||
itemsFactory.createFlipToHideBalanceSwitch(isChecked = false, isEnabled = true) { _ -> },
|
||||
itemsFactory.createSelectThemeModeButton(AppThemeMode.DEFAULT, {}),
|
||||
|
|
|
|||
|
|
@ -43,12 +43,12 @@ private fun AlertDialogPreview(@PreviewParameter(AlertDialogProvider::class) dia
|
|||
}
|
||||
}
|
||||
|
||||
private class AlertDialogProvider : CollectionPreviewParameterProvider<Dialog.Alert>(
|
||||
private class AlertDialogProvider : CollectionPreviewParameterProvider<Dialog>(
|
||||
collection = buildList {
|
||||
val dialogsFactory = AppSettingsDialogsFactory()
|
||||
|
||||
add(dialogsFactory.createDeleteSavedAccessCodesAlert({}, {}))
|
||||
add(dialogsFactory.createDeleteSavedWalletsAlert({}, {}))
|
||||
add(dialogsFactory.createThemeModeSelectorDialog(selectedModeIndex = 0, onSelect = {}, onDismiss = {}))
|
||||
add(dialogsFactory.createDisableBiometricAuthenticationAlert(onDisable = {}, onDismiss = {}))
|
||||
},
|
||||
)
|
||||
// endregion Preview
|
||||
|
|
@ -14,10 +14,7 @@ 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.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.settings.CanUseBiometryUseCase
|
||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Settings
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
|
|
@ -52,14 +49,11 @@ internal class AppSettingsModel @Inject constructor(
|
|||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val appCurrencyRepository: AppCurrencyRepository,
|
||||
private val walletsRepository: WalletsRepository,
|
||||
private val canUseBiometryUseCase: CanUseBiometryUseCase,
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val balanceHidingRepository: BalanceHidingRepository,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val appThemeModeRepository: AppThemeModeRepository,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val appSettingsItemsAnalyticsSender: AppSettingsItemsAnalyticsSender,
|
||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
private val uiMessageSender: UiMessageSender,
|
||||
) : Model(), StoreSubscriber<DetailsState> {
|
||||
|
||||
|
|
@ -120,47 +114,25 @@ internal class AppSettingsModel @Inject constructor(
|
|||
),
|
||||
)
|
||||
|
||||
if (hotWalletFeatureToggles.isHotWalletEnabled) {
|
||||
val canUseBiometrics =
|
||||
!state.needEnrollBiometrics && !state.isInProgress && state.hasSecuredWallets
|
||||
val canUseBiometrics =
|
||||
!state.needEnrollBiometrics && !state.isInProgress && state.hasSecuredWallets
|
||||
|
||||
add(
|
||||
itemsFactory.createUseBiometricsSwitch(
|
||||
isChecked = state.useBiometricAuthentication,
|
||||
isEnabled = canUseBiometrics,
|
||||
onCheckedChange = ::onBiometricAuthenticationToggled,
|
||||
onDisabledClick = ::onBiometricAuthenticationDisabledClicked,
|
||||
),
|
||||
)
|
||||
add(
|
||||
itemsFactory.createUseBiometricsSwitch(
|
||||
isChecked = state.useBiometricAuthentication,
|
||||
isEnabled = canUseBiometrics,
|
||||
onCheckedChange = ::onBiometricAuthenticationToggled,
|
||||
onDisabledClick = ::onBiometricAuthenticationDisabledClicked,
|
||||
),
|
||||
)
|
||||
|
||||
add(
|
||||
itemsFactory.createRequireAccessCodeSwitch(
|
||||
isChecked = state.requireAccessCode || !state.useBiometricAuthentication,
|
||||
isEnabled = canUseBiometrics && state.useBiometricAuthentication,
|
||||
onCheckedChange = ::onRequireAccessCodeToggled,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
if (state.isBiometricsAvailable) {
|
||||
val canUseBiometrics = !state.needEnrollBiometrics && !state.isInProgress
|
||||
|
||||
add(
|
||||
itemsFactory.createSaveWalletsSwitch(
|
||||
isChecked = state.saveWallets,
|
||||
isEnabled = canUseBiometrics,
|
||||
onCheckedChange = ::onSaveWalletsToggled,
|
||||
),
|
||||
)
|
||||
|
||||
add(
|
||||
itemsFactory.createSaveAccessCodeSwitch(
|
||||
isChecked = state.saveAccessCodes,
|
||||
isEnabled = canUseBiometrics,
|
||||
onCheckedChange = ::onSaveAccessCodesToggled,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
add(
|
||||
itemsFactory.createRequireAccessCodeSwitch(
|
||||
isChecked = state.requireAccessCode || !state.useBiometricAuthentication,
|
||||
isEnabled = canUseBiometrics && state.useBiometricAuthentication,
|
||||
onCheckedChange = ::onRequireAccessCodeToggled,
|
||||
),
|
||||
)
|
||||
|
||||
add(
|
||||
itemsFactory.createFlipToHideBalanceSwitch(
|
||||
|
|
@ -261,42 +233,6 @@ internal class AppSettingsModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun onSaveWalletsToggled(isChecked: Boolean) {
|
||||
if (isChecked) {
|
||||
onSettingsToggled(AppSetting.SaveWallets, enable = true)
|
||||
} else {
|
||||
updateContentState {
|
||||
copy(
|
||||
dialog = dialogsFactory.createDeleteSavedWalletsAlert(
|
||||
onDelete = {
|
||||
onSettingsToggled(AppSetting.SaveWallets, enable = false)
|
||||
dismissDialog()
|
||||
},
|
||||
onDismiss = ::dismissDialog,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSaveAccessCodesToggled(isChecked: Boolean) {
|
||||
if (isChecked) {
|
||||
onSettingsToggled(AppSetting.SaveAccessCode, enable = true)
|
||||
} else {
|
||||
updateContentState {
|
||||
copy(
|
||||
dialog = dialogsFactory.createDeleteSavedAccessCodesAlert(
|
||||
onDelete = {
|
||||
onSettingsToggled(AppSetting.SaveAccessCode, enable = false)
|
||||
dismissDialog()
|
||||
},
|
||||
onDismiss = ::dismissDialog,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSettingsToggled(setting: AppSetting, enable: Boolean) {
|
||||
store.dispatch(DetailsAction.AppSettings.SwitchPrivacySetting(enable = enable, setting = setting))
|
||||
}
|
||||
|
|
@ -326,9 +262,6 @@ internal class AppSettingsModel @Inject constructor(
|
|||
|
||||
private fun bootstrapBiometricsUpdates() = modelScope.launch {
|
||||
val state = AppSettingsState(
|
||||
saveWallets = walletsRepository.shouldSaveUserWalletsSync(),
|
||||
saveAccessCodes = settingsRepository.shouldSaveAccessCodes(),
|
||||
isBiometricsAvailable = canUseBiometryUseCase(),
|
||||
useBiometricAuthentication = walletsRepository.useBiometricAuthentication(),
|
||||
requireAccessCode = walletsRepository.requireAccessCode(),
|
||||
isHidingEnabled = balanceHidingRepository.getBalanceHidingSettings().isHidingEnabledInSettings,
|
||||
|
|
|
|||
|
|
@ -14,12 +14,9 @@ import com.tangem.domain.card.ResetCardUseCase
|
|||
import com.tangem.domain.card.ResetCardUserCodeParams
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.wallet.requireColdWallet
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.legacy.asLockable
|
||||
import com.tangem.domain.wallets.usecase.DeleteWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import com.tangem.tap.common.analytics.events.Settings
|
||||
import com.tangem.tap.common.extensions.dispatchNavigationAction
|
||||
import com.tangem.tap.common.extensions.onUserWalletSelected
|
||||
|
|
@ -52,10 +49,8 @@ internal class ResetCardModel @Inject constructor(
|
|||
private val resetCardUseCase: ResetCardUseCase,
|
||||
private val deleteSavedAccessCodesUseCase: DeleteSavedAccessCodesUseCase,
|
||||
private val deleteWalletUseCase: DeleteWalletUseCase,
|
||||
private val userWalletsListManager: UserWalletsListManager,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val cardSettingsInteractor: CardSettingsInteractor,
|
||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
) : Model() {
|
||||
|
||||
private val params = paramsContainer.require<ResetCardComponent.Params>()
|
||||
|
|
@ -277,16 +272,7 @@ internal class ResetCardModel @Inject constructor(
|
|||
if (newSelectedWallet != null) {
|
||||
store.dispatchNavigationAction { popTo<AppRoute.Wallet>() }
|
||||
} else {
|
||||
if (hotWalletFeatureToggles.isHotWalletEnabled) {
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Home()) }
|
||||
} else {
|
||||
val isLocked = runCatching { userWalletsListManager.asLockable()?.isLocked }.isSuccess
|
||||
if (isLocked && userWalletsListManager.hasUserWallets) {
|
||||
store.dispatchNavigationAction { popTo<AppRoute.Welcome>() }
|
||||
} else {
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Home()) }
|
||||
}
|
||||
}
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Home()) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,36 +0,0 @@
|
|||
package com.tangem.tap.features.welcome.component
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.tap.features.welcome.model.WelcomeModel
|
||||
import com.tangem.tap.features.welcome.ui.components.WelcomeScreen
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class DefaultWelcomeComponent @AssistedInject constructor(
|
||||
@Assisted context: AppComponentContext,
|
||||
@Assisted params: WelcomeComponent.Params,
|
||||
) : WelcomeComponent, AppComponentContext by context {
|
||||
|
||||
private val model: WelcomeModel = getOrCreateModel(params)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.state.collectAsStateWithLifecycle()
|
||||
|
||||
WelcomeScreen(
|
||||
modifier = modifier,
|
||||
state = state,
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : WelcomeComponent.Factory {
|
||||
override fun create(context: AppComponentContext, params: WelcomeComponent.Params): DefaultWelcomeComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
package com.tangem.tap.features.welcome.component
|
||||
|
||||
import com.tangem.common.routing.entity.InitScreenLaunchMode
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
|
||||
interface WelcomeComponent : ComposableContentComponent {
|
||||
|
||||
data class Params(
|
||||
val launchMode: InitScreenLaunchMode,
|
||||
)
|
||||
|
||||
interface Factory : ComponentFactory<Params, WelcomeComponent>
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
package com.tangem.tap.features.welcome.component.impl
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.tap.features.welcome.component.WelcomeComponent
|
||||
import com.tangem.tap.features.welcome.ui.WelcomeScreenState
|
||||
import com.tangem.tap.features.welcome.ui.components.WelcomeScreen
|
||||
|
||||
internal class PreviewWelcomeComponent(
|
||||
private val initialState: WelcomeScreenState = WelcomeScreenState(),
|
||||
) : WelcomeComponent {
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
WelcomeScreen(
|
||||
modifier = modifier,
|
||||
state = initialState,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
package com.tangem.tap.features.welcome.di
|
||||
|
||||
import com.tangem.tap.features.welcome.component.DefaultWelcomeComponent
|
||||
import com.tangem.tap.features.welcome.component.WelcomeComponent
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface ComponentModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindWelcomeComponentFactory(factory: DefaultWelcomeComponent.Factory): WelcomeComponent.Factory
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
package com.tangem.tap.features.welcome.di
|
||||
|
||||
import com.tangem.core.decompose.di.ModelComponent
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.tap.features.welcome.model.WelcomeModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
|
||||
@Module
|
||||
@InstallIn(ModelComponent::class)
|
||||
internal interface ModelModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(WelcomeModel::class)
|
||||
fun bindWelcomeModel(model: WelcomeModel): Model
|
||||
}
|
||||
|
|
@ -1,140 +0,0 @@
|
|||
package com.tangem.tap.features.welcome.model
|
||||
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.common.routing.entity.InitScreenLaunchMode
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.navigation.finisher.AppFinisher
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListError
|
||||
import com.tangem.tap.common.analytics.events.SignIn
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.tap.features.welcome.component.WelcomeComponent
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeAction
|
||||
import com.tangem.tap.features.welcome.redux.WelcomeState
|
||||
import com.tangem.tap.features.welcome.ui.WelcomeScreenState
|
||||
import com.tangem.tap.features.welcome.ui.model.WarningModel
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import org.rekotlin.StoreSubscriber
|
||||
import javax.inject.Inject
|
||||
|
||||
// FIXME: Remove redux: [REDACTED_JIRA]
|
||||
@ModelScoped
|
||||
internal class WelcomeModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val appFinisher: AppFinisher,
|
||||
private val analyticsEventsHandler: AnalyticsEventHandler,
|
||||
paramsContainer: ParamsContainer,
|
||||
) : Model(), StoreSubscriber<WelcomeState> {
|
||||
|
||||
private val params: WelcomeComponent.Params = paramsContainer.require()
|
||||
private val initialState: WelcomeScreenState = WelcomeScreenState(
|
||||
onPopBack = appFinisher::finish,
|
||||
onUnlockClick = this::unlockWallets,
|
||||
onScanCardClick = this::scanCard,
|
||||
onCloseError = this::closeError,
|
||||
)
|
||||
|
||||
val state: MutableStateFlow<WelcomeScreenState> = MutableStateFlow(initialState)
|
||||
|
||||
init {
|
||||
subscribeToStoreChanges()
|
||||
initGlobalState()
|
||||
|
||||
val welcomeAction = when (params.launchMode) {
|
||||
is InitScreenLaunchMode.WithCardScan -> WelcomeAction.ProceedWithCard
|
||||
is InitScreenLaunchMode.Standard -> WelcomeAction.ProceedWithBiometrics
|
||||
}
|
||||
|
||||
store.dispatch(welcomeAction)
|
||||
}
|
||||
|
||||
private fun unlockWallets() {
|
||||
analyticsEventsHandler.send(SignIn.ButtonBiometricSignIn())
|
||||
store.dispatch(WelcomeAction.ProceedWithBiometrics)
|
||||
}
|
||||
|
||||
private fun scanCard() {
|
||||
analyticsEventsHandler.send(SignIn.ButtonCardSignIn())
|
||||
store.dispatch(WelcomeAction.ProceedWithCard)
|
||||
}
|
||||
|
||||
private fun closeError() {
|
||||
store.dispatch(WelcomeAction.CloseError)
|
||||
}
|
||||
|
||||
override fun newState(state: WelcomeState) {
|
||||
val warning = createWarningIfNeeded(state.error)
|
||||
|
||||
this.state.update { prevState ->
|
||||
prevState.copy(
|
||||
isUnlockWithBiometricsProgressVisible = state.isUnlockWithBiometricsInProgress,
|
||||
isUnlockWithCardProgressVisible = state.isUnlockWithCardInProgress,
|
||||
warning = warning,
|
||||
error = state.error
|
||||
?.takeIf { !it.silent && warning == null }
|
||||
?.let { error ->
|
||||
val messageResId = error.messageResId
|
||||
if (messageResId != null) {
|
||||
TextReference.Res(messageResId)
|
||||
} else {
|
||||
TextReference.Str(error.customMessage)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
store.unsubscribe(subscriber = this)
|
||||
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun createWarningIfNeeded(error: TangemError?): WarningModel? {
|
||||
return when (error) {
|
||||
is UserWalletsListError.BiometricsAuthenticationLockout -> WarningModel.BiometricsLockoutWarning(
|
||||
isPermanent = error.isPermanent,
|
||||
onDismiss = this::dismissWarning,
|
||||
)
|
||||
is UserWalletsListError.AllKeysInvalidated,
|
||||
is UserWalletsListError.NoUserWalletSelected,
|
||||
-> WarningModel.KeyInvalidatedWarning(
|
||||
onDismiss = this::dismissWarning,
|
||||
)
|
||||
is UserWalletsListError.BiometricsAuthenticationDisabled -> WarningModel.BiometricsDisabledWarning(
|
||||
onDismiss = this::clearUserWallets,
|
||||
)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun dismissWarning() {
|
||||
state.update { prevState ->
|
||||
prevState.copy(
|
||||
warning = null,
|
||||
)
|
||||
}
|
||||
closeError()
|
||||
}
|
||||
|
||||
private fun clearUserWallets() {
|
||||
store.dispatch(WelcomeAction.ClearUserWallets)
|
||||
}
|
||||
|
||||
private fun subscribeToStoreChanges() {
|
||||
store.subscribe(this) { appState ->
|
||||
appState.skip { old, new -> old.welcomeState == new.welcomeState }
|
||||
.select { it.welcomeState }
|
||||
}
|
||||
}
|
||||
|
||||
private fun initGlobalState() {
|
||||
store.dispatch(GlobalAction.RestoreAppCurrency)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
package com.tangem.tap.features.welcome.redux
|
||||
|
||||
import com.tangem.common.core.TangemError
|
||||
import org.rekotlin.Action
|
||||
|
||||
internal sealed interface WelcomeAction : Action {
|
||||
|
||||
data object ProceedWithBiometrics : WelcomeAction {
|
||||
object Success : WelcomeAction
|
||||
data class Error(val error: TangemError) : WelcomeAction
|
||||
}
|
||||
|
||||
object ProceedWithCard : WelcomeAction {
|
||||
object Success : WelcomeAction
|
||||
data class Error(val error: TangemError) : WelcomeAction
|
||||
data class ChangeProgress(val isProgress: Boolean) : WelcomeAction
|
||||
}
|
||||
|
||||
object CloseError : WelcomeAction
|
||||
|
||||
object ClearUserWallets : WelcomeAction
|
||||
}
|
||||
|
|
@ -1,164 +0,0 @@
|
|||
package com.tangem.tap.features.welcome.redux
|
||||
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.doOnFailure
|
||||
import com.tangem.common.doOnResult
|
||||
import com.tangem.common.doOnSuccess
|
||||
import com.tangem.common.flatMap
|
||||
import com.tangem.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.domain.card.analytics.ParamCardCurrencyConverter
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager.Lockable.UnlockType
|
||||
import com.tangem.domain.wallets.legacy.unlockIfLockable
|
||||
import com.tangem.tap.*
|
||||
import com.tangem.tap.common.extensions.*
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
import kotlinx.coroutines.launch
|
||||
import org.rekotlin.Middleware
|
||||
import timber.log.Timber
|
||||
|
||||
internal class WelcomeMiddleware {
|
||||
val middleware: Middleware<AppState> = { _, appStateProvider ->
|
||||
{ next ->
|
||||
{ action ->
|
||||
val appState = appStateProvider()
|
||||
if (action is WelcomeAction && appState != null) {
|
||||
handleAction(action)
|
||||
}
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAction(action: WelcomeAction) {
|
||||
mainScope.launch {
|
||||
when (action) {
|
||||
is WelcomeAction.ProceedWithBiometrics -> proceedWithBiometrics()
|
||||
is WelcomeAction.ProceedWithCard -> proceedWithCard()
|
||||
is WelcomeAction.ClearUserWallets -> disableUserWalletsSaving()
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun proceedWithBiometrics() {
|
||||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
userWalletsListManager.unlockIfLockable(type = UnlockType.ANY)
|
||||
.doOnFailure { error ->
|
||||
Timber.e(error, "Unable to unlock user wallets with biometrics")
|
||||
store.dispatchWithMain(WelcomeAction.ProceedWithBiometrics.Error(error))
|
||||
}
|
||||
.doOnSuccess { selectedUserWallet ->
|
||||
sendSignedInAnalyticsEvent(
|
||||
userWallet = selectedUserWallet,
|
||||
signInType = Basic.SignedInLegacy.SignInType.Biometric,
|
||||
)
|
||||
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Wallet) }
|
||||
store.dispatchWithMain(WelcomeAction.ProceedWithBiometrics.Success)
|
||||
store.onUserWalletSelected(userWallet = selectedUserWallet)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun proceedWithCard() {
|
||||
scanCardInternal { scanResponse ->
|
||||
val userWalletBuilder = store.inject(DaggerGraphState::coldUserWalletBuilderFactory).create(scanResponse)
|
||||
|
||||
val userWallet = userWalletBuilder.build() ?: return@scanCardInternal
|
||||
|
||||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
userWalletsListManager.save(userWallet, canOverride = true)
|
||||
.doOnFailure { error ->
|
||||
Timber.e(error, "Unable to save user wallet")
|
||||
store.dispatchWithMain(WelcomeAction.ProceedWithCard.Error(error))
|
||||
}
|
||||
.doOnSuccess {
|
||||
sendSignedInAnalyticsEvent(userWallet, signInType = Basic.SignedInLegacy.SignInType.Card)
|
||||
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Wallet) }
|
||||
store.dispatchWithMain(WelcomeAction.ProceedWithCard.Success)
|
||||
store.onUserWalletSelected(userWallet = userWallet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendSignedInAnalyticsEvent(userWallet: UserWallet, signInType: Basic.SignedInLegacy.SignInType) {
|
||||
if (userWallet !is UserWallet.Cold) {
|
||||
return
|
||||
}
|
||||
|
||||
val scanResponse = userWallet.scanResponse
|
||||
val currency = ParamCardCurrencyConverter().convert(
|
||||
value = scanResponse.cardTypesResolver,
|
||||
)
|
||||
|
||||
val trackingContextProxy = store.inject(DaggerGraphState::trackingContextProxy)
|
||||
trackingContextProxy.addContext(scanResponse)
|
||||
|
||||
if (currency != null) {
|
||||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
|
||||
Analytics.send(
|
||||
event = Basic.SignedInLegacy(
|
||||
currency = currency,
|
||||
batch = scanResponse.card.batchId,
|
||||
signInType = signInType,
|
||||
walletsCount = userWalletsListManager.walletsCount.toString(),
|
||||
isImported = userWallet.isImported,
|
||||
hasBackup = scanResponse.card.backupStatus?.isActive,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun disableUserWalletsSaving() {
|
||||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||
userWalletsListManager.clear()
|
||||
.flatMap { tangemSdkManager.clearSavedUserCodes() }
|
||||
.doOnFailure { e ->
|
||||
Timber.e(e, "Unable to clear user wallets")
|
||||
}
|
||||
.doOnResult {
|
||||
store.dispatchWithMain(WelcomeAction.CloseError)
|
||||
store.dispatchNavigationAction { popTo<AppRoute.Home>() }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend inline fun scanCardInternal(crossinline onCardScanned: suspend (ScanResponse) -> Unit) {
|
||||
val shouldSaveAccessCodes = store.inject(DaggerGraphState::settingsRepository).shouldSaveAccessCodes()
|
||||
|
||||
store.inject(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy(
|
||||
isBiometricsRequestPolicy = shouldSaveAccessCodes,
|
||||
)
|
||||
|
||||
store.inject(DaggerGraphState::scanCardProcessor).scan(
|
||||
analyticsSource = AnalyticsParam.ScreensSources.SignIn,
|
||||
onSuccess = { scanResponse ->
|
||||
scope.launch { onCardScanned(scanResponse) }
|
||||
},
|
||||
onFailure = { error ->
|
||||
when (error) {
|
||||
is TangemSdkError.ExceptionError -> {
|
||||
store.dispatchOnMain(WelcomeAction.ProceedWithCard.Success)
|
||||
}
|
||||
else -> {
|
||||
store.dispatchOnMain(WelcomeAction.ProceedWithCard.Error(error))
|
||||
}
|
||||
}
|
||||
},
|
||||
onProgressStateChange = {
|
||||
store.dispatchWithMain(WelcomeAction.ProceedWithCard.ChangeProgress(it))
|
||||
},
|
||||
onWalletNotCreated = {
|
||||
store.dispatchOnMain(WelcomeAction.ProceedWithCard.Success)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
package com.tangem.tap.features.welcome.redux
|
||||
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import org.rekotlin.Action
|
||||
|
||||
internal object WelcomeReducer {
|
||||
fun reduce(action: Action, state: AppState): WelcomeState {
|
||||
return if (action is WelcomeAction) {
|
||||
internalReduce(action, state.welcomeState)
|
||||
} else {
|
||||
state.welcomeState
|
||||
}
|
||||
}
|
||||
|
||||
private fun internalReduce(action: WelcomeAction, state: WelcomeState): WelcomeState {
|
||||
return when (action) {
|
||||
is WelcomeAction.ProceedWithBiometrics -> state.copy(isUnlockWithBiometricsInProgress = true)
|
||||
is WelcomeAction.ProceedWithCard -> state.copy(isUnlockWithCardInProgress = true)
|
||||
is WelcomeAction.ProceedWithBiometrics.Error -> state.copy(
|
||||
error = action.error,
|
||||
isUnlockWithBiometricsInProgress = false,
|
||||
)
|
||||
is WelcomeAction.ProceedWithCard.Error -> state.copy(
|
||||
error = action.error,
|
||||
isUnlockWithCardInProgress = false,
|
||||
)
|
||||
is WelcomeAction.ProceedWithCard.ChangeProgress -> state.copy(
|
||||
isUnlockWithCardInProgress = action.isProgress,
|
||||
)
|
||||
is WelcomeAction.ProceedWithBiometrics.Success -> state.copy(isUnlockWithBiometricsInProgress = false)
|
||||
is WelcomeAction.ProceedWithCard.Success -> state.copy(isUnlockWithCardInProgress = false)
|
||||
is WelcomeAction.CloseError -> state.copy(error = null)
|
||||
else -> state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
package com.tangem.tap.features.welcome.redux
|
||||
|
||||
import com.tangem.common.core.TangemError
|
||||
import org.rekotlin.StateType
|
||||
|
||||
data class WelcomeState(
|
||||
val isUnlockWithBiometricsInProgress: Boolean = false,
|
||||
val isUnlockWithCardInProgress: Boolean = false,
|
||||
val error: TangemError? = null,
|
||||
) : StateType
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
package com.tangem.tap.features.welcome.ui
|
||||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.tap.features.welcome.ui.model.WarningModel
|
||||
|
||||
internal data class WelcomeScreenState(
|
||||
val onPopBack: () -> Unit = {},
|
||||
val isUnlockWithBiometricsProgressVisible: Boolean = false,
|
||||
val isUnlockWithCardProgressVisible: Boolean = false,
|
||||
val warning: WarningModel? = null,
|
||||
val error: TextReference? = null,
|
||||
val onUnlockClick: () -> Unit = {},
|
||||
val onScanCardClick: () -> Unit = {},
|
||||
val onCloseError: () -> Unit = {},
|
||||
)
|
||||
|
|
@ -1,136 +0,0 @@
|
|||
package com.tangem.tap.features.welcome.ui.components
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.BasicDialog
|
||||
import com.tangem.core.ui.components.DialogButtonUM
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.tap.features.welcome.ui.model.WarningModel
|
||||
import com.tangem.wallet.R
|
||||
|
||||
@Composable
|
||||
internal fun WarningDialog(warning: WarningModel?) {
|
||||
when (warning) {
|
||||
null -> Unit
|
||||
is WarningModel.BiometricsLockoutWarning -> {
|
||||
BasicDialog(
|
||||
title = stringResourceSafe(id = R.string.biometric_lockout_warning_title),
|
||||
message = stringResourceSafe(
|
||||
id = if (warning.isPermanent) {
|
||||
R.string.biometric_lockout_permanent_warning_description
|
||||
} else {
|
||||
R.string.biometric_lockout_warning_description
|
||||
},
|
||||
),
|
||||
onDismissDialog = warning.onDismiss,
|
||||
confirmButton = DialogButtonUM(
|
||||
title = stringResourceSafe(id = R.string.common_ok),
|
||||
onClick = warning.onDismiss,
|
||||
),
|
||||
)
|
||||
}
|
||||
is WarningModel.KeyInvalidatedWarning -> {
|
||||
BasicDialog(
|
||||
title = stringResourceSafe(id = R.string.common_attention),
|
||||
message = stringResourceSafe(id = R.string.key_invalidated_warning_description),
|
||||
onDismissDialog = warning.onDismiss,
|
||||
confirmButton = DialogButtonUM(
|
||||
title = stringResourceSafe(id = R.string.common_ok),
|
||||
onClick = warning.onDismiss,
|
||||
),
|
||||
)
|
||||
}
|
||||
is WarningModel.BiometricsDisabledWarning -> {
|
||||
BasicDialog(
|
||||
title = stringResourceSafe(id = R.string.common_warning),
|
||||
message = stringResourceSafe(id = R.string.biometric_unavailable_warning),
|
||||
onDismissDialog = warning.onDismiss,
|
||||
isDismissable = false,
|
||||
confirmButton = DialogButtonUM(
|
||||
title = stringResourceSafe(id = R.string.common_ok),
|
||||
onClick = warning.onDismiss,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
private fun BiometricsLockoutDialogSample(modifier: Modifier = Modifier) {
|
||||
Column(modifier = modifier) {
|
||||
WarningDialog(
|
||||
warning = WarningModel.BiometricsLockoutWarning(
|
||||
isPermanent = false,
|
||||
onDismiss = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun BiometricsLockoutDialogPreview() {
|
||||
TangemThemePreview {
|
||||
BiometricsLockoutDialogSample()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BiometricsLockoutDialog_Permanent_Sample(modifier: Modifier = Modifier) {
|
||||
Column(modifier = modifier) {
|
||||
WarningDialog(
|
||||
warning = WarningModel.BiometricsLockoutWarning(
|
||||
isPermanent = true,
|
||||
onDismiss = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun BiometricsLockoutDialog_Permanent_Preview() {
|
||||
TangemThemePreview {
|
||||
BiometricsLockoutDialog_Permanent_Sample()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun KeyInvalidatedWarningSample(modifier: Modifier = Modifier) {
|
||||
Column(modifier = modifier) {
|
||||
WarningDialog(warning = WarningModel.KeyInvalidatedWarning(onDismiss = {}))
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun KeyInvalidatedWarningPreview() {
|
||||
TangemThemePreview {
|
||||
KeyInvalidatedWarningSample()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BiometricDisabledWarningSample(modifier: Modifier = Modifier) {
|
||||
Column(modifier = modifier) {
|
||||
WarningDialog(warning = WarningModel.BiometricsDisabledWarning(onDismiss = {}))
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun BiometricDisabledWarningPreview() {
|
||||
TangemThemePreview {
|
||||
BiometricDisabledWarningSample()
|
||||
}
|
||||
}
|
||||
// endregion Preview
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
package com.tangem.tap.features.welcome.ui.components
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.tap.features.welcome.component.WelcomeComponent
|
||||
import com.tangem.tap.features.welcome.component.impl.PreviewWelcomeComponent
|
||||
import com.tangem.tap.features.welcome.ui.WelcomeScreenState
|
||||
import com.tangem.tap.features.welcome.ui.model.WarningModel
|
||||
|
||||
@Composable
|
||||
internal fun WelcomeScreen(state: WelcomeScreenState, modifier: Modifier = Modifier) {
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
val errorMessage by rememberUpdatedState(newValue = state.error?.resolveReference())
|
||||
val warning by rememberUpdatedState(newValue = state.warning)
|
||||
|
||||
BackHandler(onBack = state.onPopBack)
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.background(TangemTheme.colors.background.primary)
|
||||
.systemBarsPadding(),
|
||||
) {
|
||||
WelcomeScreenContent(
|
||||
showUnlockProgress = state.isUnlockWithBiometricsProgressVisible,
|
||||
showScanCardProgress = state.isUnlockWithCardProgressVisible,
|
||||
onUnlockClick = state.onUnlockClick,
|
||||
onScanCardClick = state.onScanCardClick,
|
||||
)
|
||||
|
||||
SnackbarHost(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.padding(vertical = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
hostState = snackbarHostState,
|
||||
)
|
||||
}
|
||||
|
||||
WarningDialog(warning)
|
||||
|
||||
LaunchedEffect(errorMessage, state.onCloseError) {
|
||||
errorMessage?.let { message ->
|
||||
snackbarHostState.showSnackbar(message)
|
||||
state.onCloseError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun Preview_WelcomeScreen(
|
||||
@PreviewParameter(WelcomeComponentPreviewProvider::class) component: WelcomeComponent,
|
||||
) {
|
||||
TangemThemePreview {
|
||||
component.Content(Modifier)
|
||||
}
|
||||
}
|
||||
|
||||
private class WelcomeComponentPreviewProvider : PreviewParameterProvider<WelcomeComponent> {
|
||||
override val values: Sequence<WelcomeComponent>
|
||||
get() = sequenceOf(
|
||||
PreviewWelcomeComponent(),
|
||||
PreviewWelcomeComponent(
|
||||
initialState = WelcomeScreenState(
|
||||
isUnlockWithBiometricsProgressVisible = true,
|
||||
isUnlockWithCardProgressVisible = true,
|
||||
),
|
||||
),
|
||||
PreviewWelcomeComponent(
|
||||
initialState = WelcomeScreenState(
|
||||
error = TextReference.Str(value = "Error"),
|
||||
),
|
||||
),
|
||||
PreviewWelcomeComponent(
|
||||
initialState = WelcomeScreenState(
|
||||
warning = WarningModel.KeyInvalidatedWarning(onDismiss = {}),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
// endregion Preview
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
package com.tangem.tap.features.welcome.ui.components
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.*
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.wallet.R
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
internal fun WelcomeScreenContent(
|
||||
showUnlockProgress: Boolean,
|
||||
showScanCardProgress: Boolean,
|
||||
onUnlockClick: () -> Unit,
|
||||
onScanCardClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
SpacerHMax()
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size96),
|
||||
painter = painterResource(id = R.drawable.img_tangem_logo_96),
|
||||
tint = TangemTheme.colors.icon.primary1,
|
||||
contentDescription = null,
|
||||
)
|
||||
SpacerH32()
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.fillMaxWidth(),
|
||||
text = stringResourceSafe(R.string.welcome_unlock_title),
|
||||
style = TangemTheme.typography.h2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
SpacerH12()
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing44)
|
||||
.fillMaxWidth(),
|
||||
text = stringResourceSafe(
|
||||
id = R.string.welcome_unlock_description,
|
||||
stringResourceSafe(id = R.string.common_biometric_authentication),
|
||||
),
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
SpacerHMax()
|
||||
SecondaryButton(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.fillMaxWidth(),
|
||||
text = stringResourceSafe(
|
||||
id = R.string.welcome_unlock,
|
||||
stringResourceSafe(id = R.string.common_biometrics),
|
||||
),
|
||||
showProgress = showUnlockProgress,
|
||||
onClick = onUnlockClick,
|
||||
)
|
||||
SpacerH12()
|
||||
PrimaryButtonIconEnd(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16)
|
||||
.fillMaxWidth(),
|
||||
text = stringResourceSafe(R.string.welcome_unlock_card),
|
||||
showProgress = showScanCardProgress,
|
||||
iconResId = R.drawable.ic_tangem_24,
|
||||
onClick = onScanCardClick,
|
||||
)
|
||||
SpacerH16()
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
private fun WelcomeScreenContentSample(modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.background(TangemTheme.colors.background.primary),
|
||||
) {
|
||||
WelcomeScreenContent(
|
||||
showUnlockProgress = false,
|
||||
showScanCardProgress = false,
|
||||
onUnlockClick = { /* no-op */ },
|
||||
onScanCardClick = { /* no-op */ },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun WelcomeScreenContentPreview() {
|
||||
TangemThemePreview {
|
||||
WelcomeScreenContentSample()
|
||||
}
|
||||
}
|
||||
// endregion Preview
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
package com.tangem.tap.features.welcome.ui.model
|
||||
|
||||
internal sealed interface WarningModel {
|
||||
data class BiometricsLockoutWarning(
|
||||
val isPermanent: Boolean,
|
||||
val onDismiss: () -> Unit,
|
||||
) : WarningModel
|
||||
|
||||
data class KeyInvalidatedWarning(
|
||||
val onDismiss: () -> Unit,
|
||||
) : WarningModel
|
||||
|
||||
data class BiometricsDisabledWarning(
|
||||
val onDismiss: () -> Unit,
|
||||
) : WarningModel
|
||||
}
|
||||
|
|
@ -6,14 +6,11 @@ import com.tangem.datasource.api.common.config.ApiEnvironment
|
|||
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.ProviderSuspend
|
||||
|
||||
internal class DefaultAuthProvider(
|
||||
private val userWalletsListManager: UserWalletsListManager,
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val shouldUseNewListRepository: Boolean = false,
|
||||
private val environmentConfigStorage: EnvironmentConfigStorage,
|
||||
) : AuthProvider {
|
||||
|
||||
|
|
@ -71,18 +68,10 @@ internal class DefaultAuthProvider(
|
|||
}
|
||||
|
||||
private suspend fun getWallets(): List<UserWallet> {
|
||||
return if (shouldUseNewListRepository) {
|
||||
userWalletsListRepository.userWalletsSync()
|
||||
} else {
|
||||
userWalletsListManager.userWalletsSync
|
||||
}
|
||||
return userWalletsListRepository.userWalletsSync()
|
||||
}
|
||||
|
||||
private suspend fun getSelectedWallet(): UserWallet? {
|
||||
return if (shouldUseNewListRepository) {
|
||||
userWalletsListRepository.selectedUserWalletSync()
|
||||
} else {
|
||||
userWalletsListManager.selectedUserWalletSync
|
||||
}
|
||||
return userWalletsListRepository.selectedUserWalletSync()
|
||||
}
|
||||
}
|
||||
|
|
@ -3,16 +3,10 @@ package com.tangem.tap.network.auth.di
|
|||
import com.tangem.datasource.api.common.AuthProvider
|
||||
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import com.tangem.lib.auth.ExpressAuthProvider
|
||||
import com.tangem.lib.auth.P2PEthPoolAuthProvider
|
||||
import com.tangem.lib.auth.StakeKitAuthProvider
|
||||
import com.tangem.tap.network.auth.DefaultAppVersionProvider
|
||||
import com.tangem.tap.network.auth.DefaultAuthProvider
|
||||
import com.tangem.tap.network.auth.DefaultExpressAuthProvider
|
||||
import com.tangem.tap.network.auth.DefaultP2PEthPoolAuthProvider
|
||||
import com.tangem.tap.network.auth.DefaultStakeKitAuthProvider
|
||||
import com.tangem.tap.network.auth.*
|
||||
import com.tangem.utils.version.AppVersionProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -27,15 +21,11 @@ internal class AuthModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideAuthProvider(
|
||||
userWalletsListManager: UserWalletsListManager,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
environmentConfigStorage: EnvironmentConfigStorage,
|
||||
): AuthProvider {
|
||||
return DefaultAuthProvider(
|
||||
userWalletsListManager = userWalletsListManager,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
shouldUseNewListRepository = hotWalletFeatureToggles.isHotWalletEnabled,
|
||||
environmentConfigStorage = environmentConfigStorage,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,9 +31,7 @@ import com.tangem.domain.onboarding.repository.OnboardingRepository
|
|||
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
||||
import com.tangem.hot.sdk.TangemHotSdk
|
||||
import com.tangem.operations.attestation.CardArtworksProvider
|
||||
|
|
@ -53,7 +51,6 @@ data class DaggerGraphState(
|
|||
val appThemeModeRepository: AppThemeModeRepository? = null,
|
||||
val balanceHidingRepository: BalanceHidingRepository? = null,
|
||||
val walletsRepository: WalletsRepository? = null,
|
||||
val generalUserWalletsListManager: UserWalletsListManager? = null,
|
||||
val wasTwinsOnboardingShownUseCase: WasTwinsOnboardingShownUseCase? = null,
|
||||
val saveTwinsOnboardingShownUseCase: SaveTwinsOnboardingShownUseCase? = null,
|
||||
val cardRepository: CardRepository? = null,
|
||||
|
|
@ -78,7 +75,6 @@ data class DaggerGraphState(
|
|||
val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory? = null,
|
||||
val userTokensResponseStore: UserTokensResponseStore? = null,
|
||||
val userWalletsListRepository: UserWalletsListRepository? = null,
|
||||
val hotWalletFeatureToggles: HotWalletFeatureToggles? = null,
|
||||
val tangemHotSdk: TangemHotSdk? = null,
|
||||
val trackingContextProxy: TrackingContextProxy? = null,
|
||||
) : StateType
|
||||
|
|
@ -29,7 +29,6 @@ import com.tangem.domain.common.wallets.UserWalletsListRepository
|
|||
import com.tangem.domain.models.wallet.isLocked
|
||||
import com.tangem.domain.onboarding.repository.OnboardingRepository
|
||||
import com.tangem.features.hotwallet.HotAccessCodeRequestComponent
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import com.tangem.features.hotwallet.accesscoderequest.proxy.HotWalletPasswordRequesterProxy
|
||||
import com.tangem.features.walletconnect.components.WcRoutingComponent
|
||||
import com.tangem.hot.sdk.TangemHotSdk
|
||||
|
|
@ -69,7 +68,6 @@ internal class DefaultRoutingComponent @AssistedInject constructor(
|
|||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val cardRepository: CardRepository,
|
||||
private val onboardingRepository: OnboardingRepository,
|
||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
private val trackingContextProxy: TrackingContextProxy,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val analyticsExceptionHandler: AnalyticsExceptionHandler,
|
||||
|
|
@ -254,16 +252,14 @@ internal class DefaultRoutingComponent @AssistedInject constructor(
|
|||
}
|
||||
|
||||
private suspend fun trackSignInEvent() {
|
||||
if (hotWalletFeatureToggles.isHotWalletEnabled) {
|
||||
val userWallets = userWalletsListRepository.userWalletsSync()
|
||||
val selectedWallet = userWalletsListRepository.selectedUserWalletSync() ?: return
|
||||
trackingContextProxy.addContext(selectedWallet)
|
||||
analyticsEventHandler.send(
|
||||
event = Basic.SignedIn(
|
||||
signInType = Basic.SignedIn.SignInType.NoSecurity,
|
||||
walletsCount = userWallets.size,
|
||||
),
|
||||
)
|
||||
}
|
||||
val userWallets = userWalletsListRepository.userWalletsSync()
|
||||
val selectedWallet = userWalletsListRepository.selectedUserWalletSync() ?: return
|
||||
trackingContextProxy.addContext(selectedWallet)
|
||||
analyticsEventHandler.send(
|
||||
event = Basic.SignedIn(
|
||||
signInType = Basic.SignedIn.SignInType.NoSecurity,
|
||||
walletsCount = userWallets.size,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -52,7 +52,6 @@ import com.tangem.tap.features.details.ui.cardsettings.api.CardSettingsComponent
|
|||
import com.tangem.tap.features.details.ui.cardsettings.coderecovery.api.AccessCodeRecoveryComponent
|
||||
import com.tangem.tap.features.details.ui.resetcard.api.ResetCardComponent
|
||||
import com.tangem.tap.features.details.ui.securitymode.api.SecurityModeComponent
|
||||
import com.tangem.tap.features.welcome.component.WelcomeComponent
|
||||
import com.tangem.tap.routing.component.RoutingComponent.Child
|
||||
import dagger.hilt.android.scopes.ActivityScoped
|
||||
import javax.inject.Inject
|
||||
|
|
@ -76,7 +75,6 @@ internal class ChildFactory @Inject constructor(
|
|||
private val sellCryptoComponentFactory: SellCryptoComponent.Factory,
|
||||
private val swapSelectTokensComponentFactory: SwapSelectTokensComponent.Factory,
|
||||
private val onboardingEntryComponentFactory: OnboardingEntryComponent.Factory,
|
||||
private val welcomeComponentFactory: WelcomeComponent.Factory,
|
||||
private val newWelcomeComponentFactory: NewWelcomeComponent.Factory,
|
||||
private val storiesComponentFactory: StoriesComponent.Factory,
|
||||
private val stakingComponentFactory: StakingComponent.Factory,
|
||||
|
|
@ -118,7 +116,6 @@ internal class ChildFactory @Inject constructor(
|
|||
private val tangemPayOnboardingComponentFactory: TangemPayOnboardingComponent.Factory,
|
||||
private val kycComponentFactory: KycComponent.Factory,
|
||||
private val yieldSupplyEntryComponentFactory: YieldSupplyEntryComponent.Factory,
|
||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
private val feedEntryComponentFactory: FeedEntryComponent.Factory,
|
||||
private val feedFeatureToggle: FeedFeatureToggle,
|
||||
) {
|
||||
|
|
@ -162,21 +159,11 @@ internal class ChildFactory @Inject constructor(
|
|||
)
|
||||
}
|
||||
is AppRoute.Welcome -> {
|
||||
if (hotWalletFeatureToggles.isHotWalletEnabled) {
|
||||
createComponentChild(
|
||||
context = context,
|
||||
params = Unit,
|
||||
componentFactory = newWelcomeComponentFactory,
|
||||
)
|
||||
} else {
|
||||
createComponentChild(
|
||||
context = context,
|
||||
params = WelcomeComponent.Params(
|
||||
launchMode = route.launchMode,
|
||||
),
|
||||
componentFactory = welcomeComponentFactory,
|
||||
)
|
||||
}
|
||||
createComponentChild(
|
||||
context = context,
|
||||
params = Unit,
|
||||
componentFactory = newWelcomeComponentFactory,
|
||||
)
|
||||
}
|
||||
is AppRoute.WalletSettings -> {
|
||||
createComponentChild(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue