Updated on 2026-08-14

This commit is contained in:
Tangem 2023-10-10 14:50:19 +08:00
parent e2791d9b35
commit afcde269ce
24 changed files with 243 additions and 131 deletions

View file

@ -34,6 +34,7 @@ import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.legacy.WalletManagersRepository
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.features.tokendetails.featuretoggles.TokenDetailsFeatureToggles
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
import com.tangem.tap.common.analytics.AnalyticsFactory
@ -44,6 +45,7 @@ import com.tangem.tap.common.analytics.handlers.appsFlyer.AppsFlyerAnalyticsHand
import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAnalyticsHandler
import com.tangem.tap.common.analytics.topup.TopUpController
import com.tangem.tap.common.chat.ChatManager
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.feedback.AdditionalFeedbackInfo
import com.tangem.tap.common.feedback.FeedbackManager
import com.tangem.tap.common.images.createCoilImageLoader
@ -207,34 +209,15 @@ internal class TapApplication : Application(), ImageLoaderFactory {
@Inject
lateinit var getAppThemeModeUseCase: GetAppThemeModeUseCase
@Inject
lateinit var walletsRepository: WalletsRepository
// endregion Injected
override fun onCreate() {
super.onCreate()
store = Store(
reducer = { action, state -> appReducer(action, state, appStateHolder) },
middleware = AppState.getMiddleware(),
state = AppState(
daggerGraphState = DaggerGraphState(
assetReader = assetReader,
networkConnectionManager = networkConnectionManager,
customTokenFeatureToggles = customTokenFeatureToggles,
walletFeatureToggles = walletFeatureToggles,
walletConnectRepository = walletConnect2Repository,
walletConnectSessionsRepository = walletConnectSessionsRepository,
tokenDetailsFeatureToggles = tokenDetailsFeatureToggles,
scanCardProcessor = scanCardProcessor,
appCurrencyRepository = appCurrencyRepository,
walletManagersFacade = walletManagersFacade,
appStateHolder = appStateHolder,
currenciesRepository = currenciesRepository,
appThemeModeRepository = appThemeModeRepository,
balanceHidingRepository = balanceHidingRepository,
detailsFeatureToggles = detailsFeatureToggles,
),
),
)
store = createReduxStore()
if (BuildConfig.DEBUG) {
Logger.addLogAdapter(AndroidLogAdapter(TimberFormatStrategy()))
@ -254,17 +237,17 @@ internal class TapApplication : Application(), ImageLoaderFactory {
preferencesStorage = preferencesDataSource
walletConnectRepository = WalletConnectRepository(this)
val configLoader = FeaturesLocalLoader(assetReader, MoshiConverter.sdkMoshi, BuildConfig.ENVIRONMENT)
initUserWalletsListManager()
// TODO: Try to performance and user experience.
// [REDACTED_JIRA]
runBlocking {
initUserWalletsListManager()
featureTogglesManager.init()
appRatingRepository.initialize()
walletsRepository.initialize()
// learn2earnInteractor.init()
}
val configLoader = FeaturesLocalLoader(assetReader, MoshiConverter.sdkMoshi, BuildConfig.ENVIRONMENT)
initConfigManager(configLoader, ::initWithConfigDependency)
initWarningMessagesManager()
@ -296,6 +279,33 @@ internal class TapApplication : Application(), ImageLoaderFactory {
walletConnect2Repository.init(projectId = configManager.config.walletConnectProjectId)
}
private fun createReduxStore(): Store<AppState> {
return Store(
reducer = { action, state -> appReducer(action, state, appStateHolder) },
middleware = AppState.getMiddleware(),
state = AppState(
daggerGraphState = DaggerGraphState(
assetReader = assetReader,
networkConnectionManager = networkConnectionManager,
customTokenFeatureToggles = customTokenFeatureToggles,
walletFeatureToggles = walletFeatureToggles,
walletConnectRepository = walletConnect2Repository,
walletConnectSessionsRepository = walletConnectSessionsRepository,
tokenDetailsFeatureToggles = tokenDetailsFeatureToggles,
scanCardProcessor = scanCardProcessor,
appCurrencyRepository = appCurrencyRepository,
walletManagersFacade = walletManagersFacade,
appStateHolder = appStateHolder,
currenciesRepository = currenciesRepository,
appThemeModeRepository = appThemeModeRepository,
balanceHidingRepository = balanceHidingRepository,
detailsFeatureToggles = detailsFeatureToggles,
walletsRepository = walletsRepository,
),
),
)
}
private fun initTopUpController() {
val topUpController = TopUpController(
scanResponseProvider = {
@ -356,11 +366,7 @@ internal class TapApplication : Application(), ImageLoaderFactory {
store: Store<AppState>,
) {
fun initAdditionalFeedbackInfo(context: Context): AdditionalFeedbackInfo {
return AdditionalFeedbackInfo(
userWalletsListManager = userWalletsListManager,
walletManagersFacade = walletManagersFacade,
walletFeatureToggles = walletFeatureToggles,
).apply {
return AdditionalFeedbackInfo().apply {
appVersion = try {
// TODO don't use deprecated method
val pInfo = context.packageManager.getPackageInfo(context.packageName, 0)
@ -403,13 +409,13 @@ internal class TapApplication : Application(), ImageLoaderFactory {
store.dispatch(GlobalAction.SetWarningManager(WarningMessagesManager()))
}
private fun initUserWalletsListManager() {
val manager = if (preferencesStorage.shouldSaveUserWallets) {
private suspend fun initUserWalletsListManager() {
val manager = if (walletsRepository.shouldSaveUserWalletsSync()) {
UserWalletsListManager.provideBiometricImplementation(applicationContext)
} else {
UserWalletsListManager.provideRuntimeImplementation()
}
store.dispatch(GlobalAction.UpdateUserWalletsListManager(manager))
store.dispatchOnMain(GlobalAction.UpdateUserWalletsListManager(manager))
}
}

View file

@ -6,35 +6,10 @@ import com.tangem.blockchain.common.address.Address
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.userwallets.UserWalletIdBuilder
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
import com.tangem.tap.common.extensions.stripZeroPlainString
import com.tangem.tap.scope
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.*
class AdditionalFeedbackInfo(
userWalletsListManager: UserWalletsListManager,
walletManagersFacade: WalletManagersFacade,
walletFeatureToggles: WalletFeatureToggles,
) {
init {
if (walletFeatureToggles.isRedesignedScreenEnabled) {
userWalletsListManager.selectedUserWallet
.distinctUntilChanged()
.onEach { userWallet ->
setCardInfo(data = userWallet.scanResponse)
walletManagersFacade.getAll(userWalletId = userWallet.walletId)
.onEach(::setWalletsInfo)
.launchIn(scope)
}
.launchIn(scope)
}
}
class AdditionalFeedbackInfo {
class EmailWalletInfo(
var blockchain: Blockchain = Blockchain.Unknown,

View file

@ -29,7 +29,8 @@ import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.walletCurrenciesManager
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import org.rekotlin.Action
import org.rekotlin.DispatchFunction
@ -174,6 +175,26 @@ private fun handleAction(action: Action, appState: () -> AppState?, dispatch: Di
is GlobalAction.SetTopUpController -> {
walletCurrenciesManager.addListener(action.topUpController)
}
is GlobalAction.UpdateUserWalletsListManager -> {
/*
* If UserWalletsListManager's implementation is changed,
* then all selectedUserWallet's observers is became irrelevant
*/
action.manager.selectedUserWallet
.distinctUntilChanged()
.onEach { userWallet ->
store.state.globalState.feedbackManager?.infoHolder?.let { infoHolder ->
infoHolder.setCardInfo(data = userWallet.scanResponse)
store.state.daggerGraphState.get(DaggerGraphState::walletManagersFacade)
.getAll(userWalletId = userWallet.walletId)
.onEach(infoHolder::setWalletsInfo)
.launchIn(scope)
}
}
.flowOn(Dispatchers.IO)
.launchIn(scope)
}
}
}

View file

@ -68,6 +68,14 @@ internal object WalletsDomainModule {
return DeleteWalletUseCase(walletsStateHolder = walletsStateHolder)
}
@Provides
@ViewModelScoped
fun providesShouldSaveUserWalletsSyncUseCase(
walletsRepository: WalletsRepository,
): ShouldSaveUserWalletsSyncUseCase {
return ShouldSaveUserWalletsSyncUseCase(walletsRepository = walletsRepository)
}
@Provides
@ViewModelScoped
fun providesShouldSaveUserWalletsUseCase(walletsRepository: WalletsRepository): ShouldSaveUserWalletsUseCase {

View file

@ -81,7 +81,8 @@ class TapWalletManager(
store.dispatchWalletAction(action = WalletAction.UserWalletChanged(userWallet))
store.dispatchWalletAction(
action = WalletAction.UpdateCanSaveUserWallets(
canSaveUserWallets = preferencesStorage.shouldSaveUserWallets,
canSaveUserWallets = store.state.daggerGraphState.get(DaggerGraphState::walletsRepository)
.shouldSaveUserWalletsSync(),
),
)
store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse))

View file

@ -14,6 +14,7 @@ sealed class DetailsAction : Action {
data class PrepareScreen(
val scanResponse: ScanResponse,
val darkThemeSwitchEnabled: Boolean,
val shouldSaveUserWallets: Boolean,
) : DetailsAction()
object ReCreateTwinsWallet : DetailsAction()

View file

@ -291,7 +291,8 @@ class DetailsMiddleware {
private fun toggleSaveWallets(state: DetailsState, enable: Boolean) = scope.launch {
// Nothing to change
if (preferencesStorage.shouldSaveUserWallets == enable) {
val walletsRepository = store.state.daggerGraphState.get(DaggerGraphState::walletsRepository)
if (walletsRepository.shouldSaveUserWalletsSync() == enable) {
store.dispatchWithMain(DetailsAction.AppSettings.SwitchPrivacySetting.Success)
return@launch
}
@ -379,7 +380,8 @@ class DetailsMiddleware {
Analytics.send(Settings.AppSettings.SaveWalletSwitcherChanged(AnalyticsParam.OnOffState.On))
preferencesStorage.shouldShowSaveUserWalletScreen = false
preferencesStorage.shouldSaveUserWallets = true
store.state.daggerGraphState.get(DaggerGraphState::walletsRepository)
.saveShouldSaveUserWallets(item = true)
store.dispatchWithMain(WalletAction.UpdateCanSaveUserWallets(canSaveUserWallets = true))
}
@ -395,7 +397,8 @@ class DetailsMiddleware {
Analytics.send(Settings.AppSettings.SaveWalletSwitcherChanged(AnalyticsParam.OnOffState.Off))
deleteSavedAccessCodes()
updateUserWalletsListManager(enableUserWalletsSaving = false)
preferencesStorage.shouldSaveUserWallets = false
store.state.daggerGraphState.get(DaggerGraphState::walletsRepository)
.saveShouldSaveUserWallets(item = false)
store.dispatchWithMain(WalletAction.UpdateCanSaveUserWallets(canSaveUserWallets = true))
store.dispatchWithMain(NavigationAction.PopBackTo(AppScreen.Home))

View file

@ -72,7 +72,7 @@ private fun handlePrepareScreen(action: DetailsAction.PrepareScreen): DetailsSta
createBackupAllowed = action.scanResponse.card.backupStatus == CardDTO.BackupStatus.NoBackup,
appSettingsState = AppSettingsState(
isBiometricsAvailable = tangemSdkManager.canUseBiometry,
saveWallets = preferencesStorage.shouldSaveUserWallets,
saveWallets = action.shouldSaveUserWallets,
saveAccessCodes = preferencesStorage.shouldSaveAccessCodes,
selectedFiatCurrency = store.state.globalState.appCurrency,
selectedThemeMode = runBlocking {

View file

@ -7,6 +7,7 @@ import com.tangem.core.analytics.Analytics
import com.tangem.core.navigation.NavigationAction
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.core.ui.theme.AppThemeModeHolder
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.tap.common.analytics.events.Settings
import com.tangem.tap.features.details.DarkThemeFeatureToggle
import com.tangem.tap.features.details.redux.DetailsState
@ -24,11 +25,14 @@ internal class DetailsFragment : ComposeFragment(), StoreSubscriber<DetailsState
@Inject
lateinit var darkThemeFeatureToggle: DarkThemeFeatureToggle
@Inject
lateinit var walletsRepository: WalletsRepository
private lateinit var detailsViewModel: DetailsViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
detailsViewModel = DetailsViewModel(store, darkThemeFeatureToggle)
detailsViewModel = DetailsViewModel(store, darkThemeFeatureToggle, walletsRepository)
Analytics.send(Settings.ScreenOpened())
}

View file

@ -10,6 +10,7 @@ import com.tangem.core.ui.event.consumedEvent
import com.tangem.core.ui.event.triggeredEvent
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.tap.common.analytics.events.Settings
import com.tangem.tap.common.extensions.dispatchWithMain
import com.tangem.tap.common.feedback.FeedbackEmail
@ -36,10 +37,12 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.rekotlin.Store
// TODO: change to Android ViewModel [REDACTED_JIRA]
internal class DetailsViewModel(
private val store: Store<AppState>,
private val darkThemeFeatureToggle: DarkThemeFeatureToggle,
) { // TODO: change to Android ViewModel
private val walletsRepository: WalletsRepository,
) {
var detailsScreenState: MutableState<DetailsScreenState> = mutableStateOf(updateState(store.state.detailsState))
private set
@ -186,8 +189,9 @@ internal class DetailsViewModel(
.onEach { selectedUserWallet ->
store.dispatchWithMain(
DetailsAction.PrepareScreen(
selectedUserWallet.scanResponse,
darkThemeFeatureToggle.isDarkThemeEnabled,
scanResponse = selectedUserWallet.scanResponse,
darkThemeSwitchEnabled = darkThemeFeatureToggle.isDarkThemeEnabled,
shouldSaveUserWallets = walletsRepository.shouldSaveUserWalletsSync(),
),
)
}

View file

@ -17,6 +17,7 @@ import com.tangem.tap.common.extensions.onUserWalletSelected
import com.tangem.tap.common.extensions.removeContext
import com.tangem.tap.common.extensions.setContext
import com.tangem.tap.features.saveWallet.redux.SaveWalletAction
import com.tangem.tap.proxy.redux.DaggerGraphState
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import timber.log.Timber
@ -70,40 +71,39 @@ object OnboardingHelper {
backupCardsIds: List<String>? = null,
) {
Analytics.setContext(scanResponse)
when {
// When should save user wallets, then save card without navigate to save wallet screen
preferencesStorage.shouldSaveUserWallets -> scope.launch {
proceedWithScanResponse(scanResponse, backupCardsIds)
scope.launch {
when {
// When should save user wallets, then save card without navigate to save wallet screen
store.state.daggerGraphState.get(DaggerGraphState::walletsRepository).shouldSaveUserWalletsSync() -> {
proceedWithScanResponse(scanResponse, backupCardsIds)
store.dispatchOnMain(
SaveWalletAction.ProvideBackupInfo(
scanResponse = scanResponse,
accessCode = accessCode,
backupCardsIds = backupCardsIds?.toSet(),
),
)
store.dispatchOnMain(SaveWalletAction.Save)
}
// When should not save user wallets but device has biometry and save wallet screen has not been shown,
// then open save wallet screen
tangemSdkManager.canUseBiometry &&
preferencesStorage.shouldShowSaveUserWalletScreen -> scope.launch {
proceedWithScanResponse(scanResponse, backupCardsIds)
store.dispatchOnMain(
SaveWalletAction.ProvideBackupInfo(
scanResponse = scanResponse,
accessCode = accessCode,
backupCardsIds = backupCardsIds?.toSet(),
),
)
store.dispatchOnMain(SaveWalletAction.Save)
}
// When should not save user wallets but device has biometry and save wallet screen has not been shown,
// then open save wallet screen
tangemSdkManager.canUseBiometry && preferencesStorage.shouldShowSaveUserWalletScreen -> {
proceedWithScanResponse(scanResponse, backupCardsIds)
delay(timeMillis = 1_200)
delay(timeMillis = 1_200)
store.dispatchOnMain(
SaveWalletAction.ProvideBackupInfo(
scanResponse = scanResponse,
accessCode = accessCode,
backupCardsIds = backupCardsIds?.toSet(),
),
)
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.SaveWallet))
}
// If device has no biometry and save wallet screen has been shown, then go through old scenario
else -> scope.launch {
proceedWithScanResponse(scanResponse, backupCardsIds)
store.dispatchOnMain(
SaveWalletAction.ProvideBackupInfo(
scanResponse = scanResponse,
accessCode = accessCode,
backupCardsIds = backupCardsIds?.toSet(),
),
)
store.dispatchOnMain(NavigationAction.NavigateTo(AppScreen.SaveWallet))
}
// If device has no biometry and save wallet screen has been shown, then go through old scenario
else -> proceedWithScanResponse(scanResponse, backupCardsIds)
}
}

View file

@ -29,6 +29,7 @@ import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.redux.ProgressState
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.features.wallet.redux.models.WalletDialog
import com.tangem.tap.proxy.redux.DaggerGraphState
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.rekotlin.Action
@ -292,10 +293,14 @@ private fun handle(action: Action, dispatch: DispatchFunction) {
OnboardingHelper.trySaveWalletAndNavigateToWalletScreen(scanResponse)
}
CreateTwinWalletMode.RecreateWallet -> {
if (preferencesStorage.shouldSaveUserWallets) {
OnboardingHelper.trySaveWalletAndNavigateToWalletScreen(scanResponse)
} else {
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home))
scope.launch {
val walletsRepository = store.state.daggerGraphState.get(DaggerGraphState::walletsRepository)
if (walletsRepository.shouldSaveUserWalletsSync()) {
OnboardingHelper.trySaveWalletAndNavigateToWalletScreen(scanResponse)
} else {
store.dispatchOnMain(NavigationAction.PopBackTo(AppScreen.Home))
}
}
}
}

View file

@ -107,10 +107,11 @@ internal class SaveWalletMiddleware {
store.dispatchWithMain(SaveWalletAction.Save.Error(error))
}
.doOnSuccess {
preferencesStorage.shouldSaveUserWallets = true
store.state.daggerGraphState.get(DaggerGraphState::walletsRepository)
.saveShouldSaveUserWallets(item = true)
// Enable saving access codes only if this is the first time user save the wallet
if (isFirstSavedWallet) {
preferencesStorage.shouldSaveAccessCodes = true
store.state.daggerGraphState.get(DaggerGraphState::cardSdkConfigRepository)
.setAccessCodeRequestPolicy(
isBiometricsRequestPolicy = userWallet.hasAccessCode,

View file

@ -10,6 +10,7 @@ import com.tangem.domain.card.ScanCardUseCase
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.features.tester.api.TesterRouter
import com.tangem.features.tokendetails.featuretoggles.TokenDetailsFeatureToggles
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
@ -44,6 +45,7 @@ data class DaggerGraphState(
val appThemeModeRepository: AppThemeModeRepository? = null,
val balanceHidingRepository: BalanceHidingRepository? = null,
val detailsFeatureToggles: DetailsFeatureToggles? = null,
val walletsRepository: WalletsRepository? = null,
// FIXME: It is used only for TokensList screen. Remove after refactoring of TokensList
val currenciesRepository: CurrenciesRepository? = null,