diff --git a/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt b/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt index f5e45540e0..0013049c04 100644 --- a/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/card/DefaultDerivationsRepository.kt @@ -12,6 +12,7 @@ import com.tangem.common.extensions.toMapKey import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.data.common.currency.getNetwork import com.tangem.datasource.local.userwallet.UserWalletsStore +import com.tangem.domain.card.BackendId import com.tangem.domain.card.repository.DerivationsRepository import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.tokens.model.CryptoCurrency @@ -73,15 +74,15 @@ internal class DefaultDerivationsRepository( override suspend fun hasMissedDerivations( userWalletId: UserWalletId, - networksWithDerivationPath: Map, + networksWithDerivationPath: Map, ): Boolean { val userWallet = userWalletsStore.getSyncOrNull(userWalletId) ?: error("User wallet not found") val derivations = MissedDerivationsFinder(scanResponse = userWallet.scanResponse) .findByNetworks( - networksWithDerivationPath.mapNotNull { (networkId, extraDerivationPath) -> + networksWithDerivationPath.mapNotNull { (backendId, extraDerivationPath) -> getNetwork( - blockchain = Blockchain.fromNetworkId(networkId.value) ?: return@mapNotNull null, + blockchain = Blockchain.fromNetworkId(backendId) ?: return@mapNotNull null, extraDerivationPath = extraDerivationPath, scanResponse = userWallet.scanResponse, ) diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt index 3b3e81c1a1..35007254c1 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/OnboardingHelper.kt @@ -23,6 +23,7 @@ import com.tangem.tap.common.extensions.* import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.common.redux.global.GlobalState import com.tangem.tap.features.demo.DemoHelper +import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletAction import com.tangem.tap.features.home.RUSSIA_COUNTRY_CODE import com.tangem.tap.features.saveWallet.redux.SaveWalletAction import com.tangem.tap.mainScope @@ -82,6 +83,56 @@ object OnboardingHelper { } } + fun saveWallet( + scanResponse: ScanResponse, + accessCode: String? = null, + backupCardsIds: List? = null, + hasBackupError: Boolean = false, + ) { + Analytics.setContext(scanResponse) + scope.launch { + val settingsRepository = store.inject(DaggerGraphState::settingsRepository) + when { + // When should save user wallets, then save card without navigate to save wallet screen + store.inject(DaggerGraphState::walletsRepository).shouldSaveUserWalletsSync() -> { + store.dispatchWithMain( + SaveWalletAction.ProvideBackupInfo( + scanResponse = scanResponse, + accessCode = accessCode, + backupCardsIds = backupCardsIds?.toSet(), + ), + ) + + store.dispatchWithMain( + SaveWalletAction.SaveWalletAfterBackup( + hasBackupError = hasBackupError, + shouldNavigateToWallet = false, + ), + ) + } + // When should not save user wallets but device has biometry and save wallet screen has not been shown, + // then open save wallet screen + tangemSdkManager.checkCanUseBiometry() && settingsRepository.shouldShowSaveUserWalletScreen() -> { + proceedWithScanResponse(scanResponse, backupCardsIds, hasBackupError) + + delay(timeMillis = 1_200) + + store.dispatchOnMain( + SaveWalletAction.ProvideBackupInfo( + scanResponse = scanResponse, + accessCode = accessCode, + backupCardsIds = backupCardsIds?.toSet(), + ), + ) + } + // If device has no biometry and save wallet screen has been shown, then go through old scenario + else -> { + proceedWithScanResponse(scanResponse, backupCardsIds, hasBackupError) + } + } + } + } + fun trySaveWalletAndNavigateToWalletScreen( scanResponse: ScanResponse, accessCode: String? = null, @@ -103,7 +154,12 @@ object OnboardingHelper { ), ) - store.dispatchWithMain(SaveWalletAction.SaveWalletAfterBackup(hasBackupError)) + store.dispatchWithMain( + SaveWalletAction.SaveWalletAfterBackup( + hasBackupError = hasBackupError, + shouldNavigateToWallet = true, + ), + ) } // When should not save user wallets but device has biometry and save wallet screen has not been shown, // then open save wallet screen @@ -200,6 +256,7 @@ object OnboardingHelper { } .doOnSuccess { mainScope.launch { store.onUserWalletSelected(userWallet) } + store.dispatch(OnboardingWalletAction.WalletSaved(userWallet.walletId)) } } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt index 3538c3bb27..64ce8d1864 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletAction.kt @@ -2,6 +2,7 @@ package com.tangem.tap.features.onboarding.products.wallet.redux import android.net.Uri import com.tangem.common.CompletionResult +import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.onboarding.data.model.CreateWalletResponse import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseSource import com.tangem.tap.domain.tasks.product.CreateProductWalletTaskResponse @@ -28,6 +29,8 @@ sealed class OnboardingWalletAction : Action { class SetThirdCardArtworkUrl(val artworkUri: Uri?) : OnboardingWalletAction() data object OnBackPressed : OnboardingWalletAction() + + data class WalletSaved(val userWalletId: UserWalletId) : OnboardingWalletAction() } sealed class OnboardingWallet2Action : OnboardingWalletAction() { @@ -89,6 +92,8 @@ sealed class BackupAction : Action { data class FinishBackup(val withAnalytics: Boolean = true) : BackupAction() + data class BackupFinished(val userWalletId: UserWalletId?) : BackupAction() + data object DiscardBackup : BackupAction() data object DiscardSavedBackup : BackupAction() data object ResumeFoundUnfinishedBackup : BackupAction() diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt index 95b2fd2452..98bca6119a 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletMiddleware.kt @@ -5,7 +5,6 @@ import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tangem.common.CompletionResult import com.tangem.common.card.Card import com.tangem.common.core.TangemSdkError -import com.tangem.common.extensions.guard import com.tangem.common.extensions.ifNotNull import com.tangem.common.extensions.toHexString import com.tangem.common.routing.AppRoute @@ -19,6 +18,7 @@ import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.wallets.builder.UserWalletBuilder import com.tangem.domain.wallets.models.Artwork +import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.onboarding.data.model.CreateWalletResponse import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseSource import com.tangem.feature.wallet.presentation.wallet.domain.BackupValidator @@ -164,16 +164,7 @@ private fun handleWalletAction(action: Action) { } is OnboardingWalletAction.FinishOnboarding -> { store.dispatch(GlobalAction.Onboarding.Stop) - - if (scanResponse == null) { - action.scope.launch { - readCard { newScanResponse -> - handleFinishOnboardind(newScanResponse) - } - } - } else { - handleFinishOnboardind(scanResponse) - } + navigateToWalletScreen() } is OnboardingWalletAction.ResumeBackup -> { val newAction = when (val backupState = backupService.currentState) { @@ -197,10 +188,10 @@ private fun handleWalletAction(action: Action) { } } -private fun handleFinishOnboardind(scanResponse: ScanResponse) { +private fun handleFinishBackup(scanResponse: ScanResponse) { val backupState = store.state.onboardingWalletState.backupState val updatedScanResponse = updateScanResponseAfterBackup(scanResponse, backupState) - OnboardingHelper.trySaveWalletAndNavigateToWalletScreen( + OnboardingHelper.saveWallet( scanResponse = updatedScanResponse, accessCode = backupState.accessCode, backupCardsIds = backupState.backupCardIds, @@ -208,7 +199,18 @@ private fun handleFinishOnboardind(scanResponse: ScanResponse) { ) } -private suspend fun readCard(onSuccess: (ScanResponse) -> Unit) { +private fun navigateToWalletScreen() { + mainScope.launch { + val settingsRepository = store.inject(DaggerGraphState::settingsRepository) + store.dispatchNavigationAction { replaceAll(AppRoute.Wallet) } + if (tangemSdkManager.checkCanUseBiometry() && settingsRepository.shouldShowSaveUserWalletScreen()) { + delay(timeMillis = 1_800) + store.dispatchNavigationAction { push(AppRoute.SaveWallet) } + } + } +} + +private suspend fun readCard(onSuccess: suspend (ScanResponse) -> Unit) { val shouldSaveAccessCodes = store.inject(DaggerGraphState::settingsRepository).shouldSaveAccessCodes() store.inject(DaggerGraphState::cardSdkConfigRepository).setAccessCodeRequestPolicy( @@ -403,7 +405,7 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) val onboardingWalletState = appState()?.onboardingWalletState ?: return val backupState = onboardingWalletState.backupState - val scanResponse = globalState.onboardingState.onboardingManager?.scanResponse + var scanResponse = globalState.onboardingState.onboardingManager?.scanResponse val card = scanResponse?.card when (action) { @@ -573,6 +575,15 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) cardIds = gatherCardIds(backupState, card), ) } + if (scanResponse == null) { + scope.launch { + readCard { newScanResponse -> + handleFinishBackup(newScanResponse) + } + } + } else { + handleFinishBackup(scanResponse) + } } is BackupAction.FinishBackup -> { scope.launch { @@ -580,28 +591,31 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) Analytics.send(Onboarding.Backup.Finished(backupState.backupCardsNumber)) } + var userWallet: UserWallet? = null if (scanResponse != null) { - val walletNameGenerateUseCase = store.inject(DaggerGraphState::generateWalletNameUseCase) - val userWallet = UserWalletBuilder(scanResponse, walletNameGenerateUseCase) - .backupCardsIds(backupState.backupCardIds.toSet()) - .build() - .guard { - Timber.e("User wallet not created") - return@launch - } + userWallet = createUserWallet( + scanResponse = requireNotNull(value = scanResponse, lazyMessage = { "ScanResponse is null" }), + backupState = backupState, + ) val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager) - userWalletsListManager.update( - userWalletId = userWallet.walletId, - update = { wallet -> - wallet.copy( - scanResponse = updateScanResponseAfterBackup( - scanResponse = wallet.scanResponse, - backupState = backupState, + userWalletsListManager.save( + userWallet = userWallet.copy( + scanResponse = updateScanResponseAfterBackup( + scanResponse = requireNotNull( + value = scanResponse, + lazyMessage = { "ScanResponse is null" }, ), - ) - }, + backupState = backupState, + ), + ), + canOverride = true, ) + } else { + readCard { newScanResponse -> + scanResponse = newScanResponse + userWallet = createUserWallet(newScanResponse, backupState) + } } val notActivatedCardIds = gatherCardIds(backupState, card).mapNotNull { @@ -613,11 +627,18 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) } // All cardIds may already be activated if the backup was skipped before. - if (notActivatedCardIds.isEmpty()) return@launch + if (notActivatedCardIds.isEmpty()) { + delay(1000) + store.dispatch(BackupAction.BackupFinished(userWallet?.walletId)) + return@launch + } Analytics.send(Onboarding.Finished()) store.state.globalState.onboardingState.onboardingManager?.finishActivation(notActivatedCardIds) + handleFinishBackup(requireNotNull(scanResponse)) + delay(1000) + store.dispatch(BackupAction.BackupFinished(userWalletId = userWallet?.walletId)) } } @@ -629,6 +650,16 @@ private fun handleBackupAction(appState: () -> AppState?, action: BackupAction) } } +private suspend fun createUserWallet(scanResponse: ScanResponse, backupState: BackupState): UserWallet { + val walletNameGenerateUseCase = store.inject(DaggerGraphState::generateWalletNameUseCase) + return requireNotNull( + value = UserWalletBuilder(scanResponse, walletNameGenerateUseCase) + .backupCardsIds(backupState.backupCardIds.toSet()) + .build(), + lazyMessage = { "User wallet not created" }, + ) +} + fun updateArtworks(addedBackupCardsCount: Int, card: Card) { mainScope.launch { withIOContext { diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt index 035db68bae..ca8bfeb584 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletReducer.kt @@ -1,6 +1,7 @@ package com.tangem.tap.features.onboarding.products.wallet.redux import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.domain.redux.OnboardingManageTokensAction import com.tangem.tap.backupService import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction @@ -10,12 +11,23 @@ object OnboardingWalletReducer { fun reduce(action: Action, state: AppState): OnboardingWalletState = internalReduce(action, state) } +@Suppress("CyclomaticComplexMethod") private fun internalReduce(action: Action, appState: AppState): OnboardingWalletState { val state = appState.onboardingWalletState return when (action) { is GlobalAction.Onboarding -> ReducerForGlobalAction.reduce(action, state) - is BackupAction -> state.copy(backupState = BackupReducer.reduce(action, state.backupState)) + is BackupAction.BackupFinished -> { + state.copy( + step = if (action.userWalletId != null) { + OnboardingWalletStep.ManageTokens + } else { + OnboardingWalletStep.Done + }, + userWalletId = action.userWalletId, + ) + } + is BackupAction -> state.copy(backupState = BackupReducer.reduce(action = action, state = state.backupState)) is OnboardingWallet2Action -> OnboardingWallet2Reducer.reduce(action, state) is OnboardingWalletAction.GetToCreateWalletStep -> state.copy( step = OnboardingWalletStep.CreateWallet, @@ -36,6 +48,11 @@ private fun internalReduce(action: Action, appState: AppState): OnboardingWallet walletImages = state.walletImages.copy(thirdCardImage = action.artworkUri), ) } + is OnboardingManageTokensAction.CurrenciesSaved -> state.copy(step = OnboardingWalletStep.Done) + is OnboardingWalletAction.WalletSaved -> state.copy( + step = OnboardingWalletStep.ManageTokens, + userWalletId = action.userWalletId, + ) is OnboardingWalletAction.Done -> state.copy(step = OnboardingWalletStep.Done) else -> state } diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt index 853678f202..192798e4cb 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/redux/OnboardingWalletState.kt @@ -3,6 +3,7 @@ package com.tangem.tap.features.onboarding.products.wallet.redux import android.graphics.Bitmap import android.net.Uri import com.tangem.domain.redux.StateDialog +import com.tangem.domain.wallets.models.UserWalletId import org.rekotlin.StateType /** @@ -15,18 +16,19 @@ data class OnboardingWalletState( val walletImages: WalletImages = WalletImages(), val showConfetti: Boolean = false, val isRingOnboarding: Boolean = false, + val userWalletId: UserWalletId? = null, ) : StateType { @Suppress("MagicNumber") fun getMaxProgress(): Int { - val baseProgress = 6 + val baseProgress = 7 return getWallet2Progress() + baseProgress } @Suppress("ComplexMethod", "MagicNumber") fun getProgressStep(): Int { val progressByStep = when (step) { - OnboardingWalletStep.CreateWallet -> 1 + OnboardingWalletStep.CreateWallet, OnboardingWalletStep.None -> 1 OnboardingWalletStep.Backup -> { when (backupState.backupStep) { BackupStep.InitBackup -> 2 @@ -36,11 +38,11 @@ data class OnboardingWalletState( BackupStep.ReenterAccessCode -> 4 BackupStep.SetAccessCode -> 4 BackupStep.WritePrimaryCard, is BackupStep.WriteBackupCard -> 5 - BackupStep.Finished -> getMaxProgress() + BackupStep.Finished -> 6 } } + OnboardingWalletStep.ManageTokens -> 6 OnboardingWalletStep.Done -> getMaxProgress() - else -> 1 } return getWallet2Progress() + progressByStep @@ -60,7 +62,7 @@ data class OnboardingWallet2State( ) enum class OnboardingWalletStep { - None, CreateWallet, Backup, Done + None, CreateWallet, Backup, ManageTokens, Done } data class BackupState( diff --git a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt index 6ffddd4a21..94c56bfd2c 100644 --- a/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt +++ b/app/src/main/java/com/tangem/tap/features/onboarding/products/wallet/ui/OnboardingWalletFragment.kt @@ -3,6 +3,8 @@ package com.tangem.tap.features.onboarding.products.wallet.ui import android.content.res.ColorStateList import android.net.Uri import android.os.Bundle +import android.os.Handler +import android.os.Looper import android.util.TypedValue import android.view.View import android.view.ViewGroup @@ -10,12 +12,16 @@ import android.view.WindowManager import android.widget.FrameLayout import android.widget.ImageView import androidx.appcompat.app.AppCompatActivity +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.ui.Modifier import androidx.core.view.MenuProvider import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope import androidx.transition.TransitionManager import by.kirich1409.viewbindingdelegate.viewBinding import coil.load +import com.arkivanov.decompose.defaultComponentContext import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.tabs.TabLayoutMediator import com.tangem.common.CardIdFormatter @@ -25,15 +31,22 @@ import com.tangem.common.routing.AppRoute import com.tangem.core.analytics.Analytics import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.Basic +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.context.childByContext +import com.tangem.core.decompose.di.RootAppComponentContext import com.tangem.core.ui.extensions.setStatusBarColor +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.windowsize.rememberWindowSize import com.tangem.datasource.utils.isNullOrEmpty import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.feedback.models.FeedbackEmailType +import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.onboarding.data.model.CreateWalletResponse import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseSource import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseMediator import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseRouter import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseViewModel +import com.tangem.features.managetokens.component.OnboardingManageTokensComponent import com.tangem.sdk.ui.widget.leapfrogWidget.LeapfrogWidget import com.tangem.sdk.ui.widget.leapfrogWidget.PropertyCalculator import com.tangem.tap.common.analytics.events.Onboarding @@ -56,6 +69,7 @@ import com.tangem.wallet.databinding.ViewOnboardingProgressBinding import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch import org.rekotlin.StoreSubscriber +import javax.inject.Inject @Suppress("LargeClass", "MagicNumber") @AndroidEntryPoint @@ -64,10 +78,20 @@ class OnboardingWalletFragment : StoreSubscriber, FragmentOnBackPressedHandler { + @Inject + internal lateinit var onboardingManageTokensComponentFactory: OnboardingManageTokensComponent.Factory + + @Inject + @RootAppComponentContext + internal lateinit var rootComponentContext: AppComponentContext + + private var onboardingManageTokensComponent: OnboardingManageTokensComponent? = null + internal val binding: FragmentOnboardingWalletBinding by viewBinding(FragmentOnboardingWalletBinding::bind) internal val pbBinding: ViewOnboardingProgressBinding by viewBinding(ViewOnboardingProgressBinding::bind) internal val bindingSeedPhrase: LayoutOnboardingSeedPhraseBinding by lazy { binding.onboardingSeedPhraseContainer } + private val bindingManageTokens by lazy { binding.onboardingManageTokensContainer } private val canSkipBackup by lazy { arguments?.getBoolean(AppRoute.OnboardingWallet.CAN_SKIP_BACKUP_KEY) ?: true } @@ -173,26 +197,28 @@ class OnboardingWalletFragment : override fun onStop() { super.onStop() + onboardingManageTokensComponent = null store.unsubscribe(this) } override fun newState(state: OnboardingWalletState) { if (activity == null || view == null) return + Handler(Looper.getMainLooper()).post { + animator.updateBackupState(state.backupState) + requireActivity().invalidateOptionsMenu() - animator.updateBackupState(state.backupState) - requireActivity().invalidateOptionsMenu() + pbBinding.pbState.max = state.getMaxProgress() + pbBinding.pbState.progress = state.getProgressStep() - pbBinding.pbState.max = state.getMaxProgress() - pbBinding.pbState.progress = state.getProgressStep() - - when { - state.wallet2State != null -> { - seedPhraseStateHandler.newState(this, state, seedPhraseViewModel) - updateWalletImagesState(state.walletImages) - } - else -> { - updateWalletImagesState(state.walletImages) - handleOnboardingStep(state) + when { + state.wallet2State != null -> { + seedPhraseStateHandler.newState(this, state, seedPhraseViewModel) + updateWalletImagesState(state.walletImages) + } + else -> { + updateWalletImagesState(state.walletImages) + handleOnboardingStep(state) + } } } } @@ -220,14 +246,53 @@ class OnboardingWalletFragment : internal fun handleOnboardingStep(state: OnboardingWalletState) { when (state.step) { OnboardingWalletStep.CreateWallet -> setupCreateWalletState() - OnboardingWalletStep.Backup -> setBackupState( - state = state.backupState, - ) - + OnboardingWalletStep.Backup -> setBackupState(state = state.backupState) + OnboardingWalletStep.ManageTokens -> setManageTokensState(requireNotNull(state.userWalletId)) + OnboardingWalletStep.Done -> showSuccess() else -> {} } } + private fun initializeOnboardingManageTokensComponent(userWalletId: UserWalletId) { + val componentContext = rootComponentContext.childByContext( + componentContext = defaultComponentContext(onBackPressedDispatcher = null), + ) + onboardingManageTokensComponent = onboardingManageTokensComponentFactory.create( + context = componentContext, + params = OnboardingManageTokensComponent.Params(userWalletId = userWalletId), + ) + } + + private fun setManageTokensState(userWalletId: UserWalletId) { + with(binding) { + tvHeader.show() + tvBody.show() + viewPagerBackupInfo.hide() + tabLayoutBackupInfo.hide() + onboardingWalletContainer.show() + layoutButtonsCommon.btnWalletAlternativeAction.hide() + onboardingWalletContainer.hide() + toolbar.title = getText(R.string.onboarding_add_tokens) + } + pbBinding.pbState.show() + bindingSeedPhrase.onboardingSeedPhraseContainer.hide() + if (onboardingManageTokensComponent == null) { + initializeOnboardingManageTokensComponent(userWalletId) + } + with(bindingManageTokens) { + onboardingManageTokensContainer.hide() + onboardingManageTokensContainer.show() + onboardingManageTokensContainer.setContent { + TangemTheme( + isDark = isSystemInDarkTheme(), + windowSize = rememberWindowSize(activity = requireActivity()), + ) { + onboardingManageTokensComponent?.Content(modifier = Modifier.fillMaxSize()) + } + } + } + } + private fun setupCreateWalletState() = with(binding) { layoutButtonsCommon.btnWalletMainAction.setText(R.string.onboarding_create_wallet_button_create_wallet) layoutButtonsCommon.btnWalletMainAction.setIconResource(R.drawable.ic_tangem_24) @@ -256,7 +321,10 @@ class OnboardingWalletFragment : BackupStep.ReenterAccessCode -> showReenterAccessCode(state) is BackupStep.WritePrimaryCard -> showWritePrimaryCard(state) is BackupStep.WriteBackupCard -> showWriteBackupCard(state) - BackupStep.Finished -> showSuccess() + BackupStep.Finished -> { + // don't need to navigate here" + // showSuccess() + } } } @@ -454,6 +522,8 @@ class OnboardingWalletFragment : tvBody.show() viewPagerBackupInfo.hide() tabLayoutBackupInfo.hide() + onboardingWalletContainer.show() + bindingManageTokens.onboardingManageTokensContainer.hide() tvBody.text = getText(R.string.onboarding_subtitle_success_tangem_wallet_onboarding) layoutButtonsCommon.btnWalletMainAction.text = getText(R.string.onboarding_button_continue_wallet) diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletAction.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletAction.kt index e79ea8b9fe..f4981a8c5c 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletAction.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletAction.kt @@ -24,5 +24,8 @@ internal sealed interface SaveWalletAction : Action { data object Cancel : SaveWalletAction } - data class SaveWalletAfterBackup(val hasBackupError: Boolean) : SaveWalletAction + data class SaveWalletAfterBackup( + val hasBackupError: Boolean, + val shouldNavigateToWallet: Boolean, + ) : SaveWalletAction } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt index 7cb2506f8e..c8e093611d 100644 --- a/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/saveWallet/redux/SaveWalletMiddleware.kt @@ -16,6 +16,7 @@ import com.tangem.tap.common.extensions.dispatchWithMain import com.tangem.tap.common.extensions.inject import com.tangem.tap.common.extensions.onUserWalletSelected import com.tangem.tap.common.redux.AppState +import com.tangem.tap.features.onboarding.products.wallet.redux.OnboardingWalletAction import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.utils.coroutines.JobHolder import com.tangem.utils.coroutines.saveIn @@ -44,7 +45,11 @@ internal class SaveWalletMiddleware { is SaveWalletAction.AllowToUseBiometrics -> allowToUseBiometrics(state) is SaveWalletAction.EnrollBiometrics.Enroll -> enrollBiometrics() is SaveWalletAction.Dismiss -> dismiss(state) - is SaveWalletAction.SaveWalletAfterBackup -> saveWalletAfterBackup(state, action.hasBackupError) + is SaveWalletAction.SaveWalletAfterBackup -> saveWalletAfterBackup( + state = state, + hasBackupError = action.hasBackupError, + shouldNavigateToWallet = action.shouldNavigateToWallet, + ) is SaveWalletAction.AllowToUseBiometrics.Success, is SaveWalletAction.AllowToUseBiometrics.Error, is SaveWalletAction.ProvideBackupInfo, @@ -55,7 +60,11 @@ internal class SaveWalletMiddleware { } } - private fun saveWalletAfterBackup(state: SaveWalletState, hasBackupError: Boolean) { + private fun saveWalletAfterBackup( + state: SaveWalletState, + hasBackupError: Boolean, + shouldNavigateToWallet: Boolean, + ) { scope.launch { val backupInfo = state.backupInfo ?: error("Backup info is null") @@ -77,8 +86,13 @@ internal class SaveWalletMiddleware { .doOnFailure { error -> Timber.e(error, "Unable to save user wallet") } - .doOnSuccess { mainScope.launch { store.onUserWalletSelected(userWallet) } } - .doOnResult { navigateToWallet() } + .doOnSuccess { + mainScope.launch { store.onUserWalletSelected(userWallet) } + if (!shouldNavigateToWallet) { + store.dispatch(OnboardingWalletAction.WalletSaved(userWallet.walletId)) + } + } + .doOnResult { if (shouldNavigateToWallet) navigateToWallet() } } } diff --git a/app/src/main/res/layout/fragment_onboarding_wallet.xml b/app/src/main/res/layout/fragment_onboarding_wallet.xml index 217d2db70e..c9fb79355a 100644 --- a/app/src/main/res/layout/fragment_onboarding_wallet.xml +++ b/app/src/main/res/layout/fragment_onboarding_wallet.xml @@ -258,6 +258,13 @@ android:layout_height="match_parent" android:visibility="gone" /> + + + \ No newline at end of file diff --git a/domain/card/src/main/kotlin/com/tangem/domain/card/HasMissedDerivationsUseCase.kt b/domain/card/src/main/kotlin/com/tangem/domain/card/HasMissedDerivationsUseCase.kt index 5d481fe1ca..3cfc726741 100644 --- a/domain/card/src/main/kotlin/com/tangem/domain/card/HasMissedDerivationsUseCase.kt +++ b/domain/card/src/main/kotlin/com/tangem/domain/card/HasMissedDerivationsUseCase.kt @@ -9,6 +9,9 @@ import com.tangem.domain.wallets.models.UserWalletId * [REDACTED_AUTHOR] */ + +typealias BackendId = String + class HasMissedDerivationsUseCase( private val derivationsRepository: DerivationsRepository, ) { @@ -16,7 +19,7 @@ class HasMissedDerivationsUseCase( /** Check if user [userWalletId] has missed derivations using map of [Network.ID] with extraDerivationPath */ suspend operator fun invoke( userWalletId: UserWalletId, - networksWithDerivationPath: Map, + networksWithDerivationPath: Map, ): Boolean { return derivationsRepository.hasMissedDerivations(userWalletId, networksWithDerivationPath) } diff --git a/domain/card/src/main/kotlin/com/tangem/domain/card/repository/DerivationsRepository.kt b/domain/card/src/main/kotlin/com/tangem/domain/card/repository/DerivationsRepository.kt index 83ea2e4b48..e14d8deb18 100644 --- a/domain/card/src/main/kotlin/com/tangem/domain/card/repository/DerivationsRepository.kt +++ b/domain/card/src/main/kotlin/com/tangem/domain/card/repository/DerivationsRepository.kt @@ -2,6 +2,7 @@ package com.tangem.domain.card.repository import com.tangem.common.extensions.ByteArrayKey import com.tangem.crypto.hdWallet.DerivationPath +import com.tangem.domain.card.BackendId import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Network import com.tangem.domain.wallets.models.UserWalletId @@ -26,6 +27,6 @@ interface DerivationsRepository { /** Check if user [userWalletId] has missed derivations using map of [Network.ID] with extraDerivationPath */ suspend fun hasMissedDerivations( userWalletId: UserWalletId, - networksWithDerivationPath: Map, + networksWithDerivationPath: Map, ): Boolean } \ No newline at end of file diff --git a/domain/legacy/src/main/java/com/tangem/domain/redux/OnboardingManageTokensAction.kt b/domain/legacy/src/main/java/com/tangem/domain/redux/OnboardingManageTokensAction.kt new file mode 100644 index 0000000000..1aab9469b3 --- /dev/null +++ b/domain/legacy/src/main/java/com/tangem/domain/redux/OnboardingManageTokensAction.kt @@ -0,0 +1,7 @@ +package com.tangem.domain.redux + +import org.rekotlin.Action + +sealed class OnboardingManageTokensAction : Action { + data object CurrenciesSaved : OnboardingManageTokensAction() +} \ No newline at end of file diff --git a/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/component/OnboardingManageTokensComponent.kt b/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/component/OnboardingManageTokensComponent.kt new file mode 100644 index 0000000000..8c6eb4974b --- /dev/null +++ b/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/component/OnboardingManageTokensComponent.kt @@ -0,0 +1,12 @@ +package com.tangem.features.managetokens.component + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.domain.wallets.models.UserWalletId + +interface OnboardingManageTokensComponent : ComposableContentComponent { + + data class Params(val userWalletId: UserWalletId) + + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/features/manage-tokens/impl/build.gradle.kts b/features/manage-tokens/impl/build.gradle.kts index f018c97984..93e730eb6f 100644 --- a/features/manage-tokens/impl/build.gradle.kts +++ b/features/manage-tokens/impl/build.gradle.kts @@ -23,8 +23,9 @@ dependencies { implementation(projects.core.analytics) /* Project - Domain */ - implementation(projects.domain.manageTokens) implementation(projects.domain.card) + implementation(projects.domain.legacy) + implementation(projects.domain.manageTokens) implementation(projects.domain.tokens) implementation(projects.domain.tokens.models) implementation(projects.domain.wallets.models) @@ -49,4 +50,5 @@ dependencies { implementation(deps.kotlin.immutable.collections) implementation(deps.decompose.ext.compose) implementation(deps.timber) + implementation(deps.reKotlin) // need for legacy onboarding } \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/impl/DefaultOnboardingManageTokensComponent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/impl/DefaultOnboardingManageTokensComponent.kt new file mode 100644 index 0000000000..dc7f321d3e --- /dev/null +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/impl/DefaultOnboardingManageTokensComponent.kt @@ -0,0 +1,37 @@ +package com.tangem.features.managetokens.component.impl + +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.features.managetokens.component.OnboardingManageTokensComponent +import com.tangem.features.managetokens.model.OnboardingManageTokensModel +import com.tangem.features.managetokens.ui.OnboardingManageTokensContent +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +internal class DefaultOnboardingManageTokensComponent @AssistedInject constructor( + @Assisted context: AppComponentContext, + @Assisted params: OnboardingManageTokensComponent.Params, +) : OnboardingManageTokensComponent, AppComponentContext by context { + + private val model: OnboardingManageTokensModel = getOrCreateModel(params) + + @Composable + override fun Content(modifier: Modifier) { + val state by model.state.collectAsStateWithLifecycle() + + OnboardingManageTokensContent(modifier = modifier, state = state) + } + + @AssistedFactory + interface Factory : OnboardingManageTokensComponent.Factory { + override fun create( + context: AppComponentContext, + params: OnboardingManageTokensComponent.Params, + ): DefaultOnboardingManageTokensComponent + } +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewManageTokensComponent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewManageTokensComponent.kt index 6e5a5917e2..a0058fc2e9 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewManageTokensComponent.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewManageTokensComponent.kt @@ -26,7 +26,8 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update internal class PreviewManageTokensComponent( - private val isLoading: Boolean = false, + private val isLoading: Boolean, + params: ManageTokensComponent.Params, ) : ManageTokensComponent { private val changedItemsIds: MutableSet = mutableSetOf() @@ -36,14 +37,21 @@ internal class PreviewManageTokensComponent( value = ManageTokensUM.ManageContent( popBack = {}, items = items, - topBar = ManageTokensTopBarUM.ManageContent( - title = resourceReference(id = R.string.main_manage_tokens), - onBackButtonClick = {}, - endButton = TopAppBarButtonUM( - iconRes = R.drawable.ic_plus_24, - onIconClicked = {}, - ), - ), + topBar = if (params.userWalletId != null) { + ManageTokensTopBarUM.ManageContent( + title = resourceReference(id = R.string.main_manage_tokens), + onBackButtonClick = {}, + endButton = TopAppBarButtonUM( + iconRes = R.drawable.ic_plus_24, + onIconClicked = {}, + ), + ) + } else { + ManageTokensTopBarUM.ReadContent( + title = resourceReference(R.string.common_search_tokens), + onBackButtonClick = {}, + ) + }, search = SearchBarUM( placeholderText = resourceReference(R.string.manage_tokens_search_placeholder), query = "", diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewOnboardingManageTokensComponent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewOnboardingManageTokensComponent.kt new file mode 100644 index 0000000000..3ef520d4ec --- /dev/null +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewOnboardingManageTokensComponent.kt @@ -0,0 +1,114 @@ +package com.tangem.features.managetokens.component.preview + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import com.tangem.core.ui.components.currency.icon.CurrencyIconState +import com.tangem.core.ui.components.fields.entity.SearchBarUM +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.domain.managetokens.model.ManagedCryptoCurrency +import com.tangem.domain.tokens.model.Network +import com.tangem.features.managetokens.component.OnboardingManageTokensComponent +import com.tangem.features.managetokens.entity.item.CurrencyItemUM +import com.tangem.features.managetokens.entity.item.CurrencyNetworkUM +import com.tangem.features.managetokens.entity.managetokens.OnboardingManageTokensUM +import com.tangem.features.managetokens.impl.R +import com.tangem.features.managetokens.ui.OnboardingManageTokensContent +import kotlinx.collections.immutable.toImmutableList +import kotlinx.collections.immutable.toPersistentList + +internal class PreviewOnboardingManageTokensComponent( + private val isLoading: Boolean = false, +) : OnboardingManageTokensComponent { + + private val state = OnboardingManageTokensUM( + items = initItems(), + isInitialBatchLoading = false, + isNextBatchLoading = true, + loadMore = { false }, + onBack = {}, + search = SearchBarUM( + placeholderText = resourceReference(R.string.manage_tokens_search_placeholder), + query = "", + onQueryChange = {}, + isActive = false, + onActiveChange = { }, + ), + actionButtonConfig = OnboardingManageTokensUM.ActionButtonConfig.Later( + onClick = {}, + showProgress = false, + ), + ) + + @Composable + override fun Content(modifier: Modifier) { + OnboardingManageTokensContent(state = state, modifier = modifier) + } + + private fun initItems() = List(size = 30) { index -> + if (isLoading) { + CurrencyItemUM.Loading(index) + } else { + if (index < 2) { + getCustomItem(index) + } else { + getBasicItem(index) + } + } + }.toPersistentList() + + private fun getCustomItem(index: Int) = CurrencyItemUM.Custom( + id = ManagedCryptoCurrency.ID(index.toString()), + name = "Custom token $index", + symbol = "CT$index", + icon = CurrencyIconState.CustomTokenIcon( + tint = Color.White, + background = Color.Black, + topBadgeIconResId = R.drawable.img_eth_22, + isGrayscale = false, + showCustomBadge = true, + ), + onRemoveClick = {}, + ) + + private fun getBasicItem(index: Int) = CurrencyItemUM.Basic( + id = ManagedCryptoCurrency.ID(index.toString()), + name = "Currency $index", + symbol = "C$index", + icon = CurrencyIconState.CoinIcon( + url = null, + fallbackResId = R.drawable.img_btc_22, + isGrayscale = false, + showCustomBadge = false, + ), + networks = if (index == 2) { + CurrencyItemUM.Basic.NetworksUM.Expanded(getCurrencyNetworks()) + } else { + CurrencyItemUM.Basic.NetworksUM.Collapsed + }, + onExpandClick = {}, + ) + + private fun getCurrencyNetworks() = List(size = 3) { networkIndex -> + CurrencyNetworkUM( + network = Network( + id = Network.ID(networkIndex.toString()), + backendId = networkIndex.toString(), + name = "Network $networkIndex", + currencySymbol = "N$networkIndex", + derivationPath = Network.DerivationPath.Card(""), + isTestnet = false, + standardType = Network.StandardType.ERC20, + hasFiatFeeRate = false, + canHandleTokens = false, + ), + name = "NETWORK$networkIndex", + type = "N$networkIndex", + iconResId = R.drawable.ic_eth_16, + isMainNetwork = networkIndex == 0, + isSelected = false, + onSelectedStateChange = {}, + onLongClick = {}, + ) + }.toImmutableList() +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/di/ComponentModule.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/di/ComponentModule.kt index ea21e92af1..e39d592b09 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/di/ComponentModule.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/di/ComponentModule.kt @@ -16,6 +16,12 @@ internal interface ComponentModule { @Singleton fun bindManageTokensComponentFactory(factory: DefaultManageTokensComponent.Factory): ManageTokensComponent.Factory + @Binds + @Singleton + fun bindOnboardingManageTokensComponentFactory( + factory: DefaultOnboardingManageTokensComponent.Factory, + ): OnboardingManageTokensComponent.Factory + @Binds @Singleton fun bindAddCustomTokenComponentFactory( diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/di/ModelModule.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/di/ModelModule.kt index 0f33d5994f..101e51cfdd 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/di/ModelModule.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/di/ModelModule.kt @@ -5,6 +5,7 @@ import com.tangem.core.decompose.model.Model import com.tangem.features.managetokens.model.CustomTokenFormModel import com.tangem.features.managetokens.model.CustomTokenSelectorModel import com.tangem.features.managetokens.model.ManageTokensModel +import com.tangem.features.managetokens.model.OnboardingManageTokensModel import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -20,6 +21,11 @@ internal interface ModelModule { @ClassKey(ManageTokensModel::class) fun provideManageTokensModel(model: ManageTokensModel): Model + @Binds + @IntoMap + @ClassKey(OnboardingManageTokensModel::class) + fun provideOnboardingManageTokensModel(model: OnboardingManageTokensModel): Model + @Binds @IntoMap @ClassKey(CustomTokenFormModel::class) diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/managetokens/ManageTokensUM.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/managetokens/ManageTokensUM.kt index 8a0bf34154..d16e17e84a 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/managetokens/ManageTokensUM.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/managetokens/ManageTokensUM.kt @@ -14,7 +14,7 @@ internal sealed class ManageTokensUM { abstract val isInitialBatchLoading: Boolean abstract val isNextBatchLoading: Boolean abstract val items: ImmutableList - abstract val topBar: ManageTokensTopBarUM + abstract val topBar: ManageTokensTopBarUM? abstract val search: SearchBarUM abstract val loadMore: () -> Boolean abstract val scrollToTop: StateEvent @@ -24,7 +24,7 @@ internal sealed class ManageTokensUM { override val isInitialBatchLoading: Boolean, override val isNextBatchLoading: Boolean, override val items: ImmutableList, - override val topBar: ManageTokensTopBarUM, + override val topBar: ManageTokensTopBarUM?, override val search: SearchBarUM, override val loadMore: () -> Boolean, override val scrollToTop: StateEvent = consumedEvent(), @@ -35,7 +35,7 @@ internal sealed class ManageTokensUM { override val isInitialBatchLoading: Boolean, override val isNextBatchLoading: Boolean, override val items: ImmutableList, - override val topBar: ManageTokensTopBarUM, + override val topBar: ManageTokensTopBarUM?, override val search: SearchBarUM, override val loadMore: () -> Boolean, override val scrollToTop: StateEvent = consumedEvent(), diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/managetokens/OnboardingManageTokensUM.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/managetokens/OnboardingManageTokensUM.kt new file mode 100644 index 0000000000..005f3d43ce --- /dev/null +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/managetokens/OnboardingManageTokensUM.kt @@ -0,0 +1,41 @@ +package com.tangem.features.managetokens.entity.managetokens + +import com.tangem.core.ui.components.fields.entity.SearchBarUM +import com.tangem.core.ui.event.StateEvent +import com.tangem.core.ui.event.consumedEvent +import com.tangem.features.managetokens.entity.item.CurrencyItemUM +import kotlinx.collections.immutable.ImmutableList + +internal data class OnboardingManageTokensUM( + val onBack: () -> Unit, + val isInitialBatchLoading: Boolean, + val isNextBatchLoading: Boolean, + val items: ImmutableList, + val loadMore: () -> Boolean, + val search: SearchBarUM, + val scrollToTop: StateEvent = consumedEvent(), + val actionButtonConfig: ActionButtonConfig, +) { + sealed class ActionButtonConfig { + abstract val onClick: () -> Unit + abstract val showProgress: Boolean + + data class Continue( + override val onClick: () -> Unit, + override val showProgress: Boolean = false, + val showTangemIcon: Boolean, + ) : ActionButtonConfig() + + data class Later( + override val onClick: () -> Unit, + override val showProgress: Boolean = false, + ) : ActionButtonConfig() + + fun copySealed(showProgress: Boolean): ActionButtonConfig { + return when (this) { + is Continue -> copy(showProgress = showProgress) + is Later -> copy(showProgress = showProgress) + } + } + } +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/ManageTokensModel.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/ManageTokensModel.kt index 0e6780749b..f5375b9b43 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/ManageTokensModel.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/ManageTokensModel.kt @@ -75,7 +75,7 @@ internal class ManageTokensModel @Inject constructor( observeSearchQueryChanges() modelScope.launch { - manageTokensListManager.launchPagination(params) + manageTokensListManager.launchPagination(source = params.source, userWalletId = params.userWalletId) } } @@ -159,12 +159,7 @@ internal class ManageTokensModel @Inject constructor( } } .sample(periodMillis = 1_000) - .onEach { query -> - manageTokensListManager.search( - userWalletId = params.userWalletId, - query = query, - ) - } + .onEach { query -> manageTokensListManager.search(userWalletId = params.userWalletId, query = query) } .launchIn(modelScope) } @@ -272,10 +267,7 @@ internal class ManageTokensModel @Inject constructor( if (state.isInitialBatchLoading || state.isNextBatchLoading) return false modelScope.launch { - manageTokensListManager.loadMore( - userWalletId = params.userWalletId, - query = state.search.query, - ) + manageTokensListManager.loadMore(userWalletId = params.userWalletId, query = state.search.query) } return true diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/OnboardingManageTokensModel.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/OnboardingManageTokensModel.kt new file mode 100644 index 0000000000..2043b74d96 --- /dev/null +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/OnboardingManageTokensModel.kt @@ -0,0 +1,297 @@ +package com.tangem.features.managetokens.model + +import arrow.core.flatten +import arrow.core.getOrElse +import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.core.decompose.ui.UiMessageSender +import com.tangem.core.ui.components.fields.entity.SearchBarUM +import com.tangem.core.ui.event.consumedEvent +import com.tangem.core.ui.event.triggeredEvent +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.message.SnackbarMessage +import com.tangem.domain.card.HasMissedDerivationsUseCase +import com.tangem.domain.managetokens.SaveManagedTokensUseCase +import com.tangem.domain.redux.OnboardingManageTokensAction +import com.tangem.domain.redux.ReduxStateHolder +import com.tangem.features.managetokens.analytics.ManageTokensAnalyticEvent +import com.tangem.features.managetokens.component.ManageTokensSource +import com.tangem.features.managetokens.component.OnboardingManageTokensComponent +import com.tangem.features.managetokens.entity.item.CurrencyItemUM +import com.tangem.features.managetokens.entity.managetokens.OnboardingManageTokensUM +import com.tangem.features.managetokens.impl.R +import com.tangem.features.managetokens.utils.list.ChangedCurrencies +import com.tangem.features.managetokens.utils.list.ManageTokensListManager +import com.tangem.pagination.BatchFetchResult +import com.tangem.pagination.PaginationStatus +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toPersistentList +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch +import timber.log.Timber +import javax.inject.Inject + +@Suppress("LongParameterList") +@ComponentScoped +internal class OnboardingManageTokensModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, + private val manageTokensListManager: ManageTokensListManager, + private val messageSender: UiMessageSender, + private val reduxStateHolder: ReduxStateHolder, + private val saveManagedTokensUseCase: SaveManagedTokensUseCase, + private val hasMissedDerivationsUseCase: HasMissedDerivationsUseCase, + private val analyticsEventHandler: AnalyticsEventHandler, + paramsContainer: ParamsContainer, +) : Model() { + + private val params: OnboardingManageTokensComponent.Params = paramsContainer.require() + val state: MutableStateFlow = MutableStateFlow(getInitialState()) + + init { + manageTokensListManager.uiItems + .onEach { items -> updateItems(items) } + .launchIn(modelScope) + + manageTokensListManager.paginationStatus + .onEach { status -> updatePaginationStatus(status) } + .launchIn(modelScope) + + manageTokensListManager.currenciesToAdd + .onEach(::handleNewAddedCurrencies) + .launchIn(modelScope) + + observeSearchQueryChanges() + + modelScope.launch { + manageTokensListManager.launchPagination( + source = ManageTokensSource.ONBOARDING, + userWalletId = params.userWalletId, + ) + } + } + + private fun getInitialState(): OnboardingManageTokensUM { + analyticsEventHandler.send(ManageTokensAnalyticEvent.ScreenOpened(source = ManageTokensSource.ONBOARDING)) + + return OnboardingManageTokensUM( + isInitialBatchLoading = true, + isNextBatchLoading = false, + items = getLoadingItems(), + loadMore = ::loadMoreItems, + onBack = {}, + search = SearchBarUM( + placeholderText = resourceReference(R.string.manage_tokens_search_placeholder), + query = "", + onQueryChange = ::searchCurrencies, + isActive = false, + onActiveChange = ::toggleSearchBar, + ), + actionButtonConfig = OnboardingManageTokensUM.ActionButtonConfig.Later( + onClick = ::onLaterClick, + showProgress = false, + ), + ) + } + + @OptIn(FlowPreview::class) + private fun observeSearchQueryChanges() { + state + .distinctUntilChanged { old, new -> + // It's also used to skip search activation to avoid searching an empty query + old.search.query == new.search.query && + (old.search.isActive == new.search.isActive || new.search.isActive) + } + .transform { state -> + val query = state.search.query + + if (state.search.isActive) { + emit(query) + } + } + .sample(periodMillis = 1_000) + .onEach { query -> manageTokensListManager.search(userWalletId = params.userWalletId, query = query) } + .launchIn(modelScope) + } + + private fun updateItems(items: ImmutableList) { + val updatedState = state.updateAndGet { state -> state.copy(items = items) } + + if (updatedState.items.isEmpty() && updatedState.search.isActive) { + val event = ManageTokensAnalyticEvent.TokensIsNotFound( + query = updatedState.search.query, + source = ManageTokensSource.ONBOARDING, + ) + analyticsEventHandler.send(event) + } + } + + private fun updatePaginationStatus(status: PaginationStatus<*>) { + state.update { state -> + when (status) { + is PaginationStatus.None, + is PaginationStatus.InitialLoading, + -> { + if (state.search.isActive) { + state.copy(items = getLoadingItems()) + } else { + state.copy(items = getLoadingItems(), isInitialBatchLoading = true) + } + } + is PaginationStatus.NextBatchLoading -> state.copy(isNextBatchLoading = true) + is PaginationStatus.InitialLoadingError -> { + val message = SnackbarMessage( + message = status.throwable.localizedMessage + ?.let(::stringReference) + ?: resourceReference(R.string.common_error), + ) + messageSender.send(message) + + state.copy( + isInitialBatchLoading = false, + isNextBatchLoading = false, + ) + } + is PaginationStatus.Paginating -> { + (status.lastResult as? BatchFetchResult.Error)?.let { fetchError -> + Timber.e(fetchError.throwable) + } + + state.copy( + isInitialBatchLoading = false, + isNextBatchLoading = false, + scrollToTop = if (state.isInitialBatchLoading && state.items.isNotEmpty()) { + triggeredEvent( + data = Unit, + onConsume = ::consumeScrollToTopEvent, + ) + } else { + state.scrollToTop + }, + ) + } + is PaginationStatus.EndOfPagination -> state.copy( + isInitialBatchLoading = false, + isNextBatchLoading = false, + ) + } + } + } + + private fun getLoadingItems(): ImmutableList { + return List(size = 10) { index -> + CurrencyItemUM.Loading(index) + }.toPersistentList() + } + + private fun consumeScrollToTopEvent() { + state.update { state -> state.copy(scrollToTop = consumedEvent()) } + } + + private suspend fun handleNewAddedCurrencies(currenciesToAdd: ChangedCurrencies) { + if (currenciesToAdd.isEmpty()) { + state.update { state -> + state.copy( + actionButtonConfig = OnboardingManageTokensUM.ActionButtonConfig.Later(onClick = ::onLaterClick), + ) + } + } else { + val hasMissedDerivations = hasMissedDerivationsUseCase.invoke( + userWalletId = params.userWalletId, + networksWithDerivationPath = currenciesToAdd.values + .flatten() + .toSet() + .associate { it.backendId to null }, + ) + state.update { state -> + state.copy( + actionButtonConfig = OnboardingManageTokensUM.ActionButtonConfig.Continue( + onClick = ::saveChanges, + showTangemIcon = hasMissedDerivations, + ), + ) + } + } + } + + private fun loadMoreItems(): Boolean { + val state = state.value + if (state.isInitialBatchLoading || state.isNextBatchLoading) return false + + modelScope.launch { + manageTokensListManager.loadMore(userWalletId = params.userWalletId, query = state.search.query) + } + + return true + } + + private fun saveChanges() = resource( + acquire = { + state.update { state -> + state.copy(actionButtonConfig = state.actionButtonConfig.copySealed(showProgress = true)) + } + }, + release = { + state.update { state -> + state.copy(actionButtonConfig = state.actionButtonConfig.copySealed(showProgress = false)) + } + }, + ) { + val event = ManageTokensAnalyticEvent.TokenAdded( + tokensCount = manageTokensListManager.currenciesToAdd.value.values.sumOf { it.size }, + source = ManageTokensSource.ONBOARDING, + ) + analyticsEventHandler.send(event) + + saveManagedTokensUseCase( + userWalletId = requireNotNull(params.userWalletId), + currenciesToAdd = manageTokensListManager.currenciesToAdd.value, + currenciesToRemove = manageTokensListManager.currenciesToRemove.value, + ).getOrElse { + Timber.e(it, "Failed to save changes") + return@resource + } + + reduxStateHolder.dispatch(OnboardingManageTokensAction.CurrenciesSaved) + } + + private fun onLaterClick() = resource( + acquire = { + state.update { state -> + state.copy(actionButtonConfig = state.actionButtonConfig.copySealed(showProgress = true)) + } + }, + release = { + state.update { state -> + state.copy(actionButtonConfig = state.actionButtonConfig.copySealed(showProgress = false)) + } + }, + ) { + reduxStateHolder.dispatch(OnboardingManageTokensAction.CurrenciesSaved) + } + + private fun searchCurrencies(query: String) { + state.update { state -> + state.copy( + search = state.search.copy( + query = query, + isActive = true, + ), + ) + } + } + + private fun toggleSearchBar(isActive: Boolean) { + state.update { state -> + state.copy( + search = state.search.copy( + isActive = isActive, + ), + ) + } + } +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/ManageTokensScreen.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/ManageTokensScreen.kt index 121259e7ac..6b2547a8fd 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/ManageTokensScreen.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/ManageTokensScreen.kt @@ -59,7 +59,9 @@ import com.tangem.core.ui.res.LocalSnackbarHostState import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.utils.WindowInsetsZero +import com.tangem.domain.wallets.models.UserWalletId import com.tangem.features.managetokens.component.ManageTokensComponent +import com.tangem.features.managetokens.component.ManageTokensSource import com.tangem.features.managetokens.component.preview.PreviewManageTokensComponent import com.tangem.features.managetokens.entity.item.CurrencyItemUM import com.tangem.features.managetokens.entity.item.CurrencyItemUM.Basic.NetworksUM @@ -128,19 +130,21 @@ internal fun ManageTokensScreen(state: ManageTokensUM, modifier: Modifier = Modi } @Composable -private fun ManageTokensTopBar(topBar: ManageTokensTopBarUM, search: SearchBarUM, modifier: Modifier = Modifier) { +private fun ManageTokensTopBar(topBar: ManageTokensTopBarUM?, search: SearchBarUM, modifier: Modifier = Modifier) { Column( modifier = modifier.background(TangemTheme.colors.background.primary), verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16), ) { - TangemTopAppBar( - title = topBar.title.resolveReference(), - startButton = TopAppBarButtonUM.Back(topBar.onBackButtonClick), - endButton = when (topBar) { - is ManageTokensTopBarUM.ManageContent -> topBar.endButton - is ManageTokensTopBarUM.ReadContent -> null - }, - ) + if (topBar != null) { + TangemTopAppBar( + title = topBar.title.resolveReference(), + startButton = TopAppBarButtonUM.Back(topBar.onBackButtonClick), + endButton = when (topBar) { + is ManageTokensTopBarUM.ManageContent -> topBar.endButton + is ManageTokensTopBarUM.ReadContent -> null + }, + ) + } SearchBar( modifier = Modifier .padding(bottom = TangemTheme.dimens.spacing12) @@ -196,7 +200,7 @@ private fun Content(state: ManageTokensUM, modifier: Modifier = Modifier) { } @Composable -private fun Currencies( +internal fun Currencies( listState: LazyListState, items: ImmutableList, showLoadingItem: Boolean, @@ -466,8 +470,24 @@ private fun Preview_ManageTokens( private class PreviewManageTokensComponentProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( - PreviewManageTokensComponent(), - PreviewManageTokensComponent(isLoading = true), + PreviewManageTokensComponent( + isLoading = true, + params = ManageTokensComponent.Params( + source = ManageTokensSource.ONBOARDING, + userWalletId = UserWalletId("wallet_id"), + ), + ), + PreviewManageTokensComponent( + isLoading = false, + params = ManageTokensComponent.Params(source = ManageTokensSource.ONBOARDING, userWalletId = null), + ), + PreviewManageTokensComponent( + isLoading = false, + params = ManageTokensComponent.Params( + source = ManageTokensSource.ONBOARDING, + userWalletId = UserWalletId("wallet_id"), + ), + ), ) } // endregion Preview \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/OnboardingManageTokensContent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/OnboardingManageTokensContent.kt new file mode 100644 index 0000000000..5a7d408f89 --- /dev/null +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/OnboardingManageTokensContent.kt @@ -0,0 +1,167 @@ +package com.tangem.features.managetokens.ui + +import android.content.res.Configuration +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material3.FabPosition +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.NestedScrollSource +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import com.tangem.core.ui.components.BottomFade +import com.tangem.core.ui.components.PrimaryButton +import com.tangem.core.ui.components.PrimaryButtonIconEnd +import com.tangem.core.ui.components.SecondaryButton +import com.tangem.core.ui.components.fields.SearchBar +import com.tangem.core.ui.components.snackbar.TangemSnackbarHost +import com.tangem.core.ui.event.EventEffect +import com.tangem.core.ui.res.LocalSnackbarHostState +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.core.ui.utils.WindowInsetsZero +import com.tangem.features.managetokens.component.OnboardingManageTokensComponent +import com.tangem.features.managetokens.component.preview.PreviewOnboardingManageTokensComponent +import com.tangem.features.managetokens.entity.managetokens.OnboardingManageTokensUM +import com.tangem.features.managetokens.impl.R + +@Composable +internal fun OnboardingManageTokensContent(state: OnboardingManageTokensUM, modifier: Modifier = Modifier) { + val keyboardController = LocalSoftwareKeyboardController.current + val nestedScrollConnection = remember { + object : NestedScrollConnection { + override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { + keyboardController?.hide() + + return super.onPreScroll(available, source) + } + } + } + + Scaffold( + modifier = modifier.nestedScroll(nestedScrollConnection), + containerColor = TangemTheme.colors.background.primary, + contentWindowInsets = WindowInsetsZero, + topBar = { + SearchBar( + modifier = Modifier + .padding(vertical = TangemTheme.dimens.spacing12, horizontal = TangemTheme.dimens.spacing16), + state = state.search, + ) + }, + content = { innerPadding -> + Content( + modifier = Modifier + .padding(innerPadding) + .fillMaxSize(), + state = state, + ) + }, + snackbarHost = { + TangemSnackbarHost( + modifier = Modifier.padding(all = TangemTheme.dimens.spacing16), + hostState = LocalSnackbarHostState.current, + ) + }, + floatingActionButtonPosition = FabPosition.Center, + floatingActionButton = { + FloatingActionButton( + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing16) + .fillMaxWidth(), + config = state.actionButtonConfig, + ) + }, + ) +} + +@Composable +private fun Content(state: OnboardingManageTokensUM, modifier: Modifier = Modifier) { + val listState = rememberLazyListState() + + Box(modifier = modifier) { + Currencies( + modifier = Modifier.fillMaxSize(), + listState = listState, + items = state.items, + showLoadingItem = state.isNextBatchLoading, + onLoadMore = state.loadMore, + isEditable = true, + ) + + BottomFade(modifier = Modifier.align(Alignment.BottomCenter)) + } + + EventEffect(event = state.scrollToTop) { + listState.animateScrollToItem(index = 0) + } +} + +@Composable +private fun FloatingActionButton(config: OnboardingManageTokensUM.ActionButtonConfig, modifier: Modifier = Modifier) { + when (config) { + is OnboardingManageTokensUM.ActionButtonConfig.Continue -> ContinueButton(config = config, modifier = modifier) + is OnboardingManageTokensUM.ActionButtonConfig.Later -> SecondaryButton( + modifier = modifier, + text = stringResource(id = R.string.common_later), + showProgress = config.showProgress, + onClick = config.onClick, + ) + } +} + +@Composable +private fun ContinueButton( + config: OnboardingManageTokensUM.ActionButtonConfig.Continue, + modifier: Modifier = Modifier, +) { + if (config.showTangemIcon) { + PrimaryButtonIconEnd( + modifier = modifier, + text = stringResource(id = R.string.common_continue), + iconResId = R.drawable.ic_tangem_24, + showProgress = config.showProgress, + onClick = config.onClick, + ) + } else { + PrimaryButton( + modifier = modifier, + text = stringResource(id = R.string.common_continue), + showProgress = config.showProgress, + onClick = config.onClick, + ) + } +} + +// region Preview +@Preview(showBackground = true, widthDp = 360, heightDp = 800) +@Preview(showBackground = true, widthDp = 360, heightDp = 800, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun Preview_ManageTokens( + @PreviewParameter(PreviewOnboardingManageTokensComponentProvider::class) component: OnboardingManageTokensComponent, +) { + TangemThemePreview { + component.Content(Modifier.fillMaxWidth()) + } +} + +private class PreviewOnboardingManageTokensComponentProvider : + CollectionPreviewParameterProvider( + collection = listOf( + PreviewOnboardingManageTokensComponent(isLoading = false), + PreviewOnboardingManageTokensComponent(isLoading = true), + ), + ) +// endregion \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensListManager.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensListManager.kt index 3c584c503b..0c648acf6e 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensListManager.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensListManager.kt @@ -16,7 +16,6 @@ import com.tangem.domain.managetokens.model.* import com.tangem.domain.tokens.model.Network import com.tangem.domain.wallets.models.UserWalletId import com.tangem.features.managetokens.analytics.ManageTokensAnalyticEvent -import com.tangem.features.managetokens.component.ManageTokensComponent import com.tangem.features.managetokens.component.ManageTokensSource import com.tangem.features.managetokens.entity.item.CurrencyItemUM import com.tangem.features.managetokens.impl.R @@ -81,9 +80,9 @@ internal class ManageTokensListManager @Inject constructor( .distinctUntilChanged() val uiItems: Flow> = uiManager.items - suspend fun launchPagination(params: ManageTokensComponent.Params) = coroutineScope { + suspend fun launchPagination(source: ManageTokensSource, userWalletId: UserWalletId?) = coroutineScope { scope = this - source = params.source + this@ManageTokensListManager.source = source val batchFlow = getManagedTokensUseCase( context = ManageTokensListBatchingContext( @@ -93,13 +92,13 @@ internal class ManageTokensListManager @Inject constructor( ) batchFlow.state - .onEach { state -> updateState(state, params.userWalletId) } + .onEach { state -> updateState(state, userWalletId) } .flowOn(dispatchers.default) .launchIn(scope = this) .saveIn(jobHolder) // Initial load - reload(params.userWalletId) + reload(userWalletId) } suspend fun reload(userWalletId: UserWalletId?) { diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt index aff1bf94a8..e0e25052fc 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt @@ -195,7 +195,7 @@ internal class MarketsPortfolioModel @Inject constructor( hasMissedDerivationsUseCase.invoke( userWalletId = selectedWalletId, networksWithDerivationPath = addToPortfolioData.addedNetworks[selectedWalletId].orEmpty() - .associate { Network.ID(it.networkId) to null }, + .associate { it.networkId to null }, ) } else { false