Updated on 2026-08-14
This commit is contained in:
commit
f0d03e3648
286 changed files with 6353 additions and 5898 deletions
|
|
@ -1,44 +1,33 @@
|
|||
package com.tangem.core.ui.components
|
||||
|
||||
import androidx.compose.foundation.LocalIndication
|
||||
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.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.material.RadioButton
|
||||
import androidx.compose.material.RadioButtonDefaults
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.SelctorDialogParamsProvider.SelectorDialogParams
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
/**
|
||||
* Dialog button params
|
||||
*
|
||||
* @param title Button text. If not provided default values will be used
|
||||
* @param warning If true then button text will be in theme warning color
|
||||
* @param enabled If false button will be disabled
|
||||
* @param onClick Button click callback
|
||||
*/
|
||||
data class DialogButton(
|
||||
val title: String? = null,
|
||||
val warning: Boolean = false,
|
||||
val enabled: Boolean = true,
|
||||
val onClick: () -> Unit,
|
||||
)
|
||||
|
||||
/**
|
||||
* Additional params for dialog text field
|
||||
*/
|
||||
data class AdditionalTextInputDialogParams(
|
||||
val label: String? = null,
|
||||
val placeholder: String? = null,
|
||||
val caption: String? = null,
|
||||
val enabled: Boolean = true,
|
||||
val isError: Boolean = false,
|
||||
)
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
/**
|
||||
* Simple alert dialog with a message and 'OK' button
|
||||
|
|
@ -129,6 +118,54 @@ fun TextInputDialog(
|
|||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SelectorDialog(
|
||||
selectedItemIndex: Int,
|
||||
items: ImmutableList<String>,
|
||||
confirmButton: DialogButton,
|
||||
onSelect: (index: Int) -> Unit,
|
||||
onDismissDialog: () -> Unit,
|
||||
title: String? = null,
|
||||
isDismissable: Boolean = true,
|
||||
) {
|
||||
TangemDialog(
|
||||
type = DialogType.Selector(selectedItemIndex, items, onSelect),
|
||||
confirmButton = confirmButton,
|
||||
title = title,
|
||||
onDismissDialog = onDismissDialog,
|
||||
properties = DialogProperties(
|
||||
dismissOnBackPress = isDismissable,
|
||||
dismissOnClickOutside = isDismissable,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Dialog button params
|
||||
*
|
||||
* @param title Button text. If not provided default values will be used
|
||||
* @param warning If true then button text will be in theme warning color
|
||||
* @param enabled If false button will be disabled
|
||||
* @param onClick Button click callback
|
||||
*/
|
||||
data class DialogButton(
|
||||
val title: String? = null,
|
||||
val warning: Boolean = false,
|
||||
val enabled: Boolean = true,
|
||||
val onClick: () -> Unit,
|
||||
)
|
||||
|
||||
/**
|
||||
* Additional params for dialog text field
|
||||
*/
|
||||
data class AdditionalTextInputDialogParams(
|
||||
val label: String? = null,
|
||||
val placeholder: String? = null,
|
||||
val caption: String? = null,
|
||||
val enabled: Boolean = true,
|
||||
val isError: Boolean = false,
|
||||
)
|
||||
|
||||
// region Defaults
|
||||
@Composable
|
||||
private fun TangemDialog(
|
||||
|
|
@ -146,15 +183,18 @@ private fun TangemDialog(
|
|||
shape = TangemTheme.shapes.roundedCornersLarge,
|
||||
color = TangemTheme.colors.background.plain,
|
||||
)
|
||||
.padding(all = TangemTheme.dimens.spacing24),
|
||||
.padding(vertical = TangemTheme.dimens.spacing24),
|
||||
) {
|
||||
if (title != null) {
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing24)
|
||||
.fillMaxWidth(),
|
||||
text = title,
|
||||
style = when (type) {
|
||||
is DialogType.Message -> TangemTheme.typography.h2
|
||||
is DialogType.TextInput -> TangemTheme.typography.h3
|
||||
is DialogType.Selector -> TangemTheme.typography.h2
|
||||
},
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
|
|
@ -162,7 +202,13 @@ private fun TangemDialog(
|
|||
}
|
||||
DialogContent(type = type)
|
||||
SpacerH24()
|
||||
DialogButtons(confirmButton = confirmButton, dismissButton = dismissButton)
|
||||
DialogButtons(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing24)
|
||||
.fillMaxWidth(),
|
||||
confirmButton = confirmButton,
|
||||
dismissButton = dismissButton,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -177,7 +223,9 @@ private fun DialogContent(type: DialogType, modifier: Modifier = Modifier) {
|
|||
when (type) {
|
||||
is DialogType.Message -> {
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing24)
|
||||
.fillMaxWidth(),
|
||||
text = type.message,
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
|
|
@ -185,7 +233,9 @@ private fun DialogContent(type: DialogType, modifier: Modifier = Modifier) {
|
|||
}
|
||||
is DialogType.TextInput -> {
|
||||
OutlineTextField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing24)
|
||||
.fillMaxWidth(),
|
||||
value = type.value,
|
||||
label = type.params.label,
|
||||
placeholder = type.params.placeholder,
|
||||
|
|
@ -197,6 +247,14 @@ private fun DialogContent(type: DialogType, modifier: Modifier = Modifier) {
|
|||
},
|
||||
)
|
||||
}
|
||||
is DialogType.Selector -> {
|
||||
SelectorDialogContent(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
selectedItemIndex = type.selectedItemIndex,
|
||||
items = type.items,
|
||||
onSelect = type.onSelect,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -204,7 +262,7 @@ private fun DialogContent(type: DialogType, modifier: Modifier = Modifier) {
|
|||
@Composable
|
||||
private fun DialogButtons(confirmButton: DialogButton, dismissButton: DialogButton?, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.spacedBy(
|
||||
space = TangemTheme.dimens.spacing4,
|
||||
alignment = Alignment.End,
|
||||
|
|
@ -252,14 +310,72 @@ private fun DialogButton(
|
|||
}
|
||||
}
|
||||
|
||||
private sealed interface DialogType {
|
||||
data class Message(val message: String) : DialogType
|
||||
@Composable
|
||||
private fun SelectorDialogContent(
|
||||
selectedItemIndex: Int,
|
||||
items: ImmutableList<String>,
|
||||
onSelect: (index: Int) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
LazyColumn(modifier = modifier) {
|
||||
itemsIndexed(items = items) { index, itemText ->
|
||||
val onClick = remember(index) {
|
||||
{ onSelect(index) }
|
||||
}
|
||||
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clickable(
|
||||
interactionSource = interactionSource,
|
||||
indication = LocalIndication.current,
|
||||
onClick = onClick,
|
||||
)
|
||||
.padding(
|
||||
vertical = TangemTheme.dimens.spacing16,
|
||||
horizontal = TangemTheme.dimens.spacing18,
|
||||
)
|
||||
.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
RadioButton(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size24),
|
||||
selected = index == selectedItemIndex,
|
||||
onClick = onClick,
|
||||
colors = RadioButtonDefaults.colors(
|
||||
selectedColor = TangemTheme.colors.icon.accent,
|
||||
unselectedColor = TangemTheme.colors.icon.secondary,
|
||||
),
|
||||
interactionSource = interactionSource,
|
||||
)
|
||||
Text(
|
||||
text = itemText,
|
||||
style = TangemTheme.typography.body1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
private sealed class DialogType {
|
||||
|
||||
data class Message(val message: String) : DialogType()
|
||||
|
||||
data class TextInput(
|
||||
val value: TextFieldValue,
|
||||
val onValueChange: (TextFieldValue) -> Unit,
|
||||
val params: AdditionalTextInputDialogParams = AdditionalTextInputDialogParams(),
|
||||
) : DialogType
|
||||
) : DialogType()
|
||||
|
||||
data class Selector(
|
||||
val selectedItemIndex: Int,
|
||||
val items: ImmutableList<String>,
|
||||
val onSelect: (index: Int) -> Unit,
|
||||
) : DialogType()
|
||||
}
|
||||
// endregion Defaults
|
||||
|
||||
|
|
@ -381,4 +497,65 @@ private fun TextInputDialogPreview_Dark() {
|
|||
TextInputDialogSample()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun SelectorDialogPreview_Light(
|
||||
@PreviewParameter(SelctorDialogParamsProvider::class) param: SelectorDialogParams,
|
||||
) {
|
||||
TangemTheme(isDark = false) {
|
||||
SelectorDialog(
|
||||
title = param.title,
|
||||
items = param.items,
|
||||
selectedItemIndex = param.selectedItemIndex,
|
||||
confirmButton = DialogButton(title = "Cancel", onClick = {}),
|
||||
onSelect = {},
|
||||
onDismissDialog = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun SelectorDialogPreview_Dark(
|
||||
@PreviewParameter(SelctorDialogParamsProvider::class) param: SelectorDialogParams,
|
||||
) {
|
||||
TangemTheme(isDark = true) {
|
||||
SelectorDialog(
|
||||
title = param.title,
|
||||
items = param.items,
|
||||
selectedItemIndex = param.selectedItemIndex,
|
||||
confirmButton = DialogButton(title = "Cancel", onClick = {}),
|
||||
onSelect = {},
|
||||
onDismissDialog = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class SelctorDialogParamsProvider : CollectionPreviewParameterProvider<SelectorDialogParams>(
|
||||
collection = listOf(
|
||||
SelectorDialogParams(
|
||||
title = "Theme",
|
||||
selectedItemIndex = 0,
|
||||
persistentListOf("Light", "Dark", "Follow system"),
|
||||
),
|
||||
SelectorDialogParams(
|
||||
title = null,
|
||||
selectedItemIndex = 2,
|
||||
persistentListOf("Light", "Dark", "Follow system"),
|
||||
),
|
||||
SelectorDialogParams(
|
||||
title = "Count",
|
||||
selectedItemIndex = 8,
|
||||
List(size = 10) { it.toString() }.toImmutableList(),
|
||||
),
|
||||
),
|
||||
) {
|
||||
|
||||
data class SelectorDialogParams(
|
||||
val title: String?,
|
||||
val selectedItemIndex: Int,
|
||||
val items: ImmutableList<String>,
|
||||
)
|
||||
}
|
||||
// endregion Preview
|
||||
|
|
@ -20,7 +20,6 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
|||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.SpacerW8
|
||||
import com.tangem.core.ui.components.buttons.common.*
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
|
@ -99,7 +98,7 @@ private fun Button(
|
|||
val iconTint by animateColorAsState(
|
||||
targetValue = when {
|
||||
!config.enabled -> TangemTheme.colors.icon.informative
|
||||
config.dimContent -> TangemTheme.colors.icon.secondary
|
||||
config.dimContent -> TangemTheme.colors.icon.informative
|
||||
else -> TangemTheme.colors.icon.primary1
|
||||
},
|
||||
label = "Update tint color",
|
||||
|
|
@ -117,7 +116,7 @@ private fun Button(
|
|||
val textColor by animateColorAsState(
|
||||
targetValue = when {
|
||||
!config.enabled -> TangemTheme.colors.text.disabled
|
||||
config.dimContent -> TangemTheme.colors.text.secondary
|
||||
config.dimContent -> TangemTheme.colors.text.tertiary
|
||||
else -> TangemTheme.colors.text.primary1
|
||||
},
|
||||
label = "Update text color",
|
||||
|
|
|
|||
|
|
@ -1,45 +1,49 @@
|
|||
package com.tangem.core.ui.extensions
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import com.tangem.core.ui.R
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.luminance
|
||||
import androidx.core.graphics.toColorInt
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.domain.tokens.models.CryptoCurrency
|
||||
|
||||
private const val LIGHT_LUMINANCE = 0.5f
|
||||
private const val COLOR_HEX_START_INDEX = 2
|
||||
private const val COLOR_HEX_END_INDEX = 7
|
||||
|
||||
/**
|
||||
* Retrieves the resource ID for the network badge of a [CryptoCurrency].
|
||||
* Retrieves the resource ID for the network of a [CryptoCurrency].
|
||||
*
|
||||
* This property provides a way to fetch the appropriate drawable resource ID
|
||||
* for the network badge of a given cryptocurrency. For coins, this will typically
|
||||
* return null as they do not have network badges, while tokens will fetch the icon
|
||||
* based on their associated network ID.
|
||||
*
|
||||
* @return Drawable resource ID for the network badge or null if the cryptocurrency is a coin.
|
||||
* @return Drawable resource ID for the network.
|
||||
*/
|
||||
@get:DrawableRes
|
||||
val CryptoCurrency.networkBadgeIconResId: Int?
|
||||
get() = when (this) {
|
||||
is CryptoCurrency.Coin -> null
|
||||
is CryptoCurrency.Token -> getActiveIconRes(network.id.value)
|
||||
val CryptoCurrency.networkIconResId: Int
|
||||
get() = getActiveIconRes(network.id.value)
|
||||
|
||||
/**
|
||||
* Tries to extract a background color from the contract address of a token.
|
||||
*
|
||||
* @param fallbackColor The color to use as a fallback.
|
||||
* @return The extracted background color or the fallback color if extraction fails or if it is a test network token.
|
||||
*/
|
||||
fun CryptoCurrency.Token.tryGetBackgroundForTokenIcon(fallbackColor: Color = TangemColorPalette.Black): Color {
|
||||
if (network.isTestnet) return fallbackColor
|
||||
|
||||
return try {
|
||||
val colorHex = "#" + contractAddress.substring(range = COLOR_HEX_START_INDEX..COLOR_HEX_END_INDEX)
|
||||
Color(colorHex.toColorInt())
|
||||
} catch (exception: Exception) {
|
||||
fallbackColor
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the resource ID for the icon of a [CryptoCurrency].
|
||||
* Determines the tint color to be used for a token icon based on its background color.
|
||||
* If the icon's background color is light, a dark tint is chosen; otherwise, a light tint is chosen.
|
||||
*
|
||||
* This property provides a way to fetch the appropriate drawable resource ID
|
||||
* for the icon of a given cryptocurrency.
|
||||
*
|
||||
* @return Drawable resource ID for the cryptocurrency icon.
|
||||
* @param iconBackground The background color of the custom token icon.
|
||||
* @return The tint color to be used for the icon.
|
||||
*/
|
||||
@get:DrawableRes
|
||||
val CryptoCurrency.iconResId: Int
|
||||
get() = when (this) {
|
||||
is CryptoCurrency.Coin -> {
|
||||
val rawCoinId = id.rawCurrencyId
|
||||
|
||||
if (rawCoinId != null) {
|
||||
getActiveIconResByCoinId(rawCoinId, network.id.value)
|
||||
} else {
|
||||
R.drawable.ic_alert_24
|
||||
}
|
||||
}
|
||||
is CryptoCurrency.Token -> R.drawable.ic_alert_24
|
||||
}
|
||||
fun getTintForTokenIcon(iconBackground: Color): Color {
|
||||
return if (iconBackground.luminance() > LIGHT_LUMINANCE) TangemColorPalette.Black else TangemColorPalette.White
|
||||
}
|
||||
|
|
@ -50,6 +50,53 @@ sealed interface TextReference {
|
|||
data class Combined(val refs: WrappedList<TextReference>) : TextReference
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a [TextReference] using a string resource ID with optional format arguments.
|
||||
*
|
||||
* @param id The resource ID of the string.
|
||||
* @param formatArgs A list of format arguments to be applied to the string resource.
|
||||
* @return A [TextReference] representing the string resource with format arguments.
|
||||
*/
|
||||
fun resourceReference(@StringRes id: Int, formatArgs: WrappedList<Any> = WrappedList(emptyList())): TextReference {
|
||||
return TextReference.Res(id, formatArgs)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a [TextReference] using a plain string value.
|
||||
*
|
||||
* @param value The plain string value.
|
||||
* @return A [TextReference] representing the provided string value.
|
||||
*/
|
||||
fun stringReference(value: String): TextReference {
|
||||
return TextReference.Str(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a [TextReference] using a plural string resource ID with count and optional format arguments.
|
||||
*
|
||||
* @param id The resource ID of the plural string.
|
||||
* @param count The count value to determine the plural form.
|
||||
* @param formatArgs A list of format arguments to be applied to the plural string resource.
|
||||
* @return A [TextReference] representing the plural string resource with count and format arguments.
|
||||
*/
|
||||
fun pluralReference(
|
||||
@PluralsRes id: Int,
|
||||
count: Int,
|
||||
formatArgs: WrappedList<Any> = WrappedList(emptyList()),
|
||||
): TextReference {
|
||||
return TextReference.PluralRes(id, count, formatArgs)
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines multiple [TextReference] instances into a single [TextReference].
|
||||
*
|
||||
* @param refs A list of [TextReference] instances to be combined.
|
||||
* @return A [TextReference] representing the combined text references.
|
||||
*/
|
||||
fun combinedReference(refs: WrappedList<TextReference>): TextReference {
|
||||
return TextReference.Combined(refs)
|
||||
}
|
||||
|
||||
/** Resolve [TextReference] as [String] */
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import androidx.compose.runtime.Immutable
|
|||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Suppress("ConstructorParameterNaming")
|
||||
@Suppress("ConstructorParameterNaming", "MagicNumber")
|
||||
@Immutable
|
||||
data class TangemDimens internal constructor(
|
||||
// region Elevation
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ private fun materialThemeColors(colors: TangemColors, isDark: Boolean): Colors {
|
|||
}
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
private fun lightThemeColors(): TangemColors {
|
||||
return TangemColors(
|
||||
text = TangemColors.Text(
|
||||
|
|
@ -135,6 +136,7 @@ private fun lightThemeColors(): TangemColors {
|
|||
}
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
private fun darkThemeColors(): TangemColors {
|
||||
return TangemColors(
|
||||
text = TangemColors.Text(
|
||||
|
|
|
|||
10
core/ui/src/main/res/drawable/ic_custom_token_44.xml
Normal file
10
core/ui/src/main/res/drawable/ic_custom_token_44.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="44dp"
|
||||
android:height="44dp"
|
||||
android:viewportWidth="44"
|
||||
android:viewportHeight="44">
|
||||
|
||||
<path
|
||||
android:fillColor="#656565"
|
||||
android:pathData="M22,34L22.69,29.327C23,27.23 23.155,26.182 23.648,25.352C24.065,24.651 24.651,24.065 25.352,23.648C26.182,23.155 27.23,23 29.327,22.69L34,22L29.327,21.31C27.23,21 26.182,20.845 25.352,20.352C24.651,19.935 24.065,19.349 23.648,18.648C23.155,17.818 23,16.77 22.69,14.673L22,10L21.31,14.673C21,16.77 20.845,17.818 20.352,18.648C19.935,19.349 19.349,19.935 18.648,20.352C17.818,20.845 16.77,21 14.673,21.31L10,22L14.673,22.69C16.77,23 17.818,23.155 18.648,23.648C19.349,24.065 19.935,24.651 20.352,25.352C20.845,26.182 21,27.23 21.31,29.327L22,34Z" />
|
||||
</vector>
|
||||
Loading…
Add table
Add a link
Reference in a new issue