From db4efb71a1ddabaf1037fd43b0246fd0409904eb Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 28 Jul 2023 13:36:01 +0800 Subject: [PATCH] Updated on 2026-08-14 --- .../com/tangem/data/tokens/mock/MockTokens.kt | 230 +++++++++--------- features/wallet/impl/build.gradle.kts | 1 + .../router/DefaultWalletRouter.kt | 8 +- .../presentation/router/InnerWalletRouter.kt | 3 + .../wallet/state/WalletBottomSheetConfig.kt | 38 ++- .../wallet/state/WalletNotification.kt | 14 +- .../WalletLoadedTokensListConverter.kt | 53 ++++ .../WalletSkeletonStateConverter.kt} | 86 +++---- .../state/factory/WalletStateFactory.kt | 69 ++++++ .../wallet/ui/components/WalletBottomSheet.kt | 13 +- .../utils/TokenListToWalletStateConverter.kt | 70 ++++-- .../wallet/viewmodels/JobHolder.kt | 21 ++ .../wallet/viewmodels/WalletClickCallbacks.kt | 28 +++ .../WalletNotificationsListFactory.kt | 81 ++++-- .../wallet/viewmodels/WalletViewModel.kt | 194 +++++++++------ 15 files changed, 595 insertions(+), 314 deletions(-) create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletLoadedTokensListConverter.kt rename features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/{builder/WalletStateFactory.kt => factory/WalletSkeletonStateConverter.kt} (51%) create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletStateFactory.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/JobHolder.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletClickCallbacks.kt diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/mock/MockTokens.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/mock/MockTokens.kt index ac1c0809ea..97384de272 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/mock/MockTokens.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/mock/MockTokens.kt @@ -5,116 +5,126 @@ import com.tangem.domain.wallets.models.UserWalletId internal object MockTokens { - val token1 = Token( - id = Token.ID("token1"), - networkId = MockNetworks.network1.id, - name = "Token 1", - symbol = "T1", - isCustom = false, - decimals = 8, - iconUrl = null, - contractAddress = null, - derivationPath = null, - ) - val token2 = Token( - id = Token.ID("token2"), - networkId = MockNetworks.network1.id, - name = "Token 2", - symbol = "T2", - isCustom = false, - decimals = 8, - iconUrl = null, - contractAddress = "address", - derivationPath = null, - ) - val token3 = Token( - id = Token.ID("token3"), - networkId = MockNetworks.network1.id, - name = "Token 3", - symbol = "T3", - isCustom = false, - decimals = 8, - iconUrl = null, - contractAddress = "address", - derivationPath = null, - ) - val token4 = Token( - id = Token.ID("token4"), - networkId = MockNetworks.network2.id, - name = "Token 4", - symbol = "T4", - isCustom = false, - decimals = 8, - iconUrl = null, - contractAddress = null, - derivationPath = null, - ) - val token5 = Token( - id = Token.ID("token5"), - networkId = MockNetworks.network2.id, - name = "Token 5", - symbol = "T5", - isCustom = false, - decimals = 8, - iconUrl = null, - contractAddress = "address", - derivationPath = null, - ) - val token6 = Token( - id = Token.ID("token6"), - networkId = MockNetworks.network2.id, - name = "Token 6", - symbol = "T6", - isCustom = false, - decimals = 8, - iconUrl = null, - contractAddress = "address", - derivationPath = null, - ) - val token7 = Token( - id = Token.ID("token7"), - networkId = MockNetworks.network3.id, - name = "Token 7", - symbol = "T7", - isCustom = false, - decimals = 8, - iconUrl = null, - contractAddress = null, - derivationPath = null, - ) - val token8 = Token( - id = Token.ID("token8"), - networkId = MockNetworks.network3.id, - name = "Token 8", - symbol = "T8", - isCustom = false, - decimals = 8, - iconUrl = null, - contractAddress = "address", - derivationPath = null, - ) - val token9 = Token( - id = Token.ID("token9"), - networkId = MockNetworks.network3.id, - name = "Token 9", - symbol = "T9", - isCustom = false, - decimals = 8, - iconUrl = null, - contractAddress = "address", - derivationPath = null, - ) - val token10 = Token( - id = Token.ID("token10"), - networkId = MockNetworks.network3.id, - name = "Token 10", - symbol = "T10", - isCustom = false, - decimals = 8, - iconUrl = null, - contractAddress = "address", - derivationPath = null, - ) + val token1 + get() = Token( + id = Token.ID("token1"), + networkId = MockNetworks.network1.id, + name = "Token 1", + symbol = "T1", + isCustom = false, + decimals = 8, + iconUrl = null, + contractAddress = null, + derivationPath = null, + ) + val token2 + get() = Token( + id = Token.ID("token2"), + networkId = MockNetworks.network1.id, + name = "Token 2", + symbol = "T2", + isCustom = false, + decimals = 8, + iconUrl = null, + contractAddress = "address", + derivationPath = null, + ) + val token3 + get() = Token( + id = Token.ID("token3"), + networkId = MockNetworks.network1.id, + name = "Token 3", + symbol = "T3", + isCustom = false, + decimals = 8, + iconUrl = null, + contractAddress = "address", + derivationPath = null, + ) + val token4 + get() = Token( + id = Token.ID("token4"), + networkId = MockNetworks.network2.id, + name = "Token 4", + symbol = "T4", + isCustom = false, + decimals = 8, + iconUrl = null, + contractAddress = null, + derivationPath = null, + ) + val token5 + get() = Token( + id = Token.ID("token5"), + networkId = MockNetworks.network2.id, + name = "Token 5", + symbol = "T5", + isCustom = false, + decimals = 8, + iconUrl = null, + contractAddress = "address", + derivationPath = null, + ) + val token6 + get() = Token( + id = Token.ID("token6"), + networkId = MockNetworks.network2.id, + name = "Token 6", + symbol = "T6", + isCustom = false, + decimals = 8, + iconUrl = null, + contractAddress = "address", + derivationPath = null, + ) + val token7 + get() = Token( + id = Token.ID("token7"), + networkId = MockNetworks.network3.id, + name = "Token 7", + symbol = "T7", + isCustom = false, + decimals = 8, + iconUrl = null, + contractAddress = null, + derivationPath = null, + ) + val token8 + get() = Token( + id = Token.ID("token8"), + networkId = MockNetworks.network3.id, + name = "Token 8", + symbol = "T8", + isCustom = false, + decimals = 8, + iconUrl = null, + contractAddress = "address", + derivationPath = null, + ) + val token9 + get() = Token( + id = Token.ID("token9"), + networkId = MockNetworks.network3.id, + name = "Token 9", + symbol = "T9", + isCustom = false, + decimals = 8, + iconUrl = null, + contractAddress = "address", + derivationPath = null, + ) + val token10 + get() = Token( + id = Token.ID("token10"), + networkId = MockNetworks.network3.id, + name = "Token 10", + symbol = "T10", + isCustom = false, + decimals = 8, + iconUrl = null, + contractAddress = "address", + derivationPath = null, + ) val tokens get() = mapOf( diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts index 13a57f56c7..60ca50d490 100644 --- a/features/wallet/impl/build.gradle.kts +++ b/features/wallet/impl/build.gradle.kts @@ -45,6 +45,7 @@ dependencies { /** Domain modules */ implementation(projects.common) implementation(projects.domain.card) + implementation(projects.domain.demo) implementation(projects.domain.legacy) implementation(projects.domain.models) implementation(projects.domain.settings) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt index 3964582227..85fb11d43b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt @@ -74,11 +74,15 @@ internal class DefaultWalletRouter(private val navigationStateHolder: Navigation } override fun openOrganizeTokensScreen() { - navController.navigate(WalletScreens.ORGANIZE_TOKENS.name) + navController.navigate(route = WalletScreens.ORGANIZE_TOKENS.name) } override fun openDetailsScreen() { - navigationStateHolder.navigate(NavigationAction.NavigateTo(AppScreen.Details)) + navigationStateHolder.navigate(action = NavigationAction.NavigateTo(AppScreen.Details)) + } + + override fun openOnboardingScreen() { + navigationStateHolder.navigate(action = NavigationAction.NavigateTo(AppScreen.OnboardingWallet)) } private companion object { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt index 520d008d6d..866bbc9c3d 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt @@ -33,4 +33,7 @@ internal interface InnerWalletRouter : WalletRouter { /** Open details screen */ fun openDetailsScreen() + + /** Open onboarding screen */ + fun openOnboardingScreen() } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletBottomSheetConfig.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletBottomSheetConfig.kt index 5aafcd4464..5a8f12209b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletBottomSheetConfig.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletBottomSheetConfig.kt @@ -29,10 +29,14 @@ internal data class WalletBottomSheetConfig( @DrawableRes open val iconResId: Int, open val tint: Color? = null, val primaryButtonConfig: ButtonConfig, - val secondaryButtonConfig: ButtonConfig? = null, + val secondaryButtonConfig: ButtonConfig, ) { - data class ButtonConfig(val text: String, val onClick: () -> Unit, @DrawableRes val iconResId: Int? = null) + data class ButtonConfig( + val text: TextReference, + val onClick: () -> Unit, + @DrawableRes val iconResId: Int? = null, + ) data class UnlockWallets( val onUnlockClick: () -> Unit, @@ -45,9 +49,9 @@ internal data class WalletBottomSheetConfig( ), iconResId = R.drawable.ic_locked_24, tint = TangemColorPalette.Black, - primaryButtonConfig = ButtonConfig(text = "Unlock", onClick = onUnlockClick), + primaryButtonConfig = ButtonConfig(text = TextReference.Str(value = "Unlock"), onClick = onUnlockClick), secondaryButtonConfig = ButtonConfig( - text = "Scan card", + text = TextReference.Str(value = "Scan card"), onClick = onScanClick, iconResId = R.drawable.ic_tangem_24, ), @@ -61,19 +65,35 @@ internal data class WalletBottomSheetConfig( subtitle = TextReference.Str(value = "How was your experience with our app? Let us know:"), iconResId = R.drawable.ic_star_24, tint = TangemColorPalette.Tangerine, - primaryButtonConfig = ButtonConfig(text = "Rate the app", onClick = onRateTheAppClick), - secondaryButtonConfig = ButtonConfig(text = "Share feedback", onClick = onShareClick), + primaryButtonConfig = ButtonConfig( + text = TextReference.Str(value = "Rate the app"), + onClick = onRateTheAppClick, + ), + secondaryButtonConfig = ButtonConfig( + text = TextReference.Str(value = "Share feedback"), + onClick = onShareClick, + ), ) - data class MultiWalletAlreadySignedHashes(val onLearnClick: () -> Unit) : BottomSheetContentConfig( + data class CriticalWarningAlreadySignedHashes( + val onOkClick: () -> Unit, + val onCancelClick: () -> Unit, + ) : BottomSheetContentConfig( title = TextReference.Res( id = R.string.warning_important_security_info, formatArgs = WrappedList(listOf("\u26A0")), ), - subtitle = TextReference.Res(id = R.string.warning_signed_tx_previously), + subtitle = TextReference.Res(id = R.string.alert_signed_hashes_message), iconResId = R.drawable.img_attention_20, tint = null, - primaryButtonConfig = ButtonConfig(text = "Learn more", onClick = onLearnClick), + primaryButtonConfig = ButtonConfig( + text = TextReference.Res(id = R.string.common_ok), + onClick = onOkClick, + ), + secondaryButtonConfig = ButtonConfig( + text = TextReference.Res(id = R.string.common_cancel), + onClick = onCancelClick, + ), ) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletNotification.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletNotification.kt index b1813c0544..842f92ccfe 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletNotification.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletNotification.kt @@ -81,26 +81,26 @@ sealed class WalletNotification(open val state: NotificationState) { ) /** - * "Wallet already signed hashes" notification + * "Already topped up and signed hashes" warning notification * - * @property onClick lambda be invoked when notification is clicked + * @property onClick lambda be invoked when notification's close button is clicked */ - data class AlreadyToppedUpAndSignedHashes(override val onClick: () -> Unit) : Clickable, WalletNotification( - state = NotificationState.Clickable( + data class WarningAlreadySignedHashes(override val onClick: () -> Unit) : Clickable, WalletNotification( + state = NotificationState.Closable( title = TextReference.Res(id = R.string.common_warning), subtitle = TextReference.Res(id = R.string.alert_card_signed_transactions), iconResId = R.drawable.img_attention_20, - onClick = onClick, tint = null, + onCloseClick = onClick, ), ) /** - * "Multi wallet already signed hashes" notification + * "Already signed hashes" critical warning notification * * @property onClick lambda be invoked when notification is clicked */ - data class SignedTransactionsInThePast(override val onClick: () -> Unit) : Clickable, WalletNotification( + data class CriticalWarningAlreadySignedHashes(override val onClick: () -> Unit) : Clickable, WalletNotification( state = NotificationState.Clickable( title = TextReference.Res( id = R.string.warning_important_security_info, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletLoadedTokensListConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletLoadedTokensListConverter.kt new file mode 100644 index 0000000000..138f36b6fd --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletLoadedTokensListConverter.kt @@ -0,0 +1,53 @@ +package com.tangem.feature.wallet.presentation.wallet.state.factory + +import arrow.core.Either +import com.tangem.common.Provider +import com.tangem.domain.tokens.error.TokenListError +import com.tangem.domain.tokens.model.TokenList +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder +import com.tangem.feature.wallet.presentation.wallet.state.factory.WalletLoadedTokensListConverter.LoadedTokensListModel +import com.tangem.feature.wallet.presentation.wallet.utils.TokenListErrorToWalletStateConverter +import com.tangem.feature.wallet.presentation.wallet.utils.TokenListToWalletStateConverter +import com.tangem.utils.converter.Converter + +/** + * Converter from loaded [TokenListError] or [TokenList] to [WalletStateHolder] + * + * @property currentStateProvider current ui state provider + * +[REDACTED_AUTHOR] + */ +internal class WalletLoadedTokensListConverter( + private val currentStateProvider: Provider, +) : Converter { + + private val tokenListStateConverter = TokenListToWalletStateConverter( + currentState = currentStateProvider(), + isWalletContentHidden = false, // TODO: [REDACTED_JIRA] + fiatCurrencyCode = "USD", // TODO: [REDACTED_JIRA] + fiatCurrencySymbol = "$", // TODO: [REDACTED_JIRA] + ) + + private val tokenListErrorStateConverter = TokenListErrorToWalletStateConverter( + currentState = currentStateProvider(), + ) + + override fun convert(value: LoadedTokensListModel): WalletStateHolder { + return value.tokenListEither.fold( + ifLeft = tokenListErrorStateConverter::convert, + ifRight = { + tokenListStateConverter.convert( + value = TokenListToWalletStateConverter.TokensListModel( + tokenList = it, + isRefreshing = value.isRefreshing, + ), + ) + }, + ) + } + + data class LoadedTokensListModel( + val tokenListEither: Either, + val isRefreshing: Boolean, + ) +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/builder/WalletStateFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletSkeletonStateConverter.kt similarity index 51% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/builder/WalletStateFactory.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletSkeletonStateConverter.kt index 081985c3f6..e372cb1500 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/builder/WalletStateFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletSkeletonStateConverter.kt @@ -1,85 +1,75 @@ -package com.tangem.feature.wallet.presentation.wallet.state.builder +package com.tangem.feature.wallet.presentation.wallet.state.factory -import com.tangem.common.Provider +import com.tangem.core.ui.components.marketprice.MarketPriceBlockState +import com.tangem.domain.common.CardTypesResolver import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.wallet.presentation.common.WalletPreviewData -import com.tangem.feature.wallet.presentation.router.InnerWalletRouter import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver import com.tangem.feature.wallet.presentation.wallet.state.* +import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickCallbacks +import com.tangem.utils.converter.Converter import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList -internal class WalletStateFactory( - private val routerProvider: Provider, - private val onScanCardClick: () -> Unit, - private val onWalletChange: (Int) -> Unit, - private val onRefreshSwipe: () -> Unit, -) { +/** + * Converter from loaded list of [UserWallet] to skeleton state of screen [WalletStateHolder] + * + * @property clickCallbacks screen click callbacks + * +[REDACTED_AUTHOR] + */ +internal class WalletSkeletonStateConverter( + private val clickCallbacks: WalletClickCallbacks, +) : Converter, WalletStateHolder> { - fun getInitialState(): WalletStateHolder = WalletStateHolder.Loading(onBackClick = ::onBackClick) - - fun getContentState(wallets: List): WalletStateHolder { - val cardTypeResolver = requireNotNull(wallets.firstOrNull()).scanResponse.cardTypesResolver + override fun convert(value: List): WalletStateHolder { + val cardTypeResolver = requireNotNull(value.firstOrNull()).scanResponse.cardTypesResolver return if (cardTypeResolver.isMultiwalletAllowed()) { - createMultiCurrencyState(wallets) + createMultiCurrencyState(value) } else { - createSingleCurrencyState(wallets) + createSingleCurrencyState(value, cardTypeResolver) } } private fun createMultiCurrencyState(wallets: List): WalletStateHolder.MultiCurrencyContent { return WalletStateHolder.MultiCurrencyContent( - onBackClick = ::onBackClick, + onBackClick = clickCallbacks::onBackClick, topBarConfig = createTopBarConfig(), walletsListConfig = createWalletsListConfig(wallets), pullToRefreshConfig = createPullToRefreshConfig(), contentItems = persistentListOf(), notifications = persistentListOf(), - bottomSheet = WalletBottomSheetConfig( - // TODO: check notifications - isShow = false, - onDismissRequest = {}, - content = WalletBottomSheetConfig.BottomSheetContentConfig.LikeTangemApp( - onRateTheAppClick = {}, - onShareClick = {}, - ), - ), - onOrganizeTokensClick = routerProvider()::openOrganizeTokensScreen, + bottomSheet = null, + onOrganizeTokensClick = clickCallbacks::onOrganizeTokensClick, ) } - private fun createSingleCurrencyState(wallets: List): WalletStateHolder.SingleCurrencyContent { + private fun createSingleCurrencyState( + wallets: List, + cardTypeResolver: CardTypesResolver, + ): WalletStateHolder.SingleCurrencyContent { return WalletStateHolder.SingleCurrencyContent( - onBackClick = ::onBackClick, + onBackClick = clickCallbacks::onBackClick, topBarConfig = createTopBarConfig(), walletsListConfig = createWalletsListConfig(wallets), pullToRefreshConfig = createPullToRefreshConfig(), contentItems = persistentListOf(), notifications = persistentListOf(), - bottomSheet = WalletBottomSheetConfig( - // TODO: check notifications - isShow = false, - onDismissRequest = {}, - content = WalletBottomSheetConfig.BottomSheetContentConfig.LikeTangemApp( - onRateTheAppClick = {}, - onShareClick = {}, - ), - ), + bottomSheet = null, buttons = WalletPreviewData.singleWalletScreenState.buttons, // TODO: create buttons - // TODO: create market price block - marketPriceBlockState = WalletPreviewData.singleWalletScreenState.marketPriceBlockState, + marketPriceBlockState = MarketPriceBlockState.Loading( + currencyName = cardTypeResolver.getBlockchain().currency, + ), ) } - private fun onBackClick() = routerProvider().popBackStack() - private fun createTopBarConfig(): WalletTopBarConfig { return WalletTopBarConfig( - onScanCardClick = onScanCardClick, - onMoreClick = routerProvider()::openDetailsScreen, + onScanCardClick = clickCallbacks::onScanCardClick, + onMoreClick = clickCallbacks::onDetailsClick, ) } @@ -88,22 +78,22 @@ internal class WalletStateFactory( selectedWalletIndex = 0, wallets = wallets.map { wallet -> WalletCardState.Loading( - id = if (wallet.scanResponse.cardTypesResolver.isMultiwalletAllowed()) { // TODO + // TODO remove after adding release logic for GetTokenListUseCase + id = if (wallet.scanResponse.cardTypesResolver.isMultiwalletAllowed()) { UserWalletId("123") } else { UserWalletId("321") }, - // TODO: wallet.walletId, title = wallet.name, - additionalInfo = "", // TODO + additionalInfo = "", // TODO add additional info resolver imageResId = WalletImageResolver.resolve(cardTypesResolver = wallet.scanResponse.cardTypesResolver), ) }.toImmutableList(), - onWalletChange = onWalletChange, + onWalletChange = clickCallbacks::onWalletChange, ) } private fun createPullToRefreshConfig(): WalletPullToRefreshConfig { - return WalletPullToRefreshConfig(isRefreshing = false, onRefresh = onRefreshSwipe) + return WalletPullToRefreshConfig(isRefreshing = false, onRefresh = clickCallbacks::onRefreshSwipe) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletStateFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletStateFactory.kt new file mode 100644 index 0000000000..3aec8d0359 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletStateFactory.kt @@ -0,0 +1,69 @@ +package com.tangem.feature.wallet.presentation.wallet.state.factory + +import arrow.core.Either +import com.tangem.common.Provider +import com.tangem.domain.tokens.error.TokenListError +import com.tangem.domain.tokens.model.TokenList +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.feature.wallet.presentation.wallet.state.WalletBottomSheetConfig +import com.tangem.feature.wallet.presentation.wallet.state.WalletNotification +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder +import com.tangem.feature.wallet.presentation.wallet.state.factory.WalletLoadedTokensListConverter.LoadedTokensListModel +import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickCallbacks +import kotlinx.collections.immutable.ImmutableList + +/** + * Main factory for creating [WalletStateHolder] + * + * @property currentStateProvider current ui state provider + * @property clickCallbacks screen click callbacks + */ +internal class WalletStateFactory( + private val currentStateProvider: Provider, + private val clickCallbacks: WalletClickCallbacks, +) { + + private val skeletonConverter = WalletSkeletonStateConverter(clickCallbacks = clickCallbacks) + private val loadedTokensListConverter = WalletLoadedTokensListConverter(currentStateProvider = currentStateProvider) + + fun getInitialState(): WalletStateHolder = WalletStateHolder.Loading(onBackClick = clickCallbacks::onBackClick) + + fun getSkeletonState(wallets: List): WalletStateHolder = skeletonConverter.convert(wallets) + + fun getStateByTokensList( + tokenListEither: Either, + isRefreshing: Boolean, + ): WalletStateHolder { + return loadedTokensListConverter.convert( + value = LoadedTokensListModel(tokenListEither = tokenListEither, isRefreshing = isRefreshing), + ) + } + + fun getStateByNotifications(notifications: ImmutableList): WalletStateHolder { + return currentStateProvider().copySealed(notifications = notifications) + } + + fun getStateAfterWalletChanging(index: Int): WalletStateHolder { + return currentStateProvider().let { stateHolder -> + stateHolder.copySealed(walletsListConfig = stateHolder.walletsListConfig.copy(selectedWalletIndex = index)) + } + } + + fun getStateAfterContentRefreshing(): WalletStateHolder { + return currentStateProvider().let { state -> + state.copySealed(pullToRefreshConfig = state.pullToRefreshConfig.copy(isRefreshing = true)) + } + } + + fun getStateWithOpenBottomSheet(content: WalletBottomSheetConfig.BottomSheetContentConfig): WalletStateHolder { + return currentStateProvider().let { state -> + state.copySealed( + bottomSheet = WalletBottomSheetConfig( + isShow = true, + onDismissRequest = { state.copySealed(bottomSheet = state.bottomSheet?.copy(isShow = false)) }, + content = content, + ), + ) + } + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletBottomSheet.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletBottomSheet.kt index 45b7a7a752..fb7a83e1bf 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletBottomSheet.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletBottomSheet.kt @@ -81,11 +81,10 @@ private fun BottomSheetContent(config: WalletBottomSheetConfig.BottomSheetConten Column(verticalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing10)) { val buttonModifier = Modifier.fillMaxWidth() + PrimaryButton(config = config.primaryButtonConfig, modifier = buttonModifier) - if (config.secondaryButtonConfig != null) { - SecondaryButton(config = config.secondaryButtonConfig, modifier = buttonModifier) - } + SecondaryButton(config = config.secondaryButtonConfig, modifier = buttonModifier) } } } @@ -97,13 +96,13 @@ private fun PrimaryButton( ) { if (config.iconResId == null) { PrimaryButton( - text = config.text, + text = config.text.resolveReference(), onClick = config.onClick, modifier = modifier, ) } else { PrimaryButtonIconStart( - text = config.text, + text = config.text.resolveReference(), iconResId = config.iconResId, onClick = config.onClick, modifier = modifier, @@ -118,13 +117,13 @@ private fun SecondaryButton( ) { if (config.iconResId == null) { SecondaryButton( - text = config.text, + text = config.text.resolveReference(), onClick = config.onClick, modifier = modifier, ) } else { SecondaryButtonIconStart( - text = config.text, + text = config.text.resolveReference(), iconResId = config.iconResId, onClick = config.onClick, modifier = modifier, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToWalletStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToWalletStateConverter.kt index 1db861142b..b1374bbead 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToWalletStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/utils/TokenListToWalletStateConverter.kt @@ -1,10 +1,13 @@ package com.tangem.feature.wallet.presentation.wallet.utils +import com.tangem.core.ui.components.transactions.TransactionState import com.tangem.domain.tokens.model.TokenList +import com.tangem.feature.wallet.presentation.common.state.TokenItemState +import com.tangem.feature.wallet.presentation.wallet.state.WalletContentItemState import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder.MultiCurrencyContent -import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder.SingleCurrencyContent import com.tangem.feature.wallet.presentation.wallet.state.WalletsListConfig +import com.tangem.feature.wallet.presentation.wallet.utils.TokenListToWalletStateConverter.TokensListModel import com.tangem.utils.converter.Converter import kotlinx.collections.immutable.toPersistentList @@ -13,47 +16,62 @@ internal class TokenListToWalletStateConverter( private val isWalletContentHidden: Boolean, private val fiatCurrencyCode: String, private val fiatCurrencySymbol: String, -) : Converter { +) : Converter { - override fun convert(value: TokenList): WalletStateHolder { - return when (currentState) { - is MultiCurrencyContent -> currentState.updateWithTokenList(value) - is SingleCurrencyContent -> currentState.updateWithTokenList(value) - is WalletStateHolder.Loading, - is WalletStateHolder.UnlockWalletContent, - -> currentState + private val tokenListToContentConverter = TokenListToContentItemsConverter( + isWalletContentHidden = isWalletContentHidden, + fiatCurrencyCode = fiatCurrencyCode, + fiatCurrencySymbol = fiatCurrencySymbol, + ) + + override fun convert(value: TokensListModel): WalletStateHolder { + val state = if (currentState is MultiCurrencyContent) { + currentState.updateWithTokenList(tokenList = value.tokenList) + } else { + currentState } + + return state.copySealed( + walletsListConfig = state.updateSelectedWallet(value.tokenList.totalFiatBalance), + pullToRefreshConfig = if (value.isRefreshing) { + state.pullToRefreshConfig.copy(isRefreshing = state.getRefreshingStatus()) + } else { + state.pullToRefreshConfig + }, + ) } private fun MultiCurrencyContent.updateWithTokenList(tokenList: TokenList): MultiCurrencyContent { - val converter = TokenListToContentItemsConverter(isWalletContentHidden, fiatCurrencyCode, fiatCurrencySymbol) - - return this.copy( - walletsListConfig = updateSelectedWallet(tokenList.totalFiatBalance), - contentItems = converter.convert(tokenList), - ) - } - - private fun SingleCurrencyContent.updateWithTokenList(tokenList: TokenList): SingleCurrencyContent { - return this.copy( - walletsListConfig = updateSelectedWallet(tokenList.totalFiatBalance), - ) + return this.copy(contentItems = tokenListToContentConverter.convert(value = tokenList)) } private fun WalletStateHolder.updateSelectedWallet(fiatBalance: TokenList.FiatBalance): WalletsListConfig { val selectedWalletIndex = walletsListConfig.selectedWalletIndex val selectedWalletCard = walletsListConfig.wallets[selectedWalletIndex] val converter = FiatBalanceToWalletCardConverter( - selectedWalletCard, - isWalletContentHidden, - fiatCurrencyCode, - fiatCurrencySymbol, + currentState = selectedWalletCard, + isWalletContentHidden = isWalletContentHidden, + fiatCurrencyCode = fiatCurrencyCode, + fiatCurrencySymbol = fiatCurrencySymbol, ) return walletsListConfig.copy( wallets = walletsListConfig.wallets .toPersistentList() - .set(selectedWalletIndex, converter.convert(fiatBalance)), + .set(index = selectedWalletIndex, element = converter.convert(fiatBalance)), ) } + + private fun WalletStateHolder.getRefreshingStatus(): Boolean { + return contentItems.any { state -> + val isMultiCurrencyItem = state as? WalletContentItemState.MultiCurrencyItem.Token + val isSingleCurrencyItem = state as? WalletContentItemState.SingleCurrencyItem.Transaction + + isMultiCurrencyItem?.state is TokenItemState.Loading || + isSingleCurrencyItem?.state is TransactionState.Loading || + state is WalletContentItemState.Loading + } + } + + data class TokensListModel(val tokenList: TokenList, val isRefreshing: Boolean) } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/JobHolder.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/JobHolder.kt new file mode 100644 index 0000000000..94c0e30ade --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/JobHolder.kt @@ -0,0 +1,21 @@ +package com.tangem.feature.wallet.presentation.wallet.viewmodels + +import kotlinx.coroutines.Job + +/** + * Job holder. It is automatically finished old job if new one is started + * +[REDACTED_AUTHOR] + */ +internal class JobHolder { + + private var job: Job? = null + + /** Update current job */ + fun update(job: Job) { + this.job?.cancel() + this.job = job + } +} + +internal fun Job.saveIn(jobHolder: JobHolder) = jobHolder.update(job = this) \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletClickCallbacks.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletClickCallbacks.kt new file mode 100644 index 0000000000..f47c10bc5c --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletClickCallbacks.kt @@ -0,0 +1,28 @@ +package com.tangem.feature.wallet.presentation.wallet.viewmodels + +internal interface WalletClickCallbacks { + + fun onBackClick() + + fun onScanCardClick() + + fun onDetailsClick() + + fun onBackupCardClick() + + fun onCriticalWarningAlreadySignedHashesClick() + + fun onCloseWarningAlreadySignedHashesClick() + + fun onLikeTangemAppClick() + + fun onRateTheAppClick() + + fun onShareClick() + + fun onWalletChange(index: Int) + + fun onRefreshSwipe() + + fun onOrganizeTokensClick() +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletNotificationsListFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletNotificationsListFactory.kt index f2de50be61..d0df6d7f3a 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletNotificationsListFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletNotificationsListFactory.kt @@ -1,27 +1,38 @@ package com.tangem.feature.wallet.presentation.wallet.viewmodels +import com.tangem.common.Provider import com.tangem.domain.common.CardTypesResolver +import com.tangem.domain.tokens.model.NetworkGroup +import com.tangem.domain.tokens.model.TokenList +import com.tangem.domain.tokens.model.TokenStatus +import com.tangem.feature.wallet.presentation.common.state.TokenItemState +import com.tangem.feature.wallet.presentation.wallet.state.WalletContentItemState import com.tangem.feature.wallet.presentation.wallet.state.WalletNotification +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow /** + * Wallet notifications list factory + * + * @property wasCardScannedCallback callback that check if card was scanned + * @property isUserAlreadyRateAppCallback callback that check if card is user already rate app + * @property isDemoCardCallback callback that check if card is demo + * @property clickCallbacks screen click callbacks + * [REDACTED_AUTHOR] */ -@Suppress("LongParameterList") internal class WalletNotificationsListFactory( + private val currentStateProvider: Provider, private val wasCardScannedCallback: suspend (String) -> Boolean, private val isUserAlreadyRateAppCallback: suspend () -> Boolean, - private val onWalletAlreadySignedHashesClick: () -> Unit, - private val onMultiWalletAlreadySignedHashesClick: () -> Unit, - private val onBackupCardClick: () -> Unit, - private val hasUnreachableNetworksCallback: () -> Boolean, - private val onLikeTangemAppClick: () -> Unit, + private val isDemoCardCallback: (String) -> Boolean, + private val clickCallbacks: WalletClickCallbacks, ) { - fun create(cardTypesResolver: CardTypesResolver): Flow> { + fun create(cardTypesResolver: CardTypesResolver, tokenList: TokenList): Flow> { // TODO: [REDACTED_JIRA] order return flow { emit( @@ -33,31 +44,31 @@ internal class WalletNotificationsListFactory( addRemainingSignaturesLeftNotifications(cardTypesResolver) + val isDemo = isDemoCardCallback(cardTypesResolver.getCardId()) if (!cardTypesResolver.isReleaseFirmwareType()) { add(element = WalletNotification.DevCard) } else { - addReleaseSpecialNotifications(cardTypesResolver) + addReleaseSpecialNotifications(cardTypesResolver = cardTypesResolver, isDemo = isDemo) } - // TODO: [REDACTED_JIRA] - DemoModeDatasource + if (isDemo) { + add(element = WalletNotification.DemoCard) + } - if (hasUnreachableNetworksCallback()) { + if (hasUnreachableNetworks()) { add(element = WalletNotification.UnreachableNetworks) } if (!cardTypesResolver.isBackupForbidden() && !cardTypesResolver.hasBackup()) { - add(element = WalletNotification.BackupCard(onClick = onBackupCardClick)) + add(element = WalletNotification.BackupCard(onClick = clickCallbacks::onBackupCardClick)) } - /* TODO: [REDACTED_JIRA] - * List - * .filter { store -> - * store.walletsData.any { it.status is WalletDataModel.MissedDerivation } - * } - */ + if (tokenList.hasMissedDerivations()) { + add(element = WalletNotification.ScanCard(onClick = clickCallbacks::onScanCardClick)) + } if (isUserAlreadyRateAppCallback()) { - add(element = WalletNotification.LikeTangemApp(onClick = onLikeTangemAppClick)) + add(element = WalletNotification.LikeTangemApp(onClick = clickCallbacks::onLikeTangemAppClick)) } }.toImmutableList(), ) @@ -75,19 +86,21 @@ internal class WalletNotificationsListFactory( private suspend fun MutableList.addReleaseSpecialNotifications( cardTypesResolver: CardTypesResolver, + isDemo: Boolean, ) { - // TODO: [REDACTED_JIRA] !isDemo - if (!wasCardScannedCallback(cardTypesResolver.getCardId()) && cardTypesResolver.isMultiwalletAllowed()) { + if (!wasCardScannedCallback(cardTypesResolver.getCardId()) && cardTypesResolver.isMultiwalletAllowed() && + !isDemo + ) { if (cardTypesResolver.isBackupForbidden() && cardTypesResolver.hasWalletSignedHashes()) { add( - element = WalletNotification.SignedTransactionsInThePast( - onClick = onMultiWalletAlreadySignedHashesClick, + element = WalletNotification.CriticalWarningAlreadySignedHashes( + onClick = clickCallbacks::onCriticalWarningAlreadySignedHashesClick, ), ) } else if (cardTypesResolver.hasWalletSignedHashes()) { add( - element = WalletNotification.AlreadyToppedUpAndSignedHashes( - onClick = onWalletAlreadySignedHashesClick, + element = WalletNotification.WarningAlreadySignedHashes( + onClick = clickCallbacks::onCloseWarningAlreadySignedHashesClick, ), ) } @@ -98,6 +111,26 @@ internal class WalletNotificationsListFactory( } } + private fun hasUnreachableNetworks(): Boolean { + val isUnreachableState = { item: WalletContentItemState.MultiCurrencyItem -> + (item as? WalletContentItemState.MultiCurrencyItem.Token)?.state is TokenItemState.Unreachable + } + + return currentStateProvider().let { state -> + state is WalletStateHolder.MultiCurrencyContent && state.contentItems.any(isUnreachableState) + } + } + + private fun TokenList.hasMissedDerivations(): Boolean { + val statuses = when (this) { + is TokenList.GroupedByNetwork -> groups.flatMap(NetworkGroup::tokens).map(TokenStatus::value) + is TokenList.Ungrouped -> tokens.map(TokenStatus::value) + TokenList.NotInitialized -> emptyList() + } + + return statuses.any { it is TokenStatus.MissedDerivation } + } + private companion object { const val MAX_REMAINING_SIGNATURES_COUNT = 10 } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index a369895e5b..13b9716f84 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -4,30 +4,25 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.* -import arrow.core.Either import com.tangem.common.Provider import com.tangem.common.doOnFailure import com.tangem.common.doOnSuccess -import com.tangem.core.ui.components.transactions.TransactionState -import com.tangem.domain.card.GetAccessCodeSavingStatusUseCase -import com.tangem.domain.card.GetBiometricsStatusUseCase -import com.tangem.domain.card.ScanCardProcessor -import com.tangem.domain.card.SetAccessCodeRequestPolicyUseCase +import com.tangem.domain.card.* +import com.tangem.domain.common.CardTypesResolver +import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.domain.demo.IsDemoCardUseCase +import com.tangem.domain.settings.IsUserAlreadyRateAppUseCase import com.tangem.domain.tokens.GetTokenListUseCase -import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.userwallets.UserWalletBuilder +import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.GetWalletsUseCase import com.tangem.domain.wallets.usecase.SaveWalletUseCase -import com.tangem.feature.wallet.presentation.common.state.TokenItemState import com.tangem.feature.wallet.presentation.router.InnerWalletRouter import com.tangem.feature.wallet.presentation.wallet.state.* -import com.tangem.feature.wallet.presentation.wallet.state.builder.WalletStateFactory -import com.tangem.feature.wallet.presentation.wallet.utils.TokenListErrorToWalletStateConverter -import com.tangem.feature.wallet.presentation.wallet.utils.TokenListToWalletStateConverter +import com.tangem.feature.wallet.presentation.wallet.state.factory.WalletStateFactory import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import javax.inject.Inject @@ -41,74 +36,106 @@ import kotlin.properties.Delegates @Suppress("LongParameterList") @HiltViewModel internal class WalletViewModel @Inject constructor( + private val getWalletsUseCase: GetWalletsUseCase, private val saveWalletUseCase: SaveWalletUseCase, private val getBiometricsStatusUseCase: GetBiometricsStatusUseCase, private val setAccessCodeRequestPolicyUseCase: SetAccessCodeRequestPolicyUseCase, private val getAccessCodeSavingStatusUseCase: GetAccessCodeSavingStatusUseCase, private val getTokenListUseCase: GetTokenListUseCase, - private val getWalletsUseCase: GetWalletsUseCase, + private val getCardWasScannedUseCase: GetCardWasScannedUseCase, + private val isUserAlreadyRateAppUseCase: IsUserAlreadyRateAppUseCase, + private val isDemoCardUseCase: IsDemoCardUseCase, private val scanCardProcessor: ScanCardProcessor, private val dispatchers: CoroutineDispatcherProvider, -) : ViewModel(), DefaultLifecycleObserver { +) : ViewModel(), DefaultLifecycleObserver, WalletClickCallbacks { /** Feature router */ var router: InnerWalletRouter by Delegates.notNull() + private val notificationsListFactory = WalletNotificationsListFactory( + currentStateProvider = Provider { uiState }, + wasCardScannedCallback = getCardWasScannedUseCase::invoke, + isUserAlreadyRateAppCallback = isUserAlreadyRateAppUseCase::invoke, + isDemoCardCallback = isDemoCardUseCase::invoke, + clickCallbacks = this, + ) + private val stateFactory = WalletStateFactory( - routerProvider = Provider { router }, - onScanCardClick = ::onScanCardClick, - onWalletChange = ::changeWallet, - onRefreshSwipe = ::refreshContent, + currentStateProvider = Provider { uiState }, + clickCallbacks = this, ) /** Screen state */ - var uiState by mutableStateOf(stateFactory.getInitialState()) + var uiState: WalletStateHolder by mutableStateOf(stateFactory.getInitialState()) private set + private var cardTypeResolverMap: Map by Delegates.notNull() + + private val getTokensJobHolder = JobHolder() + private val getNotificationJobHolder = JobHolder() + override fun onCreate(owner: LifecycleOwner) { getWalletsUseCase() - .distinctUntilChanged() .flowWithLifecycle(owner.lifecycle) + .distinctUntilChanged() .onEach { wallets -> if (wallets.isEmpty()) return@onEach - uiState = stateFactory.getContentState(wallets = wallets) - updateContentItems() + cardTypeResolverMap = wallets.associate { wallet -> + // TODO remove after adding release logic for GetTokenListUseCase + if (wallet.scanResponse.cardTypesResolver.isMultiwalletAllowed()) { + UserWalletId("123") + } else { + UserWalletId("321") + } to wallet.scanResponse.cardTypesResolver + } + + uiState = stateFactory.getSkeletonState(wallets = wallets) + + updateContentItems(index = 0) } .flowOn(dispatchers.io) .launchIn(viewModelScope) } - @OptIn(ExperimentalCoroutinesApi::class) - private fun updateContentItems() { - getTokenListUseCase( - userWalletId = uiState.walletsListConfig.wallets.get( - index = uiState.walletsListConfig.selectedWalletIndex, - ).id, + private fun updateContentItems(index: Int, isRefreshing: Boolean = false) { + // TODO: use GetTransactionsUseCase + + getTokenListUseCase(userWalletId = uiState.walletsListConfig.wallets[index].id) + .distinctUntilChanged() + .onEach { tokenListEither -> + uiState = stateFactory.getStateByTokensList( + tokenListEither = tokenListEither, + isRefreshing = isRefreshing, + ) + + tokenListEither.onRight { updateNotifications(index = index, tokenList = it) } + } + .flowOn(dispatchers.io) + .launchIn(viewModelScope) + .saveIn(jobHolder = getTokensJobHolder) + } + + private fun updateNotifications(index: Int, tokenList: TokenList) { + notificationsListFactory.create( + cardTypesResolver = requireNotNull( + value = cardTypeResolverMap[uiState.walletsListConfig.wallets[index].id], + lazyMessage = { + "CardTypeResolverMap doesn't contain this id = ${uiState.walletsListConfig.wallets[index].id}" + }, + ), + tokenList = tokenList, ) .distinctUntilChanged() - .mapLatest(::updateStateWithTokenListOrError) - .onEach { - uiState = it.copySealed( - pullToRefreshConfig = uiState.pullToRefreshConfig.copy(isRefreshing = getRefreshingStatus()), - ) - } + .onEach { uiState = stateFactory.getStateByNotifications(notifications = it) } .flowOn(dispatchers.io) .launchIn(viewModelScope) + .saveIn(jobHolder = getNotificationJobHolder) } - private fun getRefreshingStatus(): Boolean { - return uiState.contentItems.any { state -> - val isMultiCurrencyItem = state as? WalletContentItemState.MultiCurrencyItem.Token - val isSingleCurrencyItem = state as? WalletContentItemState.SingleCurrencyItem.Transaction + override fun onBackClick() = router.popBackStack() - isMultiCurrencyItem?.state is TokenItemState.Loading || - isSingleCurrencyItem?.state is TransactionState.Loading || - state is WalletContentItemState.Loading - } - } - - private fun onScanCardClick() { + override fun onScanCardClick() { val prevRequestPolicyStatus = getBiometricsStatusUseCase() // Update access the code policy according access code saving status @@ -138,47 +165,52 @@ internal class WalletViewModel @Inject constructor( } } - private fun changeWallet(index: Int) { + override fun onDetailsClick() = router.openDetailsScreen() + + override fun onBackupCardClick() = router.openOnboardingScreen() + + override fun onCriticalWarningAlreadySignedHashesClick() { + uiState = stateFactory.getStateWithOpenBottomSheet( + content = WalletBottomSheetConfig.BottomSheetContentConfig.CriticalWarningAlreadySignedHashes( + onOkClick = {}, + onCancelClick = {}, + ), + ) + } + + override fun onCloseWarningAlreadySignedHashesClick() { + // TODO: [REDACTED_JIRA] + } + + override fun onLikeTangemAppClick() { + uiState = stateFactory.getStateWithOpenBottomSheet( + content = WalletBottomSheetConfig.BottomSheetContentConfig.LikeTangemApp( + onRateTheAppClick = ::onRateTheAppClick, + onShareClick = ::onShareClick, + ), + ) + } + + override fun onRateTheAppClick() { + // TODO: [REDACTED_JIRA] + } + + override fun onShareClick() { + // TODO: [REDACTED_JIRA] + } + + override fun onWalletChange(index: Int) { if (uiState.walletsListConfig.selectedWalletIndex == index) return - uiState = when (val state = uiState) { - is WalletStateHolder.MultiCurrencyContent -> state.copy( - walletsListConfig = uiState.walletsListConfig.copy(selectedWalletIndex = index), - ) - is WalletStateHolder.SingleCurrencyContent -> state.copy( - walletsListConfig = uiState.walletsListConfig.copy(selectedWalletIndex = index), - ) - is WalletStateHolder.UnlockWalletContent -> state.copy( - walletsListConfig = uiState.walletsListConfig.copy(selectedWalletIndex = index), - ) - is WalletStateHolder.Loading -> state - } + uiState = stateFactory.getStateAfterWalletChanging(index = index) - updateContentItems() + updateContentItems(index = index) } - private fun refreshContent() { - uiState = uiState.copySealed(pullToRefreshConfig = uiState.pullToRefreshConfig.copy(isRefreshing = true)) - updateContentItems() + override fun onRefreshSwipe() { + uiState = stateFactory.getStateAfterContentRefreshing() + updateContentItems(index = uiState.walletsListConfig.selectedWalletIndex, isRefreshing = true) } - private fun updateStateWithTokenListOrError(tokenList: Either): WalletStateHolder { - val updateStateWithError = { error: TokenListError -> - val converter = TokenListErrorToWalletStateConverter(uiState) - - converter.convert(error) - } - val updateState = { list: TokenList -> - val converter = TokenListToWalletStateConverter( - uiState, - isWalletContentHidden = false, // TODO: [REDACTED_JIRA] - fiatCurrencyCode = "USD", // TODO: [REDACTED_JIRA] - fiatCurrencySymbol = "$", // TODO: [REDACTED_JIRA] - ) - - converter.convert(list) - } - - return tokenList.fold(ifLeft = updateStateWithError, ifRight = updateState) - } + override fun onOrganizeTokensClick() = router.openOrganizeTokensScreen() } \ No newline at end of file