diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt index f4769cd5bf..4e6ddfac2c 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt @@ -201,6 +201,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor( override fun onAccountExpandClick(account: Account) { analyticsEventHandler.send(MainScreenAnalyticsEvent.AccountShowTokens()) accountDependencies.expandedAccountsHolder.expandAccount(account.accountId) + walletEventSender.send(WalletEvent.CollapseBalance) } override fun onAccountCollapseClick(account: Account) { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewData.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewData.kt index f3019be3a6..3f563dbb3d 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewData.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewData.kt @@ -2,243 +2,97 @@ package com.tangem.feature.wallet.presentation.common.preview import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig import com.tangem.core.ui.components.currency.icon.CurrencyIconState -import com.tangem.core.ui.components.marketprice.PriceChangeType -import com.tangem.core.ui.components.notifications.NotificationConfig.ButtonsState -import com.tangem.core.ui.components.token.AccountItemPreviewData -import com.tangem.core.ui.components.token.state.TokenItemState -import com.tangem.core.ui.components.tokenlist.state.PortfolioTokensListItemUM -import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM +import com.tangem.core.ui.ds.button.TangemButtonType +import com.tangem.core.ui.ds.button.TangemButtonUM +import com.tangem.core.ui.ds.image.TangemIconUM +import com.tangem.core.ui.ds.row.token.TangemTokenRowUM import com.tangem.core.ui.event.consumedEvent -import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference -import com.tangem.domain.models.wallet.UserWalletId -import com.tangem.feature.wallet.child.wallet.model.WalletActivationBannerType import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.common.WalletPreviewDataLegacy.topBarConfig +import com.tangem.feature.wallet.presentation.preview.WalletBalancePreview +import com.tangem.feature.wallet.presentation.preview.WalletPreviewData import com.tangem.feature.wallet.presentation.wallet.state.model.* -import com.tangem.utils.StringsSigns.DASH_SIGN import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toPersistentList internal object WalletScreenPreviewData { - private val tokenItemState = TokenItemState.Content( + + private val tokenRowDefault = TangemTokenRowUM.Content( id = "1", - iconState = CurrencyIconState.Locked, - titleState = TokenItemState.TitleState.Content(text = stringReference(value = "Bitcoin")), - fiatAmountState = TokenItemState.FiatAmountState.Content(text = "12 368,14 \$"), - subtitle2State = TokenItemState.Subtitle2State.TextContent(text = "0,35853044 BTC"), - subtitleState = TokenItemState.SubtitleState.CryptoPriceContent( - price = "34 496,75 \$", - priceChangePercent = "0,43 %", - type = PriceChangeType.DOWN, + headIconUM = TangemIconUM.Currency(CurrencyIconState.Loading), + titleUM = TangemTokenRowUM.TitleUM.Content( + text = stringReference("Bitcoin"), ), + subtitleUM = TangemTokenRowUM.SubtitleUM.Content( + text = stringReference("Bitcoin"), + ), + topEndContentUM = TangemTokenRowUM.EndContentUM.Content( + text = stringReference("1 234,56 \$"), + ), + bottomEndContentUM = TangemTokenRowUM.EndContentUM.Content( + text = stringReference("0,12345678 BTC"), + ), + promoBannerUM = TangemTokenRowUM.PromoBannerUM.Empty, + tailUM = TangemTokenRowUM.TailUM.Empty, onItemClick = {}, onItemLongClick = {}, ) - private val textContentTokensState = WalletTokensListState.ContentState.Content( - items = persistentListOf( - TokensListItemUM.GroupTitle(id = 1, text = stringReference("Network Bitcoin")), - TokensListItemUM.Token(state = tokenItemState), - TokensListItemUM.GroupTitle(id = 2, text = stringReference("Network Ethereum")), - TokensListItemUM.Token( - state = tokenItemState.copy( - id = "2", - titleState = TokenItemState.TitleState.Content(text = stringReference("Ethereum")), - fiatAmountState = TokenItemState.FiatAmountState.Content(text = "3 340,79 \$"), - subtitle2State = TokenItemState.Subtitle2State.TextContent(text = "1,856660295 ETH"), - subtitleState = TokenItemState.SubtitleState.CryptoPriceContent( - price = "1 799,41 \$", - priceChangePercent = "5,16 %", - type = PriceChangeType.UP, - ), - ), - ), - TokensListItemUM.Token( - state = TokenItemState.Unreachable( - id = "3", - iconState = CurrencyIconState.Locked, - titleState = TokenItemState.TitleState.Content(text = stringReference(value = "Polygon")), - onItemClick = {}, - onItemLongClick = {}, - ), - ), - TokensListItemUM.Token( - state = tokenItemState.copy( - id = "4", - titleState = TokenItemState.TitleState.Content(text = stringReference(value = "Shiba Inu")), - fiatAmountState = TokenItemState.FiatAmountState.Content(text = "48,64 \$"), - subtitle2State = TokenItemState.Subtitle2State.TextContent(text = "6 200 220,00 SHIB"), - subtitleState = TokenItemState.SubtitleState.CryptoPriceContent( - price = "0.01 \$", - priceChangePercent = "1,34 %", - type = PriceChangeType.DOWN, - ), - ), - ), + private val tokenListDefault = WalletTokensListUM.Content( + tokenList = persistentListOf( + TokensListItemUM2.Token(tokenRowDefault.copy(id = "0")), + TokensListItemUM2.Token(tokenRowDefault.copy(id = "1")), + TokensListItemUM2.Token(tokenRowDefault.copy(id = "2")), ), - organizeTokensButtonConfig = WalletTokensListState.OrganizeTokensButtonConfig( - isEnabled = true, + organizeButtonUM = TangemButtonUM( + text = resourceReference(R.string.organize_tokens_title), + type = TangemButtonType.Secondary, onClick = {}, ), ) - private val portfolioContentState = WalletTokensListState.ContentState.PortfolioContent( - items = persistentListOf( - TokensListItemUM.Portfolio( - tokens = textContentTokensState.items.filterIsInstance().toPersistentList(), - isExpanded = false, - isCollapsable = true, - tokenItemUM = AccountItemPreviewData.accountItem - .copy(iconState = AccountItemPreviewData.accountLetterIcon), - ), - TokensListItemUM.Portfolio( - tokens = textContentTokensState.items.filterIsInstance().toPersistentList(), - isExpanded = true, - isCollapsable = true, - tokenItemUM = AccountItemPreviewData.accountItem, - ), - ), - organizeTokensButtonConfig = WalletTokensListState.OrganizeTokensButtonConfig( - isEnabled = true, - onClick = {}, - ), + private val walletLocked = WalletUM.Locked( + walletsBalanceUM = WalletBalancePreview.content, + buttons = WalletPreviewData.actionButtons, + type = WalletType.Cold, + notifications = persistentListOf(), ) - private val emptyPortfolioContentState = WalletTokensListState.ContentState.PortfolioContent( - items = persistentListOf( - TokensListItemUM.Portfolio( - tokens = textContentTokensState.items.filterIsInstance().toPersistentList(), - isExpanded = false, - isCollapsable = true, - tokenItemUM = AccountItemPreviewData.accountItem - .copy(iconState = AccountItemPreviewData.accountLetterIcon), - ), - TokensListItemUM.Portfolio( - tokens = persistentListOf(), - isExpanded = true, - isCollapsable = true, - tokenItemUM = AccountItemPreviewData.accountItem, - ), + private val walletDefault = WalletUM.Content( + walletsBalanceUM = WalletBalancePreview.content, + buttons = WalletPreviewData.actionButtons, + type = WalletType.Cold, + pullToRefreshConfig = PullToRefreshConfig( + isRefreshing = false, + onRefresh = {}, ), - organizeTokensButtonConfig = WalletTokensListState.OrganizeTokensButtonConfig( - isEnabled = true, - onClick = {}, + notifications = persistentListOf(), + notificationsCarousel = persistentListOf(), + tokensListUM = tokenListDefault, + nftState = WalletNFTItemUM.Content( + previews = persistentListOf(), + collectionsCount = 0, + allAssetsCount = 0, + noCollectionAssetsCount = 0, + isFlickering = false, + onItemClick = {}, ), + tangemPayState = TangemPayState.Loading, ) - private val noteLockedCard by lazy { - WalletCardState.LockedContent( - id = UserWalletId(stringValue = "1"), - title = "Note", - additionalInfo = WalletAdditionalInfo( - hideable = false, - content = TextReference.Str("Locked"), - ), - imageResId = R.drawable.ill_note_btc_120_106, - dropDownItems = persistentListOf(), - ) - } - private val miltiUnreachableCard by lazy { - WalletCardState.Content( - id = UserWalletId(stringValue = "2"), - title = "Wallet 1", - additionalInfo = WalletAdditionalInfo( - hideable = false, - content = TextReference.Str("Seed phrase"), - ), - imageResId = R.drawable.ill_wallet2_cards3_120_106, - cardCount = 3, - balance = DASH_SIGN, - dropDownItems = persistentListOf(), - isZeroBalance = false, - isBalanceFlickering = false, - ) - } - private val multiWalletState by lazy { - WalletState.MultiCurrency.Content( - pullToRefreshConfig = PullToRefreshConfig( - isRefreshing = false, - onRefresh = {}, - ), - walletCardState = miltiUnreachableCard, - buttons = persistentListOf(buyButton), - warnings = persistentListOf( - WalletNotification.Warning.SomeNetworksUnreachable, - WalletNotification.FinishWalletActivation( - type = WalletActivationBannerType.Attention, - buttonsState = ButtonsState.SecondaryButtonConfig( - text = resourceReference(R.string.hw_activation_need_finish), - onClick = { }, - ), - isBackupExists = false, - ), - ), - bottomSheetConfig = null, - tokensListState = textContentTokensState, - nftState = WalletNFTItemUM.Content( - previews = persistentListOf(WalletNFTItemUM.Content.CollectionPreview.Image("img1")), - collectionsCount = 1, - allAssetsCount = 3, - noCollectionAssetsCount = 0, - isFlickering = false, - onItemClick = { }, - ), - tangemPayState = TangemPayState.Empty, - type = WalletType.Cold, - ) - } - - private val buyButton = WalletManageButton.Buy(enabled = false, dimContent = true, onClick = {}) - private val sendButton = WalletManageButton.Send(enabled = false, dimContent = true, onClick = {}) - private val receiveButton = WalletManageButton.Receive( - enabled = false, - dimContent = true, - onClick = {}, - onLongClick = null, - ) - - private val singleWalletLockedState = WalletState.SingleCurrency.Locked( - walletCardState = noteLockedCard, - buttons = persistentListOf( - buyButton, - sendButton, - receiveButton, - ), - bottomSheetConfig = null, - onUnlockNotificationClick = {}, - onExploreClick = {}, - ) - - internal val walletScreenState = WalletScreenState( + internal val defaultState = WalletScreenState( topBarConfig = topBarConfig, selectedWalletIndex = 0, - wallets = persistentListOf( - singleWalletLockedState, - multiWalletState, + wallets = persistentListOf(), + wallets2 = persistentListOf( + walletLocked, + walletDefault, ), - wallets2 = persistentListOf(), onWalletChange = { _, _ -> }, event = consumedEvent(), isHidingMode = false, showMarketsOnboarding = false, onDismissMarketsTooltip = {}, ) - - internal val accountScreenState = - walletScreenState.copy( - wallets = persistentListOf( - singleWalletLockedState, - multiWalletState.copy(tokensListState = portfolioContentState), - ), - ) - - internal val accountScreenWithEmptyTokensState = - walletScreenState.copy( - wallets = persistentListOf( - singleWalletLockedState, - multiWalletState.copy(tokensListState = emptyPortfolioContentState), - ), - ) } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewDataLegacy.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewDataLegacy.kt new file mode 100644 index 0000000000..ab76474be4 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewDataLegacy.kt @@ -0,0 +1,245 @@ +package com.tangem.feature.wallet.presentation.common.preview + +import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig +import com.tangem.core.ui.components.currency.icon.CurrencyIconState +import com.tangem.core.ui.components.marketprice.PriceChangeType +import com.tangem.core.ui.components.notifications.NotificationConfig.ButtonsState +import com.tangem.core.ui.components.token.AccountItemPreviewData +import com.tangem.core.ui.components.token.state.TokenItemState +import com.tangem.core.ui.components.tokenlist.state.PortfolioTokensListItemUM +import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM +import com.tangem.core.ui.event.consumedEvent +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.feature.wallet.child.wallet.model.WalletActivationBannerType +import com.tangem.feature.wallet.impl.R +import com.tangem.feature.wallet.presentation.common.WalletPreviewDataLegacy.topBarConfig +import com.tangem.feature.wallet.presentation.wallet.state.model.* +import com.tangem.utils.StringsSigns.DASH_SIGN +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toPersistentList + +internal object WalletScreenPreviewDataLegacy { + + private val buyButton = WalletManageButton.Buy(enabled = false, dimContent = true, onClick = {}) + private val sendButton = WalletManageButton.Send(enabled = false, dimContent = true, onClick = {}) + private val receiveButton = WalletManageButton.Receive( + enabled = false, + dimContent = true, + onClick = {}, + onLongClick = null, + ) + + private val tokenItemState = TokenItemState.Content( + id = "1", + iconState = CurrencyIconState.Locked, + titleState = TokenItemState.TitleState.Content(text = stringReference(value = "Bitcoin")), + fiatAmountState = TokenItemState.FiatAmountState.Content(text = "12 368,14 \$"), + subtitle2State = TokenItemState.Subtitle2State.TextContent(text = "0,35853044 BTC"), + subtitleState = TokenItemState.SubtitleState.CryptoPriceContent( + price = "34 496,75 \$", + priceChangePercent = "0,43 %", + type = PriceChangeType.DOWN, + ), + onItemClick = {}, + onItemLongClick = {}, + ) + + private val textContentTokensState = WalletTokensListState.ContentState.Content( + items = persistentListOf( + TokensListItemUM.GroupTitle(id = 1, text = stringReference("Network Bitcoin")), + TokensListItemUM.Token(state = tokenItemState), + TokensListItemUM.GroupTitle(id = 2, text = stringReference("Network Ethereum")), + TokensListItemUM.Token( + state = tokenItemState.copy( + id = "2", + titleState = TokenItemState.TitleState.Content(text = stringReference("Ethereum")), + fiatAmountState = TokenItemState.FiatAmountState.Content(text = "3 340,79 \$"), + subtitle2State = TokenItemState.Subtitle2State.TextContent(text = "1,856660295 ETH"), + subtitleState = TokenItemState.SubtitleState.CryptoPriceContent( + price = "1 799,41 \$", + priceChangePercent = "5,16 %", + type = PriceChangeType.UP, + ), + ), + ), + TokensListItemUM.Token( + state = TokenItemState.Unreachable( + id = "3", + iconState = CurrencyIconState.Locked, + titleState = TokenItemState.TitleState.Content(text = stringReference(value = "Polygon")), + onItemClick = {}, + onItemLongClick = {}, + ), + ), + TokensListItemUM.Token( + state = tokenItemState.copy( + id = "4", + titleState = TokenItemState.TitleState.Content(text = stringReference(value = "Shiba Inu")), + fiatAmountState = TokenItemState.FiatAmountState.Content(text = "48,64 \$"), + subtitle2State = TokenItemState.Subtitle2State.TextContent(text = "6 200 220,00 SHIB"), + subtitleState = TokenItemState.SubtitleState.CryptoPriceContent( + price = "0.01 \$", + priceChangePercent = "1,34 %", + type = PriceChangeType.DOWN, + ), + ), + ), + ), + organizeTokensButtonConfig = WalletTokensListState.OrganizeTokensButtonConfig( + isEnabled = true, + onClick = {}, + ), + ) + + private val portfolioContentState = WalletTokensListState.ContentState.PortfolioContent( + items = persistentListOf( + TokensListItemUM.Portfolio( + tokens = textContentTokensState.items.filterIsInstance().toPersistentList(), + isExpanded = false, + isCollapsable = true, + tokenItemUM = AccountItemPreviewData.accountItem + .copy(iconState = AccountItemPreviewData.accountLetterIcon), + ), + TokensListItemUM.Portfolio( + tokens = textContentTokensState.items.filterIsInstance().toPersistentList(), + isExpanded = true, + isCollapsable = true, + tokenItemUM = AccountItemPreviewData.accountItem, + ), + ), + organizeTokensButtonConfig = WalletTokensListState.OrganizeTokensButtonConfig( + isEnabled = true, + onClick = {}, + ), + ) + + private val emptyPortfolioContentState = WalletTokensListState.ContentState.PortfolioContent( + items = persistentListOf( + TokensListItemUM.Portfolio( + tokens = textContentTokensState.items.filterIsInstance().toPersistentList(), + isExpanded = false, + isCollapsable = true, + tokenItemUM = AccountItemPreviewData.accountItem + .copy(iconState = AccountItemPreviewData.accountLetterIcon), + ), + TokensListItemUM.Portfolio( + tokens = persistentListOf(), + isExpanded = true, + isCollapsable = true, + tokenItemUM = AccountItemPreviewData.accountItem, + ), + ), + organizeTokensButtonConfig = WalletTokensListState.OrganizeTokensButtonConfig( + isEnabled = true, + onClick = {}, + ), + ) + + private val noteLockedCard by lazy { + WalletCardState.LockedContent( + id = UserWalletId(stringValue = "1"), + title = "Note", + additionalInfo = WalletAdditionalInfo( + hideable = false, + content = TextReference.Str("Locked"), + ), + imageResId = R.drawable.ill_note_btc_120_106, + dropDownItems = persistentListOf(), + ) + } + private val miltiUnreachableCard by lazy { + WalletCardState.Content( + id = UserWalletId(stringValue = "2"), + title = "Wallet 1", + additionalInfo = WalletAdditionalInfo( + hideable = false, + content = TextReference.Str("Seed phrase"), + ), + imageResId = R.drawable.ill_wallet2_cards3_120_106, + cardCount = 3, + balance = DASH_SIGN, + dropDownItems = persistentListOf(), + isZeroBalance = false, + isBalanceFlickering = false, + ) + } + private val multiWalletState by lazy { + WalletState.MultiCurrency.Content( + pullToRefreshConfig = PullToRefreshConfig( + isRefreshing = false, + onRefresh = {}, + ), + walletCardState = miltiUnreachableCard, + buttons = persistentListOf(buyButton), + warnings = persistentListOf( + WalletNotification.Warning.SomeNetworksUnreachable, + WalletNotification.FinishWalletActivation( + type = WalletActivationBannerType.Attention, + buttonsState = ButtonsState.SecondaryButtonConfig( + text = resourceReference(R.string.hw_activation_need_finish), + onClick = { }, + ), + isBackupExists = false, + ), + ), + bottomSheetConfig = null, + tokensListState = textContentTokensState, + nftState = WalletNFTItemUM.Content( + previews = persistentListOf(WalletNFTItemUM.Content.CollectionPreview.Image("img1")), + collectionsCount = 1, + allAssetsCount = 3, + noCollectionAssetsCount = 0, + isFlickering = false, + onItemClick = { }, + ), + tangemPayState = TangemPayState.Empty, + type = WalletType.Cold, + ) + } + + private val singleWalletLockedState = WalletState.SingleCurrency.Locked( + walletCardState = noteLockedCard, + buttons = persistentListOf( + buyButton, + sendButton, + receiveButton, + ), + bottomSheetConfig = null, + onUnlockNotificationClick = {}, + onExploreClick = {}, + ) + + internal val walletScreenState = WalletScreenState( + topBarConfig = topBarConfig, + selectedWalletIndex = 0, + wallets = persistentListOf( + singleWalletLockedState, + multiWalletState, + ), + wallets2 = persistentListOf(), + onWalletChange = { _, _ -> }, + event = consumedEvent(), + isHidingMode = false, + showMarketsOnboarding = false, + onDismissMarketsTooltip = {}, + ) + + internal val accountScreenState = + walletScreenState.copy( + wallets = persistentListOf( + singleWalletLockedState, + multiWalletState.copy(tokensListState = portfolioContentState), + ), + ) + + internal val accountScreenWithEmptyTokensState = + walletScreenState.copy( + wallets = persistentListOf( + singleWalletLockedState, + multiWalletState.copy(tokensListState = emptyPortfolioContentState), + ), + ) +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/preview/WalletBalancePreview.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/preview/WalletBalancePreview.kt index 0d83d630b8..f71de661de 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/preview/WalletBalancePreview.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/preview/WalletBalancePreview.kt @@ -1,5 +1,6 @@ package com.tangem.feature.wallet.presentation.preview +import androidx.compose.ui.text.SpanStyle import com.tangem.core.ui.extensions.combinedReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.styledStringReference @@ -12,6 +13,14 @@ internal object WalletBalancePreview { val content: WalletBalanceUM.Content = WalletBalanceUM.Content( id = UserWalletId("0"), name = "My Wallet", + balanceInAppBar = combinedReference( + stringReference("1,234"), + styledStringReference( + ".56", + { SpanStyle(color = TangemTheme.colors2.text.neutral.secondary) }, + ), + stringReference(" $"), + ), balance = combinedReference( stringReference("1,234"), styledStringReference( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletBalanceUM.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletBalanceUM.kt index cdbe103b75..a081ffe922 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletBalanceUM.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletBalanceUM.kt @@ -35,6 +35,7 @@ internal sealed interface WalletBalanceUM { override val id: UserWalletId, override val name: String, val balance: TextReference, + val balanceInAppBar: TextReference, val isBalanceFlickering: Boolean, val isZeroBalance: Boolean?, ) : WalletBalanceUM diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletEvent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletEvent.kt index 4e3b74f4cf..d26b02f2f7 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletEvent.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletEvent.kt @@ -31,4 +31,6 @@ internal sealed class WalletEvent { val onAllow: () -> Unit, val onDeny: () -> Unit, ) : WalletEvent() + + data object CollapseBalance : WalletEvent() } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListErrorTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListErrorTransformer.kt index 130c44f114..aacaf7a5d2 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListErrorTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListErrorTransformer.kt @@ -1,5 +1,6 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers +import androidx.compose.ui.text.SpanStyle import com.tangem.core.ui.format.bigdecimal.fiat import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.format.bigdecimal.formatStyled @@ -90,6 +91,13 @@ internal class SetTokenListErrorTransformer( return WalletBalanceUM.Content( id = id, name = name, + balanceInAppBar = BigDecimal.ZERO.formatStyled { + fiat( + fiatCurrencyCode = appCurrency.code, + fiatCurrencySymbol = appCurrency.symbol, + spanStyleReference = { SpanStyle(color = TangemTheme.colors2.text.neutral.secondary) }, + ) + }, balance = BigDecimal.ZERO.formatStyled { fiat( fiatCurrencyCode = appCurrency.code, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformer.kt index 4175d36c43..57be4ec68c 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformer.kt @@ -10,7 +10,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.transformers.converte import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.MultiWalletCardStateConverter import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.TokenListStateConverter -import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.WalletTokensListUMTransformer +import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.WalletTokensListUMConverter import com.tangem.feature.wallet.presentation.wallet.state.utils.enableButtons import timber.log.Timber import java.math.BigDecimal @@ -101,7 +101,7 @@ internal class SetTokenListTransformer( private fun toLoadedState(): WalletTokensListUM { if (params !is TokenConverterParams.Account) return WalletTokensListUM.Empty - return WalletTokensListUMTransformer( + return WalletTokensListUMConverter( selectedWallet = userWallet, appCurrency = appCurrency, clickIntents = clickIntents, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/MultiWalletBalanceUMTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/MultiWalletBalanceUMTransformer.kt index 69a82da93c..8369a81071 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/MultiWalletBalanceUMTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/MultiWalletBalanceUMTransformer.kt @@ -1,5 +1,6 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter +import androidx.compose.ui.text.SpanStyle import com.tangem.core.ui.format.bigdecimal.fiat import com.tangem.core.ui.format.bigdecimal.formatStyled import com.tangem.core.ui.res.TangemTheme @@ -41,6 +42,13 @@ internal class MultiWalletBalanceUMTransformer( return WalletBalanceUM.Content( id = id, name = name, + balanceInAppBar = fiatBalance.amount.formatStyled { + fiat( + fiatCurrencyCode = appCurrency.code, + fiatCurrencySymbol = appCurrency.symbol, + spanStyleReference = { SpanStyle(color = TangemTheme.colors2.text.neutral.secondary) }, + ) + }, balance = fiatBalance.amount.formatStyled { fiat( fiatCurrencyCode = appCurrency.code, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/WalletTokenAccountItemConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/WalletTokenAccountItemConverter.kt new file mode 100644 index 0000000000..75a56e8010 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/WalletTokenAccountItemConverter.kt @@ -0,0 +1,130 @@ +package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter + +import androidx.compose.ui.text.SpanStyle +import com.tangem.common.ui.account.AccountIconItemStateConverter +import com.tangem.common.ui.account.toUM +import com.tangem.core.ui.components.account.AccountIconSize +import com.tangem.core.ui.components.marketprice.PriceChangeState +import com.tangem.core.ui.components.marketprice.PriceChangeType +import com.tangem.core.ui.ds.image.TangemIconUM +import com.tangem.core.ui.ds.row.token.TangemTokenRowUM +import com.tangem.core.ui.extensions.pluralReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.extensions.styledResourceReference +import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.format.bigdecimal.fiat +import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.core.ui.format.bigdecimal.formatStyled +import com.tangem.core.ui.format.bigdecimal.percent +import com.tangem.core.ui.res.TangemTheme +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.models.TotalFiatBalance +import com.tangem.domain.models.account.Account +import com.tangem.domain.models.account.AccountId +import com.tangem.domain.models.account.AccountStatus +import com.tangem.domain.models.quote.PriceChange +import com.tangem.feature.wallet.impl.R +import com.tangem.utils.StringsSigns +import com.tangem.utils.converter.Converter +import kotlinx.collections.immutable.persistentListOf + +internal class WalletTokenAccountItemConverter( + private val appCurrency: AppCurrency, + private val expandedAccounts: Set, + private val onAccountCollapseClick: (account: Account) -> Unit, + private val onAccountExpandClick: (account: Account) -> Unit, +) : Converter { + override fun convert(value: AccountStatus.CryptoPortfolio): TangemTokenRowUM { + val account = value.account + val isExpanded = expandedAccounts.contains(account.accountId) + + return TangemTokenRowUM.Content( + id = account.accountId.value, + headIconUM = TangemIconUM.Currency( + currencyIconState = AccountIconItemStateConverter(size = AccountIconSize.ExtraSmall).convert(account), + ), + titleUM = TangemTokenRowUM.TitleUM.Content( + text = account.accountName.toUM().value, + ), + subtitleUM = TangemTokenRowUM.SubtitleUM.Content( + text = pluralReference( + R.plurals.common_tokens_count, + count = account.tokensCount, + formatArgs = wrappedList(account.tokensCount), + ), + ), + topEndContentUM = getTopEndContent(value.tokenList.totalFiatBalance), + bottomEndContentUM = getBottomEndContent( + value.tokenList.totalFiatBalance, + value.priceChangeLce.getOrNull(), + ), + onItemClick = { + if (isExpanded) { + onAccountCollapseClick(account) + } else { + onAccountExpandClick(account) + } + }, + onItemLongClick = null, + ) + } + + private fun getTopEndContent(accountBalance: TotalFiatBalance): TangemTokenRowUM.EndContentUM { + return when (accountBalance) { + TotalFiatBalance.Failed -> TangemTokenRowUM.EndContentUM.Content( + text = stringReference(StringsSigns.DASH_SIGN), + ) + is TotalFiatBalance.Loaded -> TangemTokenRowUM.EndContentUM.Content( + text = accountBalance.amount.formatStyled { + fiat( + fiatCurrencyCode = appCurrency.code, + fiatCurrencySymbol = appCurrency.symbol, + spanStyleReference = { SpanStyle(color = TangemTheme.colors2.text.neutral.secondary) }, + ) + }, + ) + TotalFiatBalance.Loading -> TangemTokenRowUM.EndContentUM.Loading + } + } + + private fun getBottomEndContent( + accountBalance: TotalFiatBalance, + priceChange: PriceChange?, + ): TangemTokenRowUM.EndContentUM { + return when (accountBalance) { + TotalFiatBalance.Failed -> TangemTokenRowUM.EndContentUM.Content( + text = styledResourceReference( + id = R.string.common_unreachable, + spanStyleReference = { SpanStyle(color = TangemTheme.colors2.text.status.attention) }, + ), + endIcons = persistentListOf( + TangemIconUM.Icon( + iconRes = R.drawable.ic_attention_default_24, + tintReference = { TangemTheme.colors2.graphic.status.attention }, + ), + ), + ) + is TotalFiatBalance.Loaded -> if (priceChange != null) { + val priceChangeType = PriceChangeType.fromBigDecimal(priceChange.value) + + TangemTokenRowUM.EndContentUM.Content( + text = stringReference( + priceChange.value.format { + fiat( + fiatCurrencyCode = appCurrency.code, + fiatCurrencySymbol = appCurrency.symbol, + ) + }, + ), + priceChangeUM = PriceChangeState.Content( + type = priceChangeType, + valueInPercent = priceChange.value.format { percent() }, + ), + ) + } else { + TangemTokenRowUM.EndContentUM.Empty + } + TotalFiatBalance.Loading -> TangemTokenRowUM.EndContentUM.Loading + } + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/WalletTokensListUMTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/WalletTokenCurrencyItemConverter.kt similarity index 53% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/WalletTokensListUMTransformer.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/WalletTokenCurrencyItemConverter.kt index 652591e155..0828c8e871 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/WalletTokensListUMTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/WalletTokenCurrencyItemConverter.kt @@ -3,252 +3,50 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers.convert import androidx.compose.ui.text.SpanStyle import com.tangem.common.getTotalCryptoAmount import com.tangem.common.getTotalFiatAmount -import com.tangem.common.ui.account.AccountIconItemStateConverter -import com.tangem.common.ui.account.toUM -import com.tangem.core.ui.components.account.AccountIconSize import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter import com.tangem.core.ui.components.marketprice.PriceChangeState import com.tangem.core.ui.components.marketprice.PriceChangeType import com.tangem.core.ui.ds.badge.* -import com.tangem.core.ui.ds.button.TangemButtonShape -import com.tangem.core.ui.ds.button.TangemButtonSize -import com.tangem.core.ui.ds.button.TangemButtonType -import com.tangem.core.ui.ds.button.TangemButtonUM import com.tangem.core.ui.ds.image.TangemIconUM -import com.tangem.core.ui.ds.row.header.TangemHeaderRowUM import com.tangem.core.ui.ds.row.token.TangemTokenRowUM -import com.tangem.core.ui.ds.row.token.TangemTokenRowUM.EndContentUM -import com.tangem.core.ui.extensions.* +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.extensions.styledResourceReference +import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.format.bigdecimal.* import com.tangem.core.ui.res.TangemTheme -import com.tangem.domain.account.models.AccountStatusList import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.card.common.util.cardTypesResolver import com.tangem.domain.models.StatusSource -import com.tangem.domain.models.TotalFiatBalance -import com.tangem.domain.models.account.AccountId -import com.tangem.domain.models.account.AccountStatus import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus -import com.tangem.domain.models.network.Network -import com.tangem.domain.models.tokenlist.TokenList import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.staking.model.StakingAvailability -import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents +import com.tangem.feature.wallet.child.wallet.model.intents.WalletContentClickIntents import com.tangem.feature.wallet.impl.R -import com.tangem.feature.wallet.presentation.wallet.state.model.TokensListItemUM2 -import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListUM import com.tangem.utils.StringsSigns import com.tangem.utils.converter.Converter import com.tangem.utils.extensions.addIf import com.tangem.utils.extensions.orZero import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList -import kotlinx.collections.immutable.toPersistentList import java.math.BigDecimal -@Suppress("LargeClass", "LongParameterList") -internal class WalletTokensListUMTransformer( +internal class WalletTokenCurrencyItemConverter( private val appCurrency: AppCurrency, private val selectedWallet: UserWallet, - private val clickIntents: WalletClickIntents, private val yieldModuleApyMap: Map, - private val isAccountsModeEnabled: Boolean, - private val expandedAccounts: Set, + private val clickIntents: WalletContentClickIntents, stakingAvailabilityMap: Map, - shouldShowMainPromo: Boolean, -) : Converter { +) : Converter, TangemTokenRowUM> { - private val yieldSupplyPromoBannerConverter = YieldSupplyPromoBannerConverter( - yieldModuleApyMap, - shouldShowMainPromo, - ) private val currencyToIconStateConverter = CryptoCurrencyToIconStateConverter() private val earnApyConverter = EarnApyConverter( yieldModuleApyMap = yieldModuleApyMap, stakingApyMap = stakingAvailabilityMap, ) - override fun convert(value: AccountStatusList): WalletTokensListUM { - val promoCryptoCurrency = yieldSupplyPromoBannerConverter.convert2(value = value) - return if (value.accountStatuses.isEmpty()) { - WalletTokensListUM.Empty - } else { - val isCollapsable = value.accountStatuses.count { - it is AccountStatus.CryptoPortfolio && it.account.tokensCount > 0 - } > 1 - - val tokenListUM = value.accountStatuses - .filterIsInstance() - .asSequence() - .flatMap { accountStatus -> - if (isAccountsModeEnabled) { - val isExpanded = expandedAccounts.contains(accountStatus.account.accountId) - sequenceOf( - TokensListItemUM2.Portfolio( - tokenRowUM = toAccountRow(accountStatus, isExpanded), - isExpanded = isExpanded || !isCollapsable, - isCollapsable = isCollapsable, - tokenList = getTokenListItems( - accountStatus.tokenList, - promoCryptoCurrency, - ).toPersistentList(), - ), - ) - } else { - getTokenListItems(accountStatus.tokenList, promoCryptoCurrency) - } - }.toPersistentList() - - WalletTokensListUM.Content( - tokenList = tokenListUM, - organizeButtonUM = getOrganizeButtonUM(value), - ) - } - } - - private fun getTokenListItems( - tokenList: TokenList, - promoCryptoCurrency: CryptoCurrencyStatus?, - ): Sequence { - return when (tokenList) { - TokenList.Empty -> emptySequence() - is TokenList.GroupedByNetwork -> { - tokenList.groups.asSequence().flatMap { (network, currencies) -> - buildList { - add( - TokensListItemUM2.GroupTitle( - tokenRowUM = toGroupRow(network), - ), - ) - addAll( - currencies.asSequence().map { currencyStatus -> - val shouldShowPromo = promoCryptoCurrency?.currency?.id == currencyStatus.currency.id - TokensListItemUM2.Token( - tokenRowUM = toCurrencyRow( - currencyStatus = currencyStatus, - shouldShowPromo = shouldShowPromo, - ), - ) - }.toList(), - ) - } - } - } - is TokenList.Ungrouped -> { - tokenList.currencies.asSequence().map { currencyStatus -> - TokensListItemUM2.Token( - toCurrencyRow( - currencyStatus = currencyStatus, - shouldShowPromo = promoCryptoCurrency?.currency?.id == currencyStatus.currency.id, - ), - ) - } - } - } - } - - private fun toAccountRow(accountStatus: AccountStatus.CryptoPortfolio, isExpanded: Boolean): TangemTokenRowUM { - val account = accountStatus.account - - val (topEndContent, bottomEndContent) = when (val accountBalance = accountStatus.tokenList.totalFiatBalance) { - TotalFiatBalance.Failed -> toFailedAccountRow() - is TotalFiatBalance.Loaded -> toLoadedAccountRow(accountStatus, accountBalance) - TotalFiatBalance.Loading -> EndContentUM.Loading to EndContentUM.Loading - } - - return TangemTokenRowUM.Content( - id = accountStatus.account.accountId.value, - headIconUM = TangemIconUM.Currency( - currencyIconState = AccountIconItemStateConverter(size = AccountIconSize.ExtraSmall).convert(account), - ), - titleUM = TangemTokenRowUM.TitleUM.Content( - text = account.accountName.toUM().value, - ), - subtitleUM = TangemTokenRowUM.SubtitleUM.Content( - text = pluralReference( - R.plurals.common_tokens_count, - count = account.tokensCount, - formatArgs = wrappedList(account.tokensCount), - ), - ), - topEndContentUM = topEndContent, - bottomEndContentUM = bottomEndContent, - onItemClick = { - if (isExpanded) { - clickIntents.onAccountCollapseClick(account) - } else { - clickIntents.onAccountExpandClick(account) - } - }, - onItemLongClick = null, - ) - } - - private fun toFailedAccountRow(): Pair { - return EndContentUM.Content( - text = stringReference(StringsSigns.DASH_SIGN), - ) to EndContentUM.Content( - text = styledResourceReference( - id = R.string.common_unreachable, - spanStyleReference = { SpanStyle(color = TangemTheme.colors2.text.status.attention) }, - ), - endIcons = persistentListOf( - TangemIconUM.Icon( - iconRes = R.drawable.ic_attention_default_24, - tintReference = { TangemTheme.colors2.graphic.status.attention }, - ), - ), - ) - } - - private fun toLoadedAccountRow( - accountStatus: AccountStatus.CryptoPortfolio, - accountBalance: TotalFiatBalance.Loaded, - ): Pair { - val priceChange = accountStatus.priceChangeLce.getOrNull() - - return EndContentUM.Content( - text = accountBalance.amount.formatStyled { - fiat( - fiatCurrencyCode = appCurrency.code, - fiatCurrencySymbol = appCurrency.symbol, - spanStyleReference = { SpanStyle(color = TangemTheme.colors2.text.neutral.secondary) }, - ) - }, - ) to if (priceChange != null) { - val priceChangeType = PriceChangeType.fromBigDecimal(priceChange.value) - - EndContentUM.Content( - text = stringReference( - priceChange.value.format { - fiat( - fiatCurrencyCode = appCurrency.code, - fiatCurrencySymbol = appCurrency.symbol, - ) - }, - ), - priceChangeUM = PriceChangeState.Content( - type = priceChangeType, - valueInPercent = priceChange.value.format { percent() }, - ), - ) - } else { - EndContentUM.Empty - } - } - - private fun toGroupRow(network: Network): TangemHeaderRowUM { - return TangemHeaderRowUM( - id = network.hashCode().toString(), - title = resourceReference( - id = R.string.wallet_network_group_title, - formatArgs = wrappedList(network.name), - ), - ) - } - - private fun toCurrencyRow(currencyStatus: CryptoCurrencyStatus, shouldShowPromo: Boolean): TangemTokenRowUM { + override fun convert(value: Pair): TangemTokenRowUM { + val (currencyStatus, shouldShowPromo) = value val earnApyInfo = earnApyConverter.convert(currencyStatus) return TangemTokenRowUM.Content( @@ -362,7 +160,7 @@ internal class WalletTokensListUMTransformer( } } - private fun toCurrencyRowTopEnd(currencyStatus: CryptoCurrencyStatus): EndContentUM { + private fun toCurrencyRowTopEnd(currencyStatus: CryptoCurrencyStatus): TangemTokenRowUM.EndContentUM { val yieldSupply = currencyStatus.value.yieldSupplyStatus return when (currencyStatus.value) { is CryptoCurrencyStatus.Loaded, @@ -370,7 +168,7 @@ internal class WalletTokensListUMTransformer( is CryptoCurrencyStatus.NoQuote, is CryptoCurrencyStatus.NoAccount, -> { - EndContentUM.Content( + TangemTokenRowUM.EndContentUM.Content( text = currencyStatus.getTotalFiatAmount().formatStyled { fiat( fiatCurrencyCode = appCurrency.code, @@ -397,11 +195,11 @@ internal class WalletTokensListUMTransformer( }.toImmutableList(), ) } - is CryptoCurrencyStatus.Loading -> EndContentUM.Loading - is CryptoCurrencyStatus.MissedDerivation -> EndContentUM.Content( + is CryptoCurrencyStatus.Loading -> TangemTokenRowUM.EndContentUM.Loading + is CryptoCurrencyStatus.MissedDerivation -> TangemTokenRowUM.EndContentUM.Content( text = stringReference(StringsSigns.DASH_SIGN), ) - is CryptoCurrencyStatus.Unreachable -> EndContentUM.Content( + is CryptoCurrencyStatus.Unreachable -> TangemTokenRowUM.EndContentUM.Content( text = styledResourceReference( id = R.string.common_unreachable, spanStyleReference = { SpanStyle(color = TangemTheme.colors2.text.status.attention) }, @@ -414,17 +212,17 @@ internal class WalletTokensListUMTransformer( ), ) is CryptoCurrencyStatus.NoAmount, - -> EndContentUM.Empty + -> TangemTokenRowUM.EndContentUM.Empty } } - private fun toCurrencyRowBottomEnd(currencyStatus: CryptoCurrencyStatus): EndContentUM { + private fun toCurrencyRowBottomEnd(currencyStatus: CryptoCurrencyStatus): TangemTokenRowUM.EndContentUM { return when (currencyStatus.value) { is CryptoCurrencyStatus.Loaded, is CryptoCurrencyStatus.Custom, is CryptoCurrencyStatus.NoQuote, is CryptoCurrencyStatus.NoAccount, - -> EndContentUM.Content( + -> TangemTokenRowUM.EndContentUM.Content( text = stringReference( currencyStatus.getTotalCryptoAmount().format { crypto(cryptoCurrency = currencyStatus.currency) @@ -432,8 +230,8 @@ internal class WalletTokensListUMTransformer( ), isFlickering = currencyStatus.value.isFlickering(), ) - is CryptoCurrencyStatus.Loading -> EndContentUM.Loading - is CryptoCurrencyStatus.MissedDerivation -> EndContentUM.Content( + is CryptoCurrencyStatus.Loading -> TangemTokenRowUM.EndContentUM.Loading + is CryptoCurrencyStatus.MissedDerivation -> TangemTokenRowUM.EndContentUM.Content( text = styledResourceReference( id = R.string.common_no_address, spanStyleReference = { SpanStyle(color = TangemTheme.colors2.text.status.attention) }, @@ -445,7 +243,7 @@ internal class WalletTokensListUMTransformer( ), ), ) - is CryptoCurrencyStatus.Unreachable -> EndContentUM.Content( + is CryptoCurrencyStatus.Unreachable -> TangemTokenRowUM.EndContentUM.Content( text = styledResourceReference( id = R.string.common_unreachable, spanStyleReference = { SpanStyle(color = TangemTheme.colors2.text.status.attention) }, @@ -458,7 +256,7 @@ internal class WalletTokensListUMTransformer( ), ) is CryptoCurrencyStatus.NoAmount, - -> EndContentUM.Empty + -> TangemTokenRowUM.EndContentUM.Empty } } @@ -496,26 +294,5 @@ internal class WalletTokensListUMTransformer( ) } - private fun getOrganizeButtonUM(accountList: AccountStatusList): TangemButtonUM? { - return if (accountList.flattenCurrencies().size > 1 && !isSingleCurrencyWalletWithToken()) { - TangemButtonUM( - text = resourceReference(R.string.organize_tokens_title), - isEnabled = accountList.totalFiatBalance !is TotalFiatBalance.Loading, - size = TangemButtonSize.X9, - shape = TangemButtonShape.Rounded, - type = TangemButtonType.PrimaryInverse, - iconRes = R.drawable.ic_filter_default_24, - onClick = clickIntents::onOrganizeTokensClick, - ) - } else { - null - } - } - private fun CryptoCurrencyStatus.Value.isFlickering(): Boolean = sources.total == StatusSource.CACHE - - private fun isSingleCurrencyWalletWithToken(): Boolean { - return selectedWallet is UserWallet.Cold && - selectedWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken() - } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/WalletTokensListUMConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/WalletTokensListUMConverter.kt new file mode 100644 index 0000000000..b4cf26bf7e --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/WalletTokensListUMConverter.kt @@ -0,0 +1,166 @@ +package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter + +import com.tangem.core.ui.ds.button.TangemButtonShape +import com.tangem.core.ui.ds.button.TangemButtonSize +import com.tangem.core.ui.ds.button.TangemButtonType +import com.tangem.core.ui.ds.button.TangemButtonUM +import com.tangem.core.ui.ds.row.header.TangemHeaderRowUM +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.wrappedList +import com.tangem.domain.account.models.AccountStatusList +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.models.TotalFiatBalance +import com.tangem.domain.models.account.AccountId +import com.tangem.domain.models.account.AccountStatus +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.domain.models.network.Network +import com.tangem.domain.models.tokenlist.TokenList +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.staking.model.StakingAvailability +import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents +import com.tangem.feature.wallet.impl.R +import com.tangem.feature.wallet.presentation.wallet.state.model.TokensListItemUM2 +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListUM +import com.tangem.feature.wallet.presentation.wallet.state.utils.isSingleWalletWithToken +import com.tangem.utils.converter.Converter +import kotlinx.collections.immutable.toPersistentList +import java.math.BigDecimal + +@Suppress("LongParameterList") +internal class WalletTokensListUMConverter( + private val appCurrency: AppCurrency, + private val selectedWallet: UserWallet, + private val clickIntents: WalletClickIntents, + private val yieldModuleApyMap: Map, + private val isAccountsModeEnabled: Boolean, + private val expandedAccounts: Set, + stakingAvailabilityMap: Map, + shouldShowMainPromo: Boolean, +) : Converter { + + private val accountRowConverter by lazy(LazyThreadSafetyMode.NONE) { + WalletTokenAccountItemConverter( + appCurrency = appCurrency, + expandedAccounts = expandedAccounts, + onAccountCollapseClick = clickIntents::onAccountCollapseClick, + onAccountExpandClick = clickIntents::onAccountExpandClick, + ) + } + + private val currencyRowConverter by lazy(LazyThreadSafetyMode.NONE) { + WalletTokenCurrencyItemConverter( + appCurrency = appCurrency, + selectedWallet = selectedWallet, + yieldModuleApyMap = yieldModuleApyMap, + stakingAvailabilityMap = stakingAvailabilityMap, + clickIntents = clickIntents, + ) + } + private val yieldSupplyPromoBannerConverter by lazy(LazyThreadSafetyMode.NONE) { + YieldSupplyPromoBannerConverter( + yieldModuleApyMap, + shouldShowMainPromo, + ) + } + + override fun convert(value: AccountStatusList): WalletTokensListUM { + val promoCryptoCurrency = yieldSupplyPromoBannerConverter.convert2(value = value) + return if (value.accountStatuses.isEmpty()) { + WalletTokensListUM.Empty + } else { + val isCollapsable = value.accountStatuses.count { + it is AccountStatus.CryptoPortfolio && it.account.tokensCount > 0 + } > 1 + + val tokenListUM = value.accountStatuses + .filterIsInstance() + .asSequence() + .flatMap { accountStatus -> + if (isAccountsModeEnabled) { + val isExpanded = expandedAccounts.contains(accountStatus.account.accountId) + sequenceOf( + TokensListItemUM2.Portfolio( + tokenRowUM = accountRowConverter.convert(accountStatus), + isExpanded = isExpanded || !isCollapsable, + isCollapsable = isCollapsable, + tokenList = getTokenListItems( + accountStatus.tokenList, + promoCryptoCurrency, + ).toPersistentList(), + ), + ) + } else { + getTokenListItems(accountStatus.tokenList, promoCryptoCurrency) + } + }.toPersistentList() + + WalletTokensListUM.Content( + tokenList = tokenListUM, + organizeButtonUM = getOrganizeButtonUM(value), + ) + } + } + + private fun getTokenListItems( + tokenList: TokenList, + promoCryptoCurrency: CryptoCurrencyStatus?, + ): Sequence { + return when (tokenList) { + TokenList.Empty -> emptySequence() + is TokenList.GroupedByNetwork -> { + tokenList.groups.asSequence().flatMap { (network, currencies) -> + buildList { + add( + TokensListItemUM2.GroupTitle( + tokenRowUM = toGroupRow(network), + ), + ) + addAll( + currencies.asSequence().map { currencyStatus -> + val shouldShowPromo = promoCryptoCurrency?.currency?.id == currencyStatus.currency.id + TokensListItemUM2.Token( + tokenRowUM = currencyRowConverter.convert(currencyStatus to shouldShowPromo), + ) + }.toList(), + ) + } + } + } + is TokenList.Ungrouped -> { + tokenList.currencies.asSequence().map { currencyStatus -> + val shouldShowPromo = promoCryptoCurrency?.currency?.id == currencyStatus.currency.id + TokensListItemUM2.Token( + tokenRowUM = currencyRowConverter.convert(currencyStatus to shouldShowPromo), + ) + } + } + } + } + + private fun toGroupRow(network: Network): TangemHeaderRowUM { + return TangemHeaderRowUM( + id = network.hashCode().toString(), + title = resourceReference( + id = R.string.wallet_network_group_title, + formatArgs = wrappedList(network.name), + ), + ) + } + + private fun getOrganizeButtonUM(accountList: AccountStatusList): TangemButtonUM? { + return if (accountList.flattenCurrencies().size > 1 && !selectedWallet.isSingleWalletWithToken()) { + TangemButtonUM( + text = resourceReference(R.string.organize_tokens_title), + isEnabled = accountList.totalFiatBalance !is TotalFiatBalance.Loading, + size = TangemButtonSize.X9, + shape = TangemButtonShape.Rounded, + type = TangemButtonType.PrimaryInverse, + iconRes = R.drawable.ic_filter_default_24, + onClick = clickIntents::onOrganizeTokensClick, + ) + } else { + null + } + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/UserWalletConverterExt.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/UserWalletConverterExt.kt index f762997767..8e3ebc996c 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/UserWalletConverterExt.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/UserWalletConverterExt.kt @@ -21,4 +21,8 @@ private fun UserWallet.Cold.isWalletWithTokens(): Boolean { internal fun UserWallet.isSingleWallet(): Boolean { return this is UserWallet.Cold && scanResponse.cardTypesResolver.isSingleWallet() +} + +internal fun UserWallet.isSingleWalletWithToken(): Boolean { + return this is UserWallet.Cold && scanResponse.cardTypesResolver.isSingleWalletWithToken() } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffect.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffect.kt index 9f4df0e402..838acf9e43 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffect.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletEventEffect.kt @@ -1,6 +1,7 @@ package com.tangem.feature.wallet.presentation.wallet.ui import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.pager.PagerState import androidx.compose.material3.SnackbarDuration import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.* @@ -17,7 +18,7 @@ import com.tangem.feature.wallet.presentation.wallet.ui.utils.demonstrateScrolli import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION @Composable -internal fun WalletEventEffect( +internal fun WalletEventEffectLegacy( walletsListState: LazyListState, snackbarHostState: SnackbarHostState, event: StateEvent, @@ -59,6 +60,53 @@ internal fun WalletEventEffect( is WalletEvent.RequestPushPermissions -> { showPermissionRequest = value.onAllow to value.onDeny } + is WalletEvent.CollapseBalance -> { /* no-op */ } + } + }, + ) +} + +@Composable +internal fun WalletEventEffect( + walletsPagerState: PagerState, + snackbarHostState: SnackbarHostState, + event: StateEvent, + onCollapseBalance: () -> Unit, +) { + val resources = LocalContext.current.resources + + var showPermissionRequest by remember { mutableStateOf Unit, () -> Unit>?>(null) } + HandlePermissionRequest( + permissionRequestParams = showPermissionRequest, + onPermissionRequestResult = { showPermissionRequest = null }, + ) + + EventEffect( + event = event, + onTrigger = { value -> + when (value) { + is WalletEvent.ChangeWallet -> { + walletsPagerState.animateScrollToPage(page = value.newIndex) + } + is WalletEvent.ChangeWalletWithoutScroll -> { + walletsPagerState.scrollToPage(page = value.newIndex) + } + is WalletEvent.ShowError -> { + snackbarHostState.showSnackbar(message = value.text.resolveReference(resources)) + } + is WalletEvent.CopyAddress -> { + snackbarHostState.showSnackbar( + message = resources.getStringSafe(R.string.wallet_notification_address_copied), + duration = SnackbarDuration.Short, + ) + } + is WalletEvent.DemonstrateWalletsScrollPreview -> { + /* no-op */ + } + is WalletEvent.RequestPushPermissions -> { + showPermissionRequest = value.onAllow to value.onDeny + } + is WalletEvent.CollapseBalance -> onCollapseBalance() } }, ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt index 324c26f234..114a71988b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt @@ -68,9 +68,9 @@ import com.tangem.core.ui.utils.lineTo import com.tangem.core.ui.utils.moveTo import com.tangem.core.ui.utils.toPx import com.tangem.feature.wallet.impl.R -import com.tangem.feature.wallet.presentation.common.preview.WalletScreenPreviewData.accountScreenState -import com.tangem.feature.wallet.presentation.common.preview.WalletScreenPreviewData.accountScreenWithEmptyTokensState -import com.tangem.feature.wallet.presentation.common.preview.WalletScreenPreviewData.walletScreenState +import com.tangem.feature.wallet.presentation.common.preview.WalletScreenPreviewDataLegacy.accountScreenState +import com.tangem.feature.wallet.presentation.common.preview.WalletScreenPreviewDataLegacy.accountScreenWithEmptyTokensState +import com.tangem.feature.wallet.presentation.common.preview.WalletScreenPreviewDataLegacy.walletScreenState import com.tangem.feature.wallet.presentation.wallet.state.model.* import com.tangem.feature.wallet.presentation.wallet.state.model.holder.TxHistoryStateHolder import com.tangem.feature.wallet.presentation.wallet.ui.components.TokenActionsBottomSheet @@ -111,7 +111,7 @@ internal fun WalletScreen( onBottomSheetStateChange = onBottomSheetStateChange, ) - WalletEventEffect( + WalletEventEffectLegacy( walletsListState = walletsListState, snackbarHostState = snackbarHostState, event = state.event, @@ -499,7 +499,7 @@ private fun MarketsTooltip( } @Composable -internal fun MarketsHint(isVisible: Boolean, modifier: Modifier = Modifier) { +private fun MarketsHint(isVisible: Boolean, modifier: Modifier = Modifier) { AnimatedVisibility( modifier = modifier, visible = isVisible, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen2.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen2.kt index e368898477..c2c0b04c1a 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen2.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen2.kt @@ -3,7 +3,9 @@ package com.tangem.feature.wallet.presentation.wallet.ui import android.content.res.Configuration import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.Spring import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.foundation.Canvas import androidx.compose.foundation.background @@ -12,51 +14,66 @@ import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.PagerState import androidx.compose.foundation.pager.rememberPagerState -import androidx.compose.material3.* +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.focus.onFocusChanged -import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalSoftwareKeyboardController -import androidx.compose.ui.platform.testTag import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.arkivanov.decompose.ExperimentalDecomposeApi import com.tangem.core.ui.components.atoms.Hand import com.tangem.core.ui.components.atoms.handComposableComponentHeight import com.tangem.core.ui.components.background.northernlights.NorthernLightsBackground import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState -import com.tangem.core.ui.components.haze.hazeEffectTangem import com.tangem.core.ui.components.haze.hazeSourceTangem import com.tangem.core.ui.components.rememberIsKeyboardVisible import com.tangem.core.ui.components.sheetscaffold.* import com.tangem.core.ui.components.snackbar.CopiedTextSnackbar import com.tangem.core.ui.components.snackbar.TangemSnackbar -import com.tangem.core.ui.ds.topbar.TangemTopBar +import com.tangem.core.ui.ds.topbar.collapsing.TangemCollapsingAppBarBehavior +import com.tangem.core.ui.ds.topbar.collapsing.TangemCollapsingTopBar +import com.tangem.core.ui.ds.topbar.collapsing.rememberTangemExitUntilCollapsedScrollBehavior import com.tangem.core.ui.event.StateEvent -import com.tangem.core.ui.extensions.stringReference -import com.tangem.core.ui.res.* -import com.tangem.core.ui.test.MainScreenTestTags -import com.tangem.feature.wallet.impl.R -import com.tangem.feature.wallet.presentation.common.preview.WalletScreenPreviewData.accountScreenState -import com.tangem.feature.wallet.presentation.common.preview.WalletScreenPreviewData.accountScreenWithEmptyTokensState -import com.tangem.feature.wallet.presentation.common.preview.WalletScreenPreviewData.walletScreenState +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.res.LocalMainBottomSheetColor +import com.tangem.core.ui.res.LocalWindowSize +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreviewRedesign +import com.tangem.feature.wallet.presentation.common.preview.WalletScreenPreviewData import com.tangem.feature.wallet.presentation.wallet.state.model.NOT_INITIALIZED_WALLET_INDEX +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletBalanceUM import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState -import dev.chrisbanes.haze.HazeProgressive +import com.tangem.feature.wallet.presentation.wallet.ui.components.MarketsHint +import com.tangem.feature.wallet.presentation.wallet.ui.components.MarketsTooltip +import com.tangem.feature.wallet.presentation.wallet.ui.components.common.WalletBalance +import com.tangem.feature.wallet.presentation.wallet.ui.components.common.WalletListContent +import com.tangem.feature.wallet.presentation.wallet.ui.components.common.WalletPagerIndicator +import com.tangem.feature.wallet.presentation.wallet.ui.components.common.WalletTopBar +import com.tangem.feature.wallet.presentation.wallet.ui.utils.lazyListStateMapSaver import kotlinx.coroutines.launch +import kotlin.math.abs + +private const val MARKET_HINT_THRESHOLD = 0.5f @OptIn(ExperimentalDecomposeApi::class) @Composable @@ -69,98 +86,201 @@ internal fun WalletScreen2( // It means that screen is still initializing if (state.selectedWalletIndex == NOT_INITIALIZED_WALLET_INDEX) return - val walletsListState = rememberLazyListState(initialFirstVisibleItemIndex = state.selectedWalletIndex) + val statusBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getTop(this).toDp() } + val snackbarHostState = remember(::SnackbarHostState) - val isAutoScroll = remember { mutableStateOf(value = false) } + val walletsPagerState = rememberPagerState( + initialPage = state.selectedWalletIndex, + pageCount = { state.wallets2.size }, + ) + + val partialCollapsedHeight = 64.dp + statusBarHeight + val balanceBlockHeight = 320.dp + partialCollapsedHeight + val behavior = rememberTangemExitUntilCollapsedScrollBehavior( + expandedHeight = balanceBlockHeight, + partialCollapsedHeight = partialCollapsedHeight, + snapAnimationSpec = spring(stiffness = Spring.StiffnessMedium), + ) + + val coroutineScope = rememberCoroutineScope() WalletContent2( state = state, - walletsListState = walletsListState, + walletsPagerState = walletsPagerState, snackbarHostState = snackbarHostState, - isAutoScroll = isAutoScroll, - onAutoScrollReset = { isAutoScroll.value = false }, + behavior = behavior, bottomSheetContent = bottomSheetContent, bottomSheetHeaderHeightProvider = bottomSheetHeaderHeightProvider, onBottomSheetStateChange = onBottomSheetStateChange, ) WalletEventEffect( - walletsListState = walletsListState, + walletsPagerState = walletsPagerState, snackbarHostState = snackbarHostState, event = state.event, - onAutoScrollSet = { isAutoScroll.value = true }, + onCollapseBalance = { + if (behavior.state.collapsedFraction < 1f) { + coroutineScope.launch { + behavior.state.collapse() + } + } + }, ) } -@OptIn(ExperimentalMaterial3Api::class, ExperimentalDecomposeApi::class) -@Suppress("LongMethod", "LongParameterList", "UnusedPrivateMember") +@Suppress("LongMethod", "LongParameterList", "CyclomaticComplexMethod") @Composable private fun WalletContent2( state: WalletScreenState, - walletsListState: LazyListState, + walletsPagerState: PagerState, + behavior: TangemCollapsingAppBarBehavior, snackbarHostState: SnackbarHostState, - isAutoScroll: State, - onAutoScrollReset: () -> Unit, bottomSheetHeaderHeightProvider: () -> Dp, onBottomSheetStateChange: (BottomSheetState) -> Unit, bottomSheetContent: @Composable (() -> Unit), ) { - /* - * Don't pass key to remember, because it will brake scroll animation. - * selectedWalletIndex will be changed in WalletsListEffects. - */ - // val selectedWalletIndex by remember(state.selectedWalletIndex) { mutableIntStateOf(state.selectedWalletIndex) } - // val selectedWallet = state.wallets2.getOrElse(selectedWalletIndex) { state.wallets2[state.selectedWalletIndex] } + val density = LocalDensity.current + val bottomBarHeight = with(density) { WindowInsets.systemBars.getBottom(this).toDp() } - val statusBarHeight = with(LocalDensity.current) { WindowInsets.statusBars.getBottom(this).toDp() } - - val listState = rememberLazyListState() - - val partialCollapsedHeight = 64.dp + statusBarHeight - - val scaffoldContent: @Composable (PaddingValues?) -> Unit = { _ -> - Box(Modifier.fillMaxSize()) { - NorthernLightsBackground(Modifier.matchParentSize()) - } - - val pagerState = rememberPagerState( - initialPage = state.selectedWalletIndex, - pageCount = { state.wallets2.size }, - ) - - LaunchedEffect(pagerState.currentPage) { - if (pagerState.currentPage != state.selectedWalletIndex) { - state.onWalletChange(pagerState.currentPage, false) - } - } - } + var walletBalance by remember { mutableStateOf(TextReference.EMPTY) } BaseScaffoldWithMarkets( state = state, - listState = listState, snackbarHostState = snackbarHostState, bottomSheetHeaderHeightProvider = bottomSheetHeaderHeightProvider, onBottomSheetStateChange = onBottomSheetStateChange, bottomSheetContent = bottomSheetContent, - content = scaffoldContent, - ) + appBarContent = { + WalletTopBar( + topBarConfig = state.topBarConfig, + walletBalance = walletBalance, + behavior = behavior, + ) + }, + ) { paddingValues, bottomSheetState -> + val marketHintApproxHeight = 140.dp + + val contentPadding = PaddingValues( + bottom = paddingValues.calculateBottomPadding() + marketHintApproxHeight, + ) + + LaunchedEffect(walletsPagerState.currentPage) { + if (walletsPagerState.currentPage != state.selectedWalletIndex) { + state.onWalletChange(walletsPagerState.currentPage, false) + } + } + + val listStates = rememberSaveable(saver = lazyListStateMapSaver(walletsPagerState.pageCount)) { + mutableMapOf().apply { + repeat(walletsPagerState.pageCount) { index -> put(index, LazyListState()) } + } + } + + val canPagerScroll by remember { derivedStateOf { behavior.state.heightOffset == 0f } } + + Box( + modifier = Modifier + .fillMaxSize() + .hazeSourceTangem(-1f), + ) { + NorthernLightsBackground(Modifier.matchParentSize()) + + WalletPagerIndicator( + pagerState = walletsPagerState, + behavior = behavior, + ) + + HorizontalPager( + state = walletsPagerState, + userScrollEnabled = canPagerScroll, + beyondViewportPageCount = 1, + ) { currentWalletIndex -> + val listState = listStates[currentWalletIndex] ?: rememberLazyListState() + + val currentWallet = state.wallets2.getOrElse(currentWalletIndex) { + state.wallets2[state.selectedWalletIndex] + } + + LaunchedEffect(walletsPagerState.currentPage) { + if (walletsPagerState.currentPage == currentWalletIndex) { + walletBalance = (currentWallet.walletsBalanceUM as? WalletBalanceUM.Content)?.balanceInAppBar + } + } + + val isShowMarketsHint by remember { + derivedStateOf { + behavior.state.collapsedFraction > MARKET_HINT_THRESHOLD && + listState.layoutInfo.totalItemsCount > 0 && + !listState.canScrollBackward && !listState.canScrollForward || + listState.canScrollBackward && !listState.canScrollForward + } + } + + val pageSlideAlpha by rememberPageAlpha(walletsPagerState, currentWalletIndex) + + Box( + modifier = Modifier.alpha(pageSlideAlpha), + ) { + TangemCollapsingTopBar( + state = behavior.state, + collapsingPart = { + WalletBalance( + behavior = behavior, + walletBalanceUM = currentWallet.walletsBalanceUM, + buttons = currentWallet.buttons, + isBalanceHidden = state.isHidingMode, + ) + }, + body = { + WalletListContent( + currentWallet = currentWallet, + listState = listState, + isBalanceHidden = state.isHidingMode, + contentPadding = contentPadding, + modifier = Modifier + .fillMaxSize() + .nestedScroll(behavior.nestedScrollConnection), + ) + }, + ) + + val peekHeight = bottomSheetHeaderHeightProvider() + handComposableComponentHeight + bottomBarHeight + MarketsHint( + modifier = Modifier + .align(Alignment.BottomCenter) + .padding(bottom = peekHeight + TangemTheme.dimens2.x7), + isVisible = isShowMarketsHint, + ) + } + } + + MarketsTooltip( + modifier = Modifier + .align(Alignment.BottomCenter) + .padding(bottom = 24.dp) + .fillMaxWidth(fraction = 0.7f), + isVisible = state.showMarketsOnboarding, + availableHeight = LocalWindowSize.current.height, + bottomSheetState = bottomSheetState, + ) + } + } } -@Suppress("LongParameterList", "LongMethod", "CyclomaticComplexMethod", "UnusedPrivateMember") +@Suppress("LongParameterList", "LongMethod") @OptIn(ExperimentalMaterial3Api::class) @Composable private inline fun BaseScaffoldWithMarkets( state: WalletScreenState, snackbarHostState: SnackbarHostState, - listState: LazyListState, bottomSheetHeaderHeightProvider: () -> Dp, modifier: Modifier = Modifier, noinline onBottomSheetStateChange: (BottomSheetState) -> Unit, + crossinline appBarContent: @Composable () -> Unit, crossinline bottomSheetContent: @Composable () -> Unit, - crossinline content: @Composable (PaddingValues) -> Unit, + crossinline content: @Composable (PaddingValues, TangemSheetState) -> Unit, ) { val bottomSheetState = rememberTangemStandardBottomSheetState() - val isPowerSaving by LocalPowerSavingState.current.isPowerSavingModeEnabled.collectAsStateWithLifecycle() val isKeyboardVisible by rememberIsKeyboardVisible() @@ -176,7 +296,7 @@ private inline fun BaseScaffoldWithMarkets( val maxHeight = LocalWindowSize.current.height val coroutineScope = rememberCoroutineScope() - val background = TangemTheme.colors2.surface.level2 + val background = TangemTheme.colors2.surface.level3 CompositionLocalProvider( LocalMainBottomSheetColor provides remember(background) { mutableStateOf(background) }, @@ -194,20 +314,12 @@ private inline fun BaseScaffoldWithMarkets( Box(modifier = modifier) { TangemBottomSheetScaffold( - modifier = Modifier.background( - brush = Brush.verticalGradient( - listOf( - TangemTheme.colors2.surface.level1, - TangemTheme.colors2.surface.level2, - ), - ), - ), snackbarHost = { snackbarHostState -> WalletSnackbarHost( snackbarHostState = snackbarHostState, event = state.event, modifier = Modifier - .padding(bottom = TangemTheme.dimens.spacing4) + .padding(bottom = TangemTheme.dimens2.x1) .navigationBarsPadding(), ) }, @@ -250,47 +362,22 @@ private inline fun BaseScaffoldWithMarkets( } }, content = { paddingValues -> - Box { - Column( - modifier = Modifier.hazeSourceTangem(-1f), - ) { - content(paddingValues) - } + content(paddingValues, bottomSheetState) + appBarContent() - Surface( - color = Color.Unspecified, - contentColor = Color.Unspecified, - modifier = Modifier - .hazeEffectTangem { - progressive = - HazeProgressive.verticalGradient(startIntensity = 1f, endIntensity = 0f) - }, - ) { - TangemTopBar( - title = stringReference(""), // todo balance - startIconRes = R.drawable.ic_tangem_24, - endIconRes = R.drawable.ic_more_default_24, - onEndContentClick = state.topBarConfig.onDetailsClick, - isGhostButtons = !isPowerSaving, - modifier = Modifier - .testTag(MainScreenTestTags.TOP_BAR), - ) - } - - BottomSheetScrim( - color = if (state.showMarketsOnboarding) { - Color.Black.copy(alpha = .65f) - } else { - Color.Black.copy(alpha = .40f) - }, - visible = bottomSheetState.targetValue == TangemSheetValue.Expanded || - state.showMarketsOnboarding, - onDismissRequest = { - coroutineScope.launch { bottomSheetState.partialExpand() } - state.onDismissMarketsTooltip() - }, - ) - } + BottomSheetScrim( + color = if (state.showMarketsOnboarding) { + Color.Black.copy(alpha = .65f) + } else { + Color.Black.copy(alpha = .40f) + }, + visible = bottomSheetState.targetValue == TangemSheetValue.Expanded || + state.showMarketsOnboarding, + onDismissRequest = { + coroutineScope.launch { bottomSheetState.partialExpand() } + state.onDismissMarketsTooltip() + }, + ) }, ) @@ -410,6 +497,29 @@ private fun WalletSnackbarHost( } } +@Composable +private fun rememberPageAlpha(pagerState: PagerState, currentPageIndex: Int): State { + return remember { + derivedStateOf { + val pageOffset = pagerState.currentPageOffsetFraction + val currentPage = pagerState.currentPage + + when { + // Current page is being swiped away + currentPageIndex == currentPage -> { + 1f - abs(pageOffset) * 2f + } + // Target page is being swiped in + currentPageIndex == pagerState.targetPage -> { + (abs(pageOffset) * 2f - 1f).coerceAtLeast(0f) + } + // Other pages remain invisible + else -> 0f + }.coerceIn(0f, 1f) + } + } +} + // region Preview @OptIn(ExperimentalDecomposeApi::class) @Preview(showBackground = true, widthDp = 360) @@ -431,10 +541,8 @@ private fun WalletScreen2_Preview(@PreviewParameter(WalletScreen2PreviewProvider private class WalletScreen2PreviewProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( - walletScreenState, - walletScreenState.copy(selectedWalletIndex = 1), - accountScreenState.copy(selectedWalletIndex = 1), - accountScreenWithEmptyTokensState.copy(selectedWalletIndex = 1), + WalletScreenPreviewData.defaultState, + WalletScreenPreviewData.defaultState.copy(selectedWalletIndex = 1), ) } // endregion \ No newline at end of file