Updated on 2026-08-14

This commit is contained in:
Tangem 2024-08-20 15:13:03 +03:00
parent a246f92ea7
commit e51c82e615
5 changed files with 262 additions and 60 deletions

View file

@ -1,8 +1,11 @@
package com.tangem.common.ui.userwallet
import android.content.res.Configuration
import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material3.CardColors
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@ -26,6 +29,7 @@ import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.common.ui.R
import com.tangem.core.ui.coil.RotationTransformation
import com.tangem.core.ui.components.block.TangemBlockCardColors
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.res.TangemThemePreview
@ -33,9 +37,14 @@ import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.collections.immutable.persistentListOf
@Composable
fun UserWalletItem(state: UserWalletItemUM, modifier: Modifier = Modifier) {
fun UserWalletItem(
state: UserWalletItemUM,
modifier: Modifier = Modifier,
blockColors: CardColors = TangemBlockCardColors,
) {
BlockCard(
modifier = modifier,
colors = blockColors,
onClick = state.onClick,
enabled = state.isEnabled,
) {
@ -142,6 +151,7 @@ private fun CardImage(imageUrl: String, modifier: Modifier = Modifier) {
}
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun Preview() {
TangemThemePreview {
@ -174,7 +184,12 @@ private fun Preview() {
),
)
Column {
Column(
Modifier
.background(TangemTheme.colors.background.tertiary)
.padding(TangemTheme.dimens.spacing12),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
) {
list.fastForEach { userWalletItemUM ->
UserWalletItem(
modifier = Modifier.fillMaxWidth(),

View file

@ -1,5 +1,10 @@
package com.tangem.core.ui.components.buttons.common
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.Composable
@ -36,6 +41,7 @@ fun TangemButton(
textStyle: TextStyle = TangemTheme.typography.button,
shape: Shape = size.toShape(),
iconPadding: Dp = size.toIconPadding(),
animateContentChange: Boolean = false,
) {
val multipleClickPreventer = remember { MultipleClickPreventer.get() }
@ -56,6 +62,7 @@ fun TangemButton(
buttonIcon = icon,
iconPadding = iconPadding,
showProgress = showProgress,
animateContentChange = animateContentChange,
progressIndicator = {
CircularProgressIndicator(
modifier = Modifier.buttonContentSize(maxContentSize),
@ -108,24 +115,54 @@ private inline fun RowScope.ButtonContentContainer(
buttonIcon: TangemButtonIconPosition,
iconPadding: Dp,
showProgress: Boolean,
animateContentChange: Boolean,
progressIndicator: @Composable RowScope.() -> Unit,
text: @Composable RowScope.() -> Unit,
icon: @Composable RowScope.(Int) -> Unit,
crossinline text: @Composable RowScope.() -> Unit,
crossinline icon: @Composable RowScope.(Int) -> Unit,
additionalText: @Composable () -> Unit,
) {
if (showProgress) {
progressIndicator()
} else {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Row(horizontalArrangement = Arrangement.Center) {
if (buttonIcon is TangemButtonIconPosition.Start) {
icon(buttonIcon.iconResId)
Spacer(modifier = Modifier.requiredWidth(iconPadding))
if (animateContentChange) {
AnimatedContent(
targetState = buttonIcon,
transitionSpec = {
fadeIn(tween(durationMillis = 220)) togetherWith
fadeOut(tween(durationMillis = 220))
},
label = "button text with icon",
) { iconState ->
Row(horizontalArrangement = Arrangement.Center) {
when (iconState) {
is TangemButtonIconPosition.Start -> {
icon(iconState.iconResId)
Spacer(modifier = Modifier.requiredWidth(iconPadding))
text()
}
is TangemButtonIconPosition.End -> {
text()
Spacer(modifier = Modifier.requiredWidth(iconPadding))
icon(iconState.iconResId)
}
is TangemButtonIconPosition.None -> {
text()
}
}
}
}
text()
if (buttonIcon is TangemButtonIconPosition.End) {
Spacer(modifier = Modifier.requiredWidth(iconPadding))
icon(buttonIcon.iconResId)
} else {
Row(horizontalArrangement = Arrangement.Center) {
if (buttonIcon is TangemButtonIconPosition.Start) {
icon(buttonIcon.iconResId)
Spacer(modifier = Modifier.requiredWidth(iconPadding))
}
text()
if (buttonIcon is TangemButtonIconPosition.End) {
Spacer(modifier = Modifier.requiredWidth(iconPadding))
icon(buttonIcon.iconResId)
}
}
}
additionalText()

View file

@ -2,29 +2,36 @@ package com.tangem.features.markets.portfolio.impl.ui
import android.content.res.Configuration
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.util.fastForEachIndexed
import com.tangem.common.ui.userwallet.UserWalletItem
import com.tangem.core.ui.components.PrimaryButtonIconEnd
import com.tangem.core.ui.components.SpacerW12
import com.tangem.core.ui.components.SpacerW6
import com.tangem.core.ui.components.TangemSwitch
import com.tangem.core.ui.components.*
import com.tangem.core.ui.components.block.TangemBlockCardColors
import com.tangem.core.ui.components.block.information.InformationBlock
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.buttons.common.TangemButton
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
import com.tangem.core.ui.components.buttons.common.TangemButtonSize
import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults
import com.tangem.core.ui.components.currency.icon.CoinIcon
import com.tangem.core.ui.components.rows.ArrowRow
import com.tangem.core.ui.components.rows.BlockchainRow
@ -36,18 +43,18 @@ import com.tangem.features.markets.impl.R
import com.tangem.features.markets.portfolio.impl.ui.preview.PreviewAddToPortfolioBSContentProvider
import com.tangem.features.markets.portfolio.impl.ui.state.AddToPortfolioBSContentUM
import com.tangem.features.markets.portfolio.impl.ui.state.SelectNetworkUM
import kotlinx.coroutines.delay
@Composable
internal fun AddToPortfolioBottomSheet(config: TangemBottomSheetConfig) {
TangemBottomSheet<AddToPortfolioBSContentUM>(
config = config,
containerColor = TangemTheme.colors.background.tertiary,
addBottomInsets = false,
titleText = resourceReference(R.string.markets_add_to_portfolio_button),
) {
Content(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = TangemTheme.dimens.spacing16),
modifier = Modifier.fillMaxWidth(),
state = config.content as AddToPortfolioBSContentUM,
)
}
@ -55,33 +62,101 @@ internal fun AddToPortfolioBottomSheet(config: TangemBottomSheetConfig) {
@Composable
private fun Content(state: AddToPortfolioBSContentUM, modifier: Modifier = Modifier) {
Column(
modifier = modifier.padding(horizontal = TangemTheme.dimens.spacing16),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
) {
UserWalletItem(state.selectedWallet)
var continueButtonAreaHeight by remember { mutableIntStateOf(0) }
val density = LocalDensity.current
val scrollState = rememberScrollState()
NetworkSelection(
modifier = Modifier.fillMaxWidth(),
state = state.selectNetworkUM,
)
AnimatedVisibility(
visible = state.isScanCardNotificationVisible,
modifier = Modifier.fillMaxWidth(),
Box(modifier = modifier) {
Column(
modifier = Modifier
.verticalScroll(state = scrollState)
.padding(horizontal = TangemTheme.dimens.spacing16),
) {
ScanWalletWarning(modifier = Modifier.fillMaxWidth())
UserWalletItem(
state = state.selectedWallet,
blockColors = TangemBlockCardColors.copy(
containerColor = TangemTheme.colors.background.action,
disabledContainerColor = TangemTheme.colors.background.action,
),
)
SpacerH12()
NetworkSelection(
modifier = Modifier.fillMaxWidth(),
state = state.selectNetworkUM,
)
SpacerH12()
AnimatedVisibility(
visible = state.isScanCardNotificationVisible,
modifier = Modifier.fillMaxWidth(),
) {
Column {
ScanWalletWarning(modifier = Modifier.fillMaxWidth())
SpacerH12()
}
// Scroll to the bottom when the notification appears and the scroll is at the bottom
LaunchedEffect(Unit) {
if (scrollState.canScrollForward.not()) {
delay(timeMillis = 500)
scrollState.animateScrollTo(scrollState.maxValue)
}
}
}
SpacerH(with(density) { continueButtonAreaHeight.toDp() })
}
PrimaryButtonIconEnd(
modifier = Modifier.fillMaxWidth(),
text = stringResource(R.string.common_continue),
iconResId = R.drawable.ic_tangem_24,
onClick = {},
AnimatedVisibility(
visible = scrollState.canScrollForward,
enter = fadeIn(),
exit = fadeOut(),
modifier = Modifier.align(Alignment.BottomCenter),
) {
BottomFade(Modifier.align(Alignment.BottomCenter))
}
ContinueButton(
modifier = Modifier
.align(Alignment.BottomCenter)
.onGloballyPositioned {
continueButtonAreaHeight = it.size.height
},
enabled = state.continueButtonEnabled,
onClick = state.onContinueButtonClick,
)
}
}
@Composable
private fun ContinueButton(enabled: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier) {
TangemButton(
enabled = enabled,
modifier = modifier
.padding(
start = TangemTheme.dimens.spacing16,
end = TangemTheme.dimens.spacing16,
bottom = TangemTheme.dimens.spacing16,
)
.navigationBarsPadding()
.fillMaxWidth(),
text = stringResource(R.string.common_continue),
icon = if (enabled) {
TangemButtonIconPosition.End(R.drawable.ic_tangem_24)
} else {
TangemButtonIconPosition.None
},
showProgress = false,
size = TangemButtonSize.Default,
colors = TangemButtonsDefaults.primaryButtonColors,
onClick = onClick,
animateContentChange = true,
)
}
@Suppress("LongMethod")
@Composable
private fun NetworkSelection(state: SelectNetworkUM, modifier: Modifier = Modifier) {
@ -95,10 +170,7 @@ private fun NetworkSelection(state: SelectNetworkUM, modifier: Modifier = Modifi
)
},
) {
Column(
modifier = Modifier
.verticalScroll(rememberScrollState()),
) {
Column {
Row(
modifier = Modifier
.fillMaxWidth()
@ -180,6 +252,7 @@ private fun ScanWalletWarning(modifier: Modifier = Modifier) {
Icon(
modifier = Modifier.requiredSize(TangemTheme.dimens.size20),
imageVector = ImageVector.vectorResource(R.drawable.ic_tangem_24),
tint = TangemTheme.colors.icon.primary1,
contentDescription = null,
)
Text(
@ -237,4 +310,53 @@ private fun PreviewContentRtl(
state = content,
)
}
}
// For on device testing
@Composable
@Preview
private fun PreviewContentTestOnDevice(
@PreviewParameter(PreviewAddToPortfolioBSContentProvider::class) content: AddToPortfolioBSContentUM,
) {
TangemThemePreview(
alwaysShowBottomSheets = false,
) {
var isShow by remember { mutableStateOf(false) }
var contentState by remember {
mutableStateOf(content)
}
LaunchedEffect(Unit) {
contentState = content.copy(
onContinueButtonClick = {
contentState = contentState.copy(
isScanCardNotificationVisible = !contentState.isScanCardNotificationVisible,
)
},
continueButtonEnabled = true,
selectedWallet = content.selectedWallet.copy(
onClick = {
contentState = contentState.copy(
continueButtonEnabled = !contentState.continueButtonEnabled,
)
},
),
)
}
AddToPortfolioBottomSheet(
config = TangemBottomSheetConfig(
isShow = isShow,
content = contentState,
onDismissRequest = { isShow = false },
),
)
Button(
onClick = { isShow = !isShow },
) {
Text(text = "Toggle")
}
}
}

View file

@ -12,6 +12,14 @@ import kotlinx.collections.immutable.persistentListOf
internal class PreviewAddToPortfolioBSContentProvider : PreviewParameterProvider<AddToPortfolioBSContentUM> {
private val blockchainRow = BlockchainRowUM(
name = "Etherium 3",
type = "TEST",
iconResId = R.drawable.ic_eth_16,
isMainNetwork = false,
isSelected = false,
)
override val values: Sequence<AddToPortfolioBSContentUM>
get() = sequenceOf(
AddToPortfolioBSContentUM(
@ -29,32 +37,50 @@ internal class PreviewAddToPortfolioBSContentProvider : PreviewParameterProvider
tokenName = "Etherium",
tokenCurrencySymbol = "ETH",
networks = persistentListOf(
BlockchainRowUM(
name = "Etherium",
blockchainRow.copy(
type = "MAIN",
iconResId = R.drawable.ic_eth_16,
isMainNetwork = true,
isSelected = true,
),
BlockchainRowUM(
name = "Etherium 2",
type = "TEST",
iconResId = R.drawable.ic_eth_16,
isMainNetwork = false,
isSelected = false,
),
BlockchainRowUM(
name = "Etherium 3",
type = "TEST",
iconResId = R.drawable.ic_eth_16,
isMainNetwork = false,
isSelected = false,
),
blockchainRow,
blockchainRow,
),
onNetworkSwitchClick = { _, _ -> },
iconUrl = null,
),
isScanCardNotificationVisible = true,
continueButtonEnabled = true,
onContinueButtonClick = {},
),
AddToPortfolioBSContentUM(
selectedWallet = UserWalletItemUM(
id = UserWalletId("1"),
name = stringReference("Wallet 1"),
information = stringReference("3 cards, 10,123$"),
imageUrl = "",
isEnabled = true,
endIcon = UserWalletItemUM.EndIcon.Arrow,
onClick = {},
),
selectNetworkUM = SelectNetworkUM(
tokenId = "etherium",
tokenName = "Etherium",
tokenCurrencySymbol = "ETH",
networks = persistentListOf(
blockchainRow.copy(
type = "MAIN",
isMainNetwork = true,
isSelected = true,
),
*Array(25) { blockchainRow },
),
onNetworkSwitchClick = { _, _ -> },
iconUrl = null,
),
isScanCardNotificationVisible = true,
continueButtonEnabled = false,
onContinueButtonClick = {},
),
)
}

View file

@ -7,4 +7,6 @@ internal data class AddToPortfolioBSContentUM(
val selectedWallet: UserWalletItemUM,
val selectNetworkUM: SelectNetworkUM,
val isScanCardNotificationVisible: Boolean,
val continueButtonEnabled: Boolean,
val onContinueButtonClick: () -> Unit,
) : TangemBottomSheetConfigContent