Updated on 2026-08-14

This commit is contained in:
Tangem 2023-11-24 18:28:09 +03:00
parent 464f8f8a80
commit d10cf0ac2e
23 changed files with 1117 additions and 10 deletions

View file

@ -6,6 +6,7 @@ import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.SheetState
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.*
import androidx.compose.ui.graphics.Color
import com.tangem.core.ui.res.TangemTheme
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
@ -20,6 +21,7 @@ import kotlinx.coroutines.launch
@Composable
inline fun <reified T : TangemBottomSheetConfigContent> TangemBottomSheet(
config: TangemBottomSheetConfig,
color: Color = TangemTheme.colors.background.primary,
crossinline content: @Composable ColumnScope.(T) -> Unit,
) {
var isVisible by remember { mutableStateOf(value = config.isShow) }
@ -29,9 +31,9 @@ inline fun <reified T : TangemBottomSheetConfigContent> TangemBottomSheet(
ModalBottomSheet(
onDismissRequest = config.onDismissRequest,
sheetState = sheetState,
containerColor = TangemTheme.colors.background.primary,
containerColor = color,
shape = TangemTheme.shapes.bottomSheetLarge,
dragHandle = { TangemBottomSheetDraggableHeader() },
dragHandle = { TangemBottomSheetDraggableHeader(color) },
) {
content(config.content)
}

View file

@ -0,0 +1,150 @@
package com.tangem.core.ui.components.inputrow
import androidx.compose.foundation.background
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.tooling.preview.Preview
import com.tangem.core.ui.R
import com.tangem.core.ui.components.currency.tokenicon.TokenIcon
import com.tangem.core.ui.components.currency.tokenicon.TokenIconState
import com.tangem.core.ui.components.inputrow.inner.DividerContainer
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
/**
* [Input Row Approx](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=2207-810&mode=design&t=fM1ZU6zQF6g3CaTv-4)
*
* @param leftIcon left token state
* @param leftTitle left token title
* @param leftSubtitle left token subtitle
* @param rightIcon right token state
* @param rightTitle right token title
* @param rightSubtitle right token subtitle
* @param modifier composable modifier
* @param showDivider show divider
*/
@Suppress("LongParameterList")
@Composable
fun InputRowApprox(
leftIcon: TokenIconState,
leftTitle: TextReference,
leftSubtitle: TextReference,
rightIcon: TokenIconState,
rightTitle: TextReference,
rightSubtitle: TextReference,
modifier: Modifier = Modifier,
showDivider: Boolean = false,
) {
DividerContainer(
showDivider = showDivider,
modifier = modifier,
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier
.padding(TangemTheme.dimens.spacing12)
.fillMaxWidth(),
) {
InputRowApproxItem(
iconState = leftIcon,
title = leftTitle,
subtitle = leftSubtitle,
)
Icon(
painter = painterResource(id = R.drawable.ic_approx_24),
contentDescription = null,
tint = TangemTheme.colors.text.tertiary,
modifier = Modifier
.padding(
horizontal = TangemTheme.dimens.spacing8,
vertical = TangemTheme.dimens.spacing10,
),
)
InputRowApproxItem(
iconState = rightIcon,
title = rightTitle,
subtitle = rightSubtitle,
)
}
}
}
@Composable
private fun InputRowApproxItem(
iconState: TokenIconState,
title: TextReference,
subtitle: TextReference,
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier,
) {
TokenIcon(
state = iconState,
modifier = Modifier
.size(TangemTheme.dimens.size36),
)
Column(
modifier = Modifier
.padding(
start = TangemTheme.dimens.spacing12,
),
) {
Text(
text = title.resolveReference(),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.primary1,
)
Text(
text = subtitle.resolveReference(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
modifier = Modifier
.padding(top = TangemTheme.dimens.spacing2),
)
}
}
}
//region Preview
@Preview
@Composable
private fun InputRowApproxPreview_Light() {
TangemTheme {
InputRowApprox(
leftIcon = TokenIconState.Loading,
leftTitle = TextReference.Str("Left title"),
leftSubtitle = TextReference.Str("Left subtitle"),
rightIcon = TokenIconState.Loading,
rightTitle = TextReference.Str("Right title"),
rightSubtitle = TextReference.Str("Right subtitle"),
modifier = Modifier
.background(TangemTheme.colors.background.action),
)
}
}
@Preview
@Composable
private fun InputRowApproxPreview_Dark() {
TangemTheme(isDark = true) {
InputRowApprox(
leftIcon = TokenIconState.Loading,
leftTitle = TextReference.Str("Left title"),
leftSubtitle = TextReference.Str("Left subtitle"),
rightIcon = TokenIconState.Loading,
rightTitle = TextReference.Str("Right title"),
rightSubtitle = TextReference.Str("Right subtitle"),
modifier = Modifier
.background(TangemTheme.colors.background.action),
)
}
}
//endregion

View file

@ -0,0 +1,221 @@
package com.tangem.core.ui.components.inputrow
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import coil.compose.SubcomposeAsyncImage
import coil.request.ImageRequest
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SpacerWMax
import com.tangem.core.ui.components.currency.tokenicon.LoadingIcon
import com.tangem.core.ui.components.inputrow.inner.DividerContainer
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
/**
* [Input Row Best Rate](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=2100-889&mode=dev)
*
* @param imageUrl image source url
* @param title title
* @param titleExtra title extra
* @param subtitle subtitle
* @param modifier composable modifier
* @param showTag show tag
* @param showDivider show divider
* @param onIconClick icon click
*/
@Composable
fun InputRowBestRate(
imageUrl: String,
title: TextReference,
titleExtra: TextReference,
subtitle: TextReference,
modifier: Modifier = Modifier,
showTag: Boolean = false,
showDivider: Boolean = false,
onIconClick: (() -> Unit)? = null,
) {
DividerContainer(
showDivider = showDivider,
modifier = modifier,
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.padding(TangemTheme.dimens.spacing12),
) {
InnerIcon(imageUrl = imageUrl)
Column(
modifier = Modifier
.padding(start = TangemTheme.dimens.spacing12),
) {
InnerTitle(
title = title,
titleExtra = titleExtra,
showTag = showTag,
)
Text(
text = subtitle.resolveReference(),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.tertiary,
modifier = Modifier.padding(top = TangemTheme.dimens.spacing8),
)
}
SpacerWMax()
onIconClick?.let {
Icon(
painter = painterResource(id = R.drawable.ic_chevron_right_24),
contentDescription = null,
tint = TangemTheme.colors.icon.informative,
modifier = Modifier
.padding(vertical = TangemTheme.dimens.spacing10)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = rememberRipple(bounded = false),
onClick = onIconClick,
),
)
}
}
}
}
@Composable
private fun InnerTitle(title: TextReference, titleExtra: TextReference, showTag: Boolean = false) {
Row {
Text(
text = title.resolveReference(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.primary1,
)
Text(
text = titleExtra.resolveReference(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
modifier = Modifier
.padding(start = TangemTheme.dimens.spacing4),
)
if (showTag) {
Text(
text = stringResource(R.string.express_provider_best_rate),
style = TangemTheme.typography.caption1,
color = TangemTheme.colors.icon.accent,
modifier = Modifier
.padding(start = TangemTheme.dimens.spacing4)
.background(
color = TangemTheme.colors.icon.accent.copy(alpha = 0.1f),
shape = RoundedCornerShape(TangemTheme.dimens.radius20),
)
.padding(horizontal = TangemTheme.dimens.spacing6),
)
}
}
}
@Composable
private fun InnerIcon(imageUrl: String) {
SubcomposeAsyncImage(
modifier = Modifier.size(TangemTheme.dimens.size40),
model = ImageRequest.Builder(context = LocalContext.current)
.data(imageUrl)
.crossfade(enable = true)
.allowHardware(enable = false)
.build(),
loading = { LoadingIcon() },
error = {
Box(
modifier = Modifier
.background(
color = TangemTheme.colors.background.tertiary,
shape = CircleShape,
),
)
},
contentDescription = null,
)
}
//region preview
@Preview
@Composable
private fun InputRowBestRatePreview_Light(
@PreviewParameter(InputRowBestRatePreviewDataProvider::class) data: InputRowBestRatePreviewData,
) {
TangemTheme {
InputRowBestRate(
imageUrl = "",
title = data.title,
titleExtra = data.titleExtra,
subtitle = data.subtitle,
showTag = data.showTag,
onIconClick = data.iconClick,
modifier = Modifier
.background(TangemTheme.colors.background.action),
)
}
}
@Preview
@Composable
private fun InputRowBestRatePreview_Dark(
@PreviewParameter(InputRowBestRatePreviewDataProvider::class) data: InputRowBestRatePreviewData,
) {
TangemTheme(isDark = true) {
InputRowBestRate(
imageUrl = "",
title = data.title,
titleExtra = data.titleExtra,
subtitle = data.subtitle,
showTag = data.showTag,
onIconClick = data.iconClick,
modifier = Modifier
.background(TangemTheme.colors.background.action),
)
}
}
private data class InputRowBestRatePreviewData(
val title: TextReference,
val titleExtra: TextReference,
val showTag: Boolean,
val subtitle: TextReference,
val iconClick: (() -> Unit)?,
)
private class InputRowBestRatePreviewDataProvider : PreviewParameterProvider<InputRowBestRatePreviewData> {
override val values: Sequence<InputRowBestRatePreviewData>
get() = sequenceOf(
InputRowBestRatePreviewData(
title = TextReference.Str("1inch"),
titleExtra = TextReference.Str("DEX"),
subtitle = TextReference.Str("0,64554846 DAI ≈ 1 MATIC "),
showTag = true,
iconClick = {},
),
InputRowBestRatePreviewData(
title = TextReference.Str("ChangeNow"),
titleExtra = TextReference.Str("CEX"),
subtitle = TextReference.Str("0,64554846 DAI ≈ 1 MATIC "),
showTag = false,
iconClick = null,
),
)
}
//endregion

View file

@ -41,6 +41,7 @@ data class TangemDimens internal constructor(
val size0: Dp = 0.dp,
val size0_5: Dp = 0.5.dp,
val size1: Dp = 1.dp,
val size1_5: Dp = 1.5.dp,
val size2: Dp = 2.dp,
val size4: Dp = 4.dp,
val size5: Dp = 5.dp,

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:pathData="M9.135,2.328L1.702,15.165C1.316,15.832 1.797,16.667 2.568,16.667H17.432C18.202,16.667 18.684,15.832 18.298,15.165L10.865,2.328C10.48,1.663 9.52,1.663 9.135,2.328ZM10.833,11.667H9.167V6.667H10.833V11.667ZM10.833,15H9.167V13.333H10.833V15Z"
android:fillColor="#FFB71B"
android:fillType="evenOdd"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M14.153,11.13C12.544,11.13 11.142,9.695 9.731,9.695C8.625,9.695 7.825,10.153 7.25,10.798V9.076C7.816,8.457 8.652,8 9.847,8C11.456,8 12.867,9.435 14.269,9.435C15.366,9.435 16.175,8.978 16.75,8.332V10.054C16.184,10.673 15.348,11.13 14.153,11.13ZM14.153,16C12.544,16 11.142,14.565 9.731,14.565C8.625,14.565 7.825,15.022 7.25,15.668V13.946C7.816,13.327 8.652,12.87 9.847,12.87C11.456,12.87 12.867,14.305 14.269,14.305C15.366,14.305 16.175,13.856 16.75,13.202V14.924C16.184,15.543 15.348,16 14.153,16Z"
android:fillColor="#909090"/>
</vector>

View file

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M11,13H13V7H11V13Z"
android:fillColor="#FFB71B"/>
<path
android:pathData="M11,17H13V15H11V17Z"
android:fillColor="#FFB71B"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M4.16,11V13H16.16L10.66,18.5L12.08,19.92L20,12L12.08,4.08L10.66,5.5L16.16,11H4.16Z"
android:fillColor="#1E1E1E"/>
</vector>