Updated on 2026-08-14

This commit is contained in:
Tangem 2026-02-19 17:05:56 +05:00
parent 2be3a915ea
commit 2348d3456f
10 changed files with 528 additions and 240 deletions

View file

@ -20,6 +20,7 @@ import com.tangem.core.ui.res.TangemThemePreview
fun TangemPullToRefreshContainer(
config: PullToRefreshConfig,
modifier: Modifier = Modifier,
indicatorModifier: Modifier = Modifier,
content: @Composable () -> Unit,
) {
val state = rememberPullToRefreshState()
@ -32,7 +33,7 @@ fun TangemPullToRefreshContainer(
modifier = modifier,
indicator = {
Indicator(
modifier = Modifier.align(Alignment.TopCenter),
modifier = indicatorModifier.align(Alignment.TopCenter),
isRefreshing = config.isRefreshing,
state = state,
containerColor = TangemTheme.colors.background.tertiary,

View file

@ -9,6 +9,12 @@ plugins {
android {
namespace = "com.tangem.feature.wallet.impl"
packaging {
resources {
// To build and run composable preview
merges += "paymentrequest.proto"
}
}
}
dependencies {
@ -44,6 +50,16 @@ dependencies {
exclude(group = "com.google.firebase", module = "protolite-well-known-types")
exclude(group = "com.google.protobuf", module = "protobuf-javalite")
}
implementation(deps.haze) {
exclude(module = "activity-compose")
exclude(module = "activity")
exclude(module = "activity-ktx")
}
implementation(deps.haze.materials) {
exclude(module = "activity-compose")
exclude(module = "activity")
exclude(module = "activity-ktx")
}
/** DI */
implementation(deps.hilt.android)

View file

@ -5,6 +5,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.arkivanov.decompose.ExperimentalDecomposeApi
import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.arkivanov.decompose.router.slot.childSlot
import com.arkivanov.decompose.router.slot.dismiss
@ -14,6 +15,7 @@ import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.child
import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.DesignFeatureToggles
import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.core.ui.decompose.ComposableContentComponent
@ -23,6 +25,7 @@ import com.tangem.feature.wallet.child.wallet.model.WalletModel
import com.tangem.feature.wallet.navigation.WalletRoute
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletDialogConfig
import com.tangem.feature.wallet.presentation.wallet.ui.WalletScreen
import com.tangem.feature.wallet.presentation.wallet.ui.WalletScreen2
import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.KycRejectedComponent
import com.tangem.feature.walletsettings.component.RenameWalletComponent
import com.tangem.features.biometry.AskBiometryComponent
@ -38,6 +41,7 @@ import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.launch
@OptIn(ExperimentalDecomposeApi::class)
@Suppress("LongParameterList")
internal class WalletComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
@ -50,6 +54,7 @@ internal class WalletComponent @AssistedInject constructor(
private val tokenReceiveComponentFactory: TokenReceiveComponent.Factory,
private val yieldSupplyDepositedWarningComponent: YieldSupplyDepositedWarningComponent.Factory,
private val feedFeatureToggle: FeedFeatureToggle,
private val designFeatureToggles: DesignFeatureToggles,
) : ComposableContentComponent, AppComponentContext by appComponentContext {
private val model: WalletModel = getOrCreateModel()
@ -148,18 +153,33 @@ internal class WalletComponent @AssistedInject constructor(
var headerSize by remember { mutableStateOf(0.dp) }
val dialog by dialog.subscribeAsState()
WalletScreen(
state = model.uiState.collectAsStateWithLifecycle().value,
bottomSheetContent = {
BottomSheetContent(
bottomSheetState = bottomSheetState,
onHeaderSizeChange = { headerSize = it },
modifier = modifier,
)
},
bottomSheetHeaderHeightProvider = { headerSize },
onBottomSheetStateChange = { bottomSheetState.value = it },
)
if (designFeatureToggles.isRedesignEnabled) {
WalletScreen2(
state = model.uiState.collectAsStateWithLifecycle().value,
bottomSheetContent = {
BottomSheetContent(
bottomSheetState = bottomSheetState,
onHeaderSizeChange = { headerSize = it },
modifier = modifier,
)
},
bottomSheetHeaderHeightProvider = { headerSize },
onBottomSheetStateChange = { bottomSheetState.value = it },
)
} else {
WalletScreen(
state = model.uiState.collectAsStateWithLifecycle().value,
bottomSheetContent = {
BottomSheetContent(
bottomSheetState = bottomSheetState,
onHeaderSizeChange = { headerSize = it },
modifier = modifier,
)
},
bottomSheetHeaderHeightProvider = { headerSize },
onBottomSheetStateChange = { bottomSheetState.value = it },
)
}
when (val dialog = dialog.child?.instance) {
is ComposableDialogComponent -> dialog.Dialog()

View file

@ -1,6 +1,5 @@
package com.tangem.feature.wallet.presentation.common
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.event.consumedEvent
@ -186,17 +185,6 @@ internal object WalletPreviewData {
)
}
val bottomSheet by lazy {
TangemBottomSheetConfig(
isShown = false,
onDismissRequest = {},
content = WalletBottomSheetConfig.UnlockWallets(
onUnlockClick = {},
onScanClick = {},
),
)
}
val actionsBottomSheet = ActionsBottomSheetConfig(
actions = listOf(
TokenActionButtonConfig(

View file

@ -0,0 +1,38 @@
package com.tangem.feature.wallet.presentation.preview
import com.tangem.core.ui.extensions.combinedReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.styledStringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletBalanceUM
internal object WalletBalancePreview {
val content: WalletBalanceUM.Content = WalletBalanceUM.Content(
id = UserWalletId("0"),
name = "My Wallet",
balance = combinedReference(
stringReference("1,234"),
styledStringReference(
".56",
{
TangemTheme.typography2.headingRegular28.toSpanStyle()
},
),
stringReference(" $"),
),
isBalanceFlickering = false,
isZeroBalance = false,
)
val loading: WalletBalanceUM.Loading = WalletBalanceUM.Loading(
id = UserWalletId("1"),
name = "My Wallet",
)
val error: WalletBalanceUM.Error = WalletBalanceUM.Error(
id = UserWalletId("2"),
name = "My Wallet",
)
}

View file

@ -1,51 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.state.model
import androidx.annotation.DrawableRes
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.feature.wallet.impl.R
/**
* Wallet bottom sheet config
*
[REDACTED_AUTHOR]
*/
sealed class WalletBottomSheetConfig(
open val title: TextReference,
open val subtitle: TextReference,
@DrawableRes open val iconResId: Int,
val primaryButtonConfig: ButtonConfig,
val secondaryButtonConfig: ButtonConfig,
) : TangemBottomSheetConfigContent {
data class ButtonConfig(
val text: TextReference,
val onClick: () -> Unit,
@DrawableRes val iconResId: Int? = null,
)
data class UnlockWallets(val onUnlockClick: () -> Unit, val onScanClick: () -> Unit) : WalletBottomSheetConfig(
title = resourceReference(id = R.string.common_access_denied),
subtitle = resourceReference(
id = R.string.unlock_wallet_description_full,
formatArgs = wrappedList(
resourceReference(R.string.common_biometrics),
),
),
iconResId = R.drawable.ic_locked_24,
primaryButtonConfig = ButtonConfig(
text = resourceReference(
id = R.string.user_wallet_list_unlock_all_with,
formatArgs = wrappedList(resourceReference(R.string.common_biometrics)),
),
onClick = onUnlockClick,
),
secondaryButtonConfig = ButtonConfig(
text = resourceReference(id = R.string.welcome_unlock_card),
onClick = onScanClick,
iconResId = R.drawable.ic_tangem_24,
),
)
}

View file

@ -1,22 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import kotlin.reflect.KClass
internal abstract class TypedWalletStateTransformer<S : WalletState>(
userWalletId: UserWalletId,
protected val targetStateClass: KClass<S>,
) : WalletStateTransformer(userWalletId) {
abstract fun transformTyped(prevState: S): WalletState
@Suppress("UNCHECKED_CAST")
final override fun transform(prevState: WalletState): WalletState {
return if (prevState::class == targetStateClass) {
transformTyped(prevState as S)
} else {
prevState
}
}
}

View file

@ -720,7 +720,6 @@ internal fun LazyListScope.nftCollections(state: WalletState, itemModifier: Modi
private fun ShowBottomSheet(bottomSheetConfig: TangemBottomSheetConfig?) {
if (bottomSheetConfig != null) {
when (bottomSheetConfig.content) {
is WalletBottomSheetConfig -> WalletBottomSheet(config = bottomSheetConfig)
is ActionsBottomSheetConfig -> TokenActionsBottomSheet(config = bottomSheetConfig)
is ChooseAddressBottomSheetConfig -> ChooseAddressBottomSheet(config = bottomSheetConfig)
is ExpressStatusBottomSheetConfig -> ExpressStatusBottomSheet(config = bottomSheetConfig)

View file

@ -0,0 +1,440 @@
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.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
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.rememberPagerState
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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.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.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.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.feature.wallet.presentation.wallet.state.model.NOT_INITIALIZED_WALLET_INDEX
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 kotlinx.coroutines.launch
@OptIn(ExperimentalDecomposeApi::class)
@Composable
internal fun WalletScreen2(
state: WalletScreenState,
bottomSheetContent: @Composable (() -> Unit),
bottomSheetHeaderHeightProvider: () -> Dp,
onBottomSheetStateChange: (BottomSheetState) -> Unit,
) {
// It means that screen is still initializing
if (state.selectedWalletIndex == NOT_INITIALIZED_WALLET_INDEX) return
val walletsListState = rememberLazyListState(initialFirstVisibleItemIndex = state.selectedWalletIndex)
val snackbarHostState = remember(::SnackbarHostState)
val isAutoScroll = remember { mutableStateOf(value = false) }
WalletContent2(
state = state,
walletsListState = walletsListState,
snackbarHostState = snackbarHostState,
isAutoScroll = isAutoScroll,
onAutoScrollReset = { isAutoScroll.value = false },
bottomSheetContent = bottomSheetContent,
bottomSheetHeaderHeightProvider = bottomSheetHeaderHeightProvider,
onBottomSheetStateChange = onBottomSheetStateChange,
)
WalletEventEffect(
walletsListState = walletsListState,
snackbarHostState = snackbarHostState,
event = state.event,
onAutoScrollSet = { isAutoScroll.value = true },
)
}
@OptIn(ExperimentalMaterial3Api::class, ExperimentalDecomposeApi::class)
@Suppress("LongMethod", "LongParameterList", "UnusedPrivateMember")
@Composable
private fun WalletContent2(
state: WalletScreenState,
walletsListState: LazyListState,
snackbarHostState: SnackbarHostState,
isAutoScroll: State<Boolean>,
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 statusBarHeight = with(LocalDensity.current) { WindowInsets.statusBars.getBottom(this).toDp() }
val listState = rememberLazyListState()
val partialCollapsedHeight = 64.dp + statusBarHeight
val scaffoldContent: @Composable (PaddingValues?) -> Unit = { _ ->
val pagerState = rememberPagerState(
initialPage = state.selectedWalletIndex,
pageCount = { state.wallets2.size },
)
LaunchedEffect(pagerState.currentPage) {
if (pagerState.currentPage != state.selectedWalletIndex) {
state.onWalletChange(pagerState.currentPage, false)
}
}
}
BaseScaffoldWithMarkets(
state = state,
listState = listState,
snackbarHostState = snackbarHostState,
bottomSheetHeaderHeightProvider = bottomSheetHeaderHeightProvider,
onBottomSheetStateChange = onBottomSheetStateChange,
bottomSheetContent = bottomSheetContent,
content = scaffoldContent,
)
}
@Suppress("LongParameterList", "LongMethod", "CyclomaticComplexMethod", "UnusedPrivateMember")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private inline fun BaseScaffoldWithMarkets(
state: WalletScreenState,
snackbarHostState: SnackbarHostState,
listState: LazyListState,
bottomSheetHeaderHeightProvider: () -> Dp,
modifier: Modifier = Modifier,
noinline onBottomSheetStateChange: (BottomSheetState) -> Unit,
crossinline bottomSheetContent: @Composable () -> Unit,
crossinline content: @Composable (PaddingValues) -> Unit,
) {
val bottomSheetState = rememberTangemStandardBottomSheetState()
val isPowerSaving by LocalPowerSavingState.current.isPowerSavingModeEnabled.collectAsStateWithLifecycle()
val isKeyboardVisible by rememberIsKeyboardVisible()
val scaffoldState = rememberTangemBottomSheetScaffoldState(
bottomSheetState = bottomSheetState,
snackbarHostState = snackbarHostState,
)
val density = LocalDensity.current
val bottomBarHeight = with(density) { WindowInsets.systemBars.getBottom(density = this).toDp() }
val statusBarHeight = with(density) { WindowInsets.statusBars.getTop(density = this).toDp() }
val peekHeight = bottomSheetHeaderHeightProvider() + handComposableComponentHeight + bottomBarHeight
val maxHeight = LocalWindowSize.current.height
val coroutineScope = rememberCoroutineScope()
val background = if (state.isNewMarketEnabled) {
TangemTheme.colors.background.tertiary
} else {
TangemTheme.colors.background.primary
}
CompositionLocalProvider(
LocalMainBottomSheetColor provides remember(background) { mutableStateOf(background) },
) {
val backgroundColor = LocalMainBottomSheetColor.current
var isSearchFieldFocused by remember { mutableStateOf(false) }
val isNavBarVisible = remember { mutableStateOf(true) }
BottomSheetStateEffects(
bottomSheetState = bottomSheetState,
onBottomSheetStateChange = onBottomSheetStateChange,
navigationBarVisible = isNavBarVisible,
isSearchFieldFocused = isSearchFieldFocused,
)
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)
.navigationBarsPadding(),
)
},
containerColor = Color.Unspecified,
sheetContainerColor = backgroundColor.value,
scaffoldState = scaffoldState,
sheetPeekHeight = peekHeight,
sheetShape = TangemTheme.shapes.bottomSheetLarge,
sheetContent = {
// hide bottom sheet when back pressed
BackHandler(
isKeyboardVisible.not() &&
bottomSheetState.currentValue == TangemSheetValue.Expanded,
) {
coroutineScope.launch { bottomSheetState.partialExpand() }
}
Column(
modifier = Modifier
// expand bottom sheet when clicked on the header
.clickable(
enabled = bottomSheetState.currentValue == TangemSheetValue.PartiallyExpanded,
indication = null,
interactionSource = null,
) {
coroutineScope.launch { bottomSheetState.expand() }
}
.sizeIn(maxHeight = maxHeight - statusBarHeight),
) {
Hand(Modifier.drawBehind { drawRect(backgroundColor.value) })
Box(
modifier = Modifier
.onFocusChanged {
isSearchFieldFocused = it.isFocused
},
) {
bottomSheetContent()
}
}
},
content = { paddingValues ->
Box {
Column(
modifier = Modifier.hazeSourceTangem(-1f),
) {
content(paddingValues)
}
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()
},
)
}
},
)
AnimatedVisibility(
modifier = Modifier.align(Alignment.BottomCenter),
visible = isNavBarVisible.value,
) {
Box(
Modifier
.align(Alignment.BottomCenter)
.background(backgroundColor.value)
.height(bottomBarHeight)
.fillMaxWidth(),
)
}
}
LaunchedEffect(state.showMarketsOnboarding, bottomSheetState.targetValue) {
if (state.showMarketsOnboarding && bottomSheetState.targetValue == TangemSheetValue.Expanded) {
state.onDismissMarketsTooltip()
}
}
}
}
@Composable
private fun BottomSheetScrim(color: Color, visible: Boolean, onDismissRequest: () -> Unit) {
val alpha by animateFloatAsState(
targetValue = if (visible) 1f else 0f,
animationSpec = tween(),
label = "scrim",
)
val dismissSheet = if (visible) {
Modifier
.pointerInput(onDismissRequest) {
detectTapGestures {
onDismissRequest()
}
}
.clearAndSetSemantics {}
} else {
Modifier
}
Canvas(
Modifier
.fillMaxSize()
.then(dismissSheet),
) {
drawRect(color = color, alpha = alpha)
}
}
@Suppress("CyclomaticComplexMethod", "MagicNumber", "LongMethod")
@Composable
private fun BottomSheetStateEffects(
bottomSheetState: TangemSheetState,
navigationBarVisible: MutableState<Boolean>,
onBottomSheetStateChange: (BottomSheetState) -> Unit,
isSearchFieldFocused: Boolean,
) {
LaunchedEffect(bottomSheetState.targetValue) {
when (bottomSheetState.targetValue) {
TangemSheetValue.Hidden,
TangemSheetValue.Expanded,
-> navigationBarVisible.value = false
TangemSheetValue.PartiallyExpanded,
-> navigationBarVisible.value = true
}
}
// expand bottom sheet when keyboard appears
val isKeyboardVisible by rememberIsKeyboardVisible()
LaunchedEffect(isKeyboardVisible) {
if (isKeyboardVisible && isSearchFieldFocused) {
bottomSheetState.expand()
}
}
val keyboardController = LocalSoftwareKeyboardController.current
// hide keyboard when bottom sheet is about to be hidden
LaunchedEffect(Unit) {
snapshotFlow {
bottomSheetState.currentValue == TangemSheetValue.Expanded &&
bottomSheetState.targetValue == TangemSheetValue.PartiallyExpanded
}.collect { sheetHasBeenHidden ->
if (sheetHasBeenHidden) {
keyboardController?.hide()
}
}
}
val isSheetHidden = bottomSheetState.targetValue == TangemSheetValue.PartiallyExpanded
LaunchedEffect(isSheetHidden) {
onBottomSheetStateChange(
if (isSheetHidden) {
BottomSheetState.COLLAPSED
} else {
BottomSheetState.EXPANDED
},
)
}
}
@Composable
private fun WalletSnackbarHost(
snackbarHostState: SnackbarHostState,
event: StateEvent<WalletEvent>,
modifier: Modifier = Modifier,
) {
SnackbarHost(hostState = snackbarHostState, modifier = modifier) { data ->
if (event is StateEvent.Triggered && event.data is WalletEvent.CopyAddress) {
CopiedTextSnackbar(data)
} else {
TangemSnackbar(data)
}
}
}
// region Preview
@OptIn(ExperimentalDecomposeApi::class)
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun WalletScreen2_Preview(@PreviewParameter(WalletScreen2PreviewProvider::class) data: WalletScreenState) {
TangemThemePreviewRedesign {
WalletScreen2(
state = data,
bottomSheetContent = {
Text("Markets Content")
},
bottomSheetHeaderHeightProvider = { 10.dp },
onBottomSheetStateChange = {},
)
}
}
private class WalletScreen2PreviewProvider : PreviewParameterProvider<WalletScreenState> {
override val values: Sequence<WalletScreenState>
get() = sequenceOf(
walletScreenState,
walletScreenState.copy(selectedWalletIndex = 1),
accountScreenState.copy(selectedWalletIndex = 1),
accountScreenWithEmptyTokensState.copy(selectedWalletIndex = 1),
)
}
// endregion

View file

@ -1,141 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components.common
import android.content.res.Configuration
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.PrimaryButtonIconStart
import com.tangem.core.ui.components.SecondaryButton
import com.tangem.core.ui.components.SecondaryButtonIconStart
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletBottomSheetConfig
/**
* Wallet bottom sheet with detail notification information
*
* @param config component config
*
[REDACTED_AUTHOR]
*/
@Composable
internal fun WalletBottomSheet(config: TangemBottomSheetConfig) {
TangemBottomSheet(config) { content: WalletBottomSheetConfig ->
BottomSheetContent(config = content)
}
}
@Composable
private fun BottomSheetContent(config: WalletBottomSheetConfig) {
Column(
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing16)
.padding(top = TangemTheme.dimens.spacing40, bottom = TangemTheme.dimens.spacing16),
verticalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing40),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Icon(
painter = painterResource(id = config.iconResId),
contentDescription = null,
modifier = Modifier.size(size = TangemTheme.dimens.size48),
tint = when (config) {
is WalletBottomSheetConfig.UnlockWallets -> TangemTheme.colors.icon.primary1
},
)
Column(
verticalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing16),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = config.title.resolveReference(),
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
style = TangemTheme.typography.h2,
)
Text(
text = config.subtitle.resolveReference(),
color = TangemTheme.colors.text.secondary,
textAlign = TextAlign.Center,
style = TangemTheme.typography.body2,
)
}
Column(verticalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing10)) {
val buttonModifier = Modifier.fillMaxWidth()
PrimaryButton(config = config.primaryButtonConfig, modifier = buttonModifier)
SecondaryButton(config = config.secondaryButtonConfig, modifier = buttonModifier)
}
}
}
@Composable
private fun PrimaryButton(config: WalletBottomSheetConfig.ButtonConfig, modifier: Modifier = Modifier) {
if (config.iconResId == null) {
PrimaryButton(
text = config.text.resolveReference(),
onClick = config.onClick,
modifier = modifier,
)
} else {
PrimaryButtonIconStart(
text = config.text.resolveReference(),
iconResId = config.iconResId,
onClick = config.onClick,
modifier = modifier,
)
}
}
@Composable
private fun SecondaryButton(config: WalletBottomSheetConfig.ButtonConfig, modifier: Modifier = Modifier) {
if (config.iconResId == null) {
SecondaryButton(
text = config.text.resolveReference(),
onClick = config.onClick,
modifier = modifier,
)
} else {
SecondaryButtonIconStart(
text = config.text.resolveReference(),
iconResId = config.iconResId,
onClick = config.onClick,
modifier = modifier,
)
}
}
// region Preview
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun WalletBottomSheetContent_Preview(
@PreviewParameter(WalletBottomSheetConfigProvider::class)
config: WalletBottomSheetConfig,
) {
TangemThemePreview {
// Use preview of content because ModalBottomSheet isn't supported in Preview mode
BottomSheetContent(config = config)
}
}
private class WalletBottomSheetConfigProvider : CollectionPreviewParameterProvider<WalletBottomSheetConfig>(
collection = listOf(WalletPreviewData.bottomSheet.content as WalletBottomSheetConfig),
)
// endregion