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

@ -192,6 +192,11 @@
<string name="exchange_tokens_unavailable_tokens_header">Недоступен для обмена с %s</string>
<string name="express_choose_providers_subtitle">Провайдеры проводят транзакции, обеспечивая плавный и эффективный обмен токенами</string>
<string name="express_choose_providers_title">Выберите провайдера</string>
<string name="express_exchange_notification_failed_text">Чтобы узнать причину, посетите сайт провайдера</string>
<string name="express_exchange_notification_failed_title">Операция не выполнена провайдером</string>
<string name="express_exchange_notification_verification_text">Посетите сайт провайдера для проверки</string>
<string name="express_exchange_notification_verification_title">Провайдер: требуется верификация</string>
<string name="express_provider">Провайдер</string>
<string name="express_provider_best_rate">Лучший курс</string>
<string name="express_provider_permission_needed">Требуется разрешение</string>
<string name="feedback_data_collection_message">Информация ниже не является обязательной. Вы можете стереть её, если хотите.</string>
@ -266,7 +271,7 @@
<string name="onboarding_access_codes_doesnt_match">Введенные коды доступа не совпадают</string>
<string name="onboarding_alert_message_not_max_backup_cards_added">Вы добавили одну резервную карту. После того, как процесс будет завершен, Вы больше не сможете добавить карт. Если у Вас есть еще одна карта, добавьте ее в резервную копию. Хотите продолжить?</string>
<string name="onboarding_backup_exit_warning">Процесс резервного копирования почти завершен. Вы не можете выйти из него сейчас.</string>
<string name="onboarding_balance_title">Баланс</string>
<string name="common_balance_title">Баланс</string>
<string name="onboarding_button_add_backup_card">Добавить резервную карту</string>
<string name="onboarding_button_backup_card_format">Сканировать карту #%d</string>
<string name="onboarding_button_backup_now">Создать резервную копию</string>

View file

@ -178,7 +178,7 @@
<string name="onboarding_access_codes_doesnt_match">輸入的訪問密碼與初始訪問密碼不匹配</string>
<string name="onboarding_alert_message_not_max_backup_cards_added">您已添加一張備用卡。備份過程完成後,您將無法添加更多備份卡。如果您還有一張卡,請將其添加到備份中。您想繼續備份過程嗎?</string>
<string name="onboarding_backup_exit_warning">備份過程已部分完成。你現在不能退出</string>
<string name="onboarding_balance_title">餘額</string>
<string name="common_balance_title">餘額</string>
<string name="onboarding_button_add_backup_card">添加備用卡</string>
<string name="onboarding_button_backup_card_format">掃描卡片 #%d</string>
<string name="onboarding_button_backup_now">立即備份</string>

View file

@ -192,7 +192,30 @@
<string name="exchange_tokens_unavailable_tokens_header">Unavailable for swap from %s</string>
<string name="express_choose_providers_subtitle">Providers facilitate transactions, ensuring smooth and efficient token exchanges</string>
<string name="express_choose_providers_title">Choose Provider</string>
<string name="express_provider_best_rate">Best Rate</string>
<string name="express_estimated_amount">Estimated amount</string>
<string name="express_exchange_notification_failed_text">Visit providers website to see why</string>
<string name="express_exchange_notification_failed_title">Operation failed by provider</string>
<string name="express_exchange_notification_verification_text">Visit providers website for verification</string>
<string name="express_exchange_notification_verification_title">Provider: Verification needed</string>
<string name="express_exchange_status_confirmed">Confirmed</string>
<string name="express_exchange_status_confirming">Confirming</string>
<string name="express_exchange_status_exchanged">Exchanged</string>
<string name="express_exchange_status_exchanging">Exchanging</string>
<string name="express_exchange_status_failed">Failed</string>
<string name="express_exchange_status_received">Deposit received</string>
<string name="express_exchange_status_receiving">Awaiting deposit</string>
<string name="express_exchange_status_sending">Sending to you</string>
<string name="express_exchange_status_sent">Sent</string>
<string name="express_exchange_status_subtitle">Provider-sourced data. Estimated amount subject to change.</string>
<string name="express_exchange_status_title">Exchange status</string>
<string name="express_exchange_status_verified">Verified</string>
<string name="express_exchange_status_verifying">Verification required</string>
<string name="express_fetch_best_rates">Fetching best rates...</string>
<string name="express_floating_rate">Floating rate</string>
<string name="express_go_to_provider">Go to provider</string>
<string name="express_provider">Provider</string>
<string name="express_provider_best_rate">Best rate</string>
<string name="express_exchange_by">Exchange by %s</string>
<string name="express_provider_permission_needed">Permission Needed</string>
<string name="feedback_data_collection_message">The following information is optional. You can erase it if you don\'t want to share it.</string>
<string name="feedback_preface_rate_negative">Tell us what functions you are missing, and we will try to help you.</string>
@ -267,7 +290,7 @@
<string name="onboarding_access_codes_doesnt_match">Entered access code didn\'t match the initial access code</string>
<string name="onboarding_alert_message_not_max_backup_cards_added">You\'ve added one backup card. When backup process is finished you can\'t add more backup cards. If you have one more card, add it to backup. Do you like to continue the backup process?</string>
<string name="onboarding_backup_exit_warning">The backup process is partly complete. You can\'t exit it now.</string>
<string name="onboarding_balance_title">Balance</string>
<string name="common_balance_title">Balance</string>
<string name="onboarding_button_add_backup_card">Add a backup card</string>
<string name="onboarding_button_backup_card_format">Scan the card #%d</string>
<string name="onboarding_button_backup_now">Backup now</string>

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>