Updated on 2026-08-14
This commit is contained in:
commit
ee12920c42
28 changed files with 1545 additions and 148 deletions
|
|
@ -0,0 +1,60 @@
|
|||
package com.tangem.core.ui.components.fields
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
/**
|
||||
* Simple text field with placeholder
|
||||
*/
|
||||
@Composable
|
||||
fun SimpleTextField(
|
||||
value: String,
|
||||
onValueChange: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
placeholder: TextReference? = null,
|
||||
singleLine: Boolean = false,
|
||||
visualTransformation: VisualTransformation = VisualTransformation.None,
|
||||
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
||||
color: Color = TangemTheme.colors.text.primary1,
|
||||
readOnly: Boolean = false,
|
||||
) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
BasicTextField(
|
||||
value = value,
|
||||
onValueChange = onValueChange,
|
||||
textStyle = TangemTheme.typography.body2.copy(color = color),
|
||||
cursorBrush = SolidColor(TangemTheme.colors.text.primary1),
|
||||
singleLine = singleLine,
|
||||
readOnly = readOnly,
|
||||
visualTransformation = visualTransformation,
|
||||
keyboardOptions = keyboardOptions,
|
||||
decorationBox = { textValue ->
|
||||
Box {
|
||||
if (value.isBlank() && placeholder != null) {
|
||||
Text(
|
||||
text = placeholder.resolveReference(),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.disabled,
|
||||
modifier = Modifier,
|
||||
)
|
||||
}
|
||||
textValue()
|
||||
}
|
||||
},
|
||||
modifier = modifier
|
||||
.focusRequester(focusRequester),
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
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.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.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import com.tangem.core.ui.R
|
||||
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
|
||||
|
||||
/**
|
||||
* [InputRowDefault](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=2100-807&mode=design&t=86eKp9izWxUvmoCq-4)
|
||||
*
|
||||
* @param title title reference
|
||||
* @param text primary text reference
|
||||
* @param modifier modifier
|
||||
* @param titleColor title color
|
||||
* @param textColor text color
|
||||
* @param iconRes action icon
|
||||
* @param iconTint action icon tint
|
||||
* @param onIconClick click on action icon
|
||||
* @param showDivider show divider
|
||||
* @see [InputRowEnter] for editable version
|
||||
*/
|
||||
@Composable
|
||||
fun InputRowDefault(
|
||||
title: TextReference,
|
||||
text: TextReference,
|
||||
modifier: Modifier = Modifier,
|
||||
titleColor: Color = TangemTheme.colors.text.secondary,
|
||||
textColor: Color = TangemTheme.colors.text.primary1,
|
||||
iconRes: Int? = null,
|
||||
iconTint: Color = TangemTheme.colors.icon.informative,
|
||||
onIconClick: (() -> Unit)? = null,
|
||||
showDivider: Boolean = false,
|
||||
) {
|
||||
DividerContainer(
|
||||
modifier = modifier,
|
||||
showDivider = showDivider,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f),
|
||||
) {
|
||||
Text(
|
||||
text = title.resolveReference(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = titleColor,
|
||||
)
|
||||
Text(
|
||||
text = text.resolveReference(),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = textColor,
|
||||
modifier = Modifier.padding(top = TangemTheme.dimens.spacing8),
|
||||
)
|
||||
}
|
||||
iconRes?.let {
|
||||
Icon(
|
||||
painter = painterResource(id = iconRes),
|
||||
contentDescription = null,
|
||||
tint = iconTint,
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
top = TangemTheme.dimens.spacing10,
|
||||
bottom = TangemTheme.dimens.spacing10,
|
||||
)
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = rememberRipple(bounded = false),
|
||||
) { onIconClick?.invoke() },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//region preview
|
||||
@Preview
|
||||
@Composable
|
||||
private fun InputRowDefaultPreview_Light(
|
||||
@PreviewParameter(InputRowDefaultPreviewDataProvider::class) data: InputRowDefaultPreviewData,
|
||||
) {
|
||||
TangemTheme {
|
||||
InputRowDefault(
|
||||
title = TextReference.Str(data.title),
|
||||
text = TextReference.Str(data.text),
|
||||
iconRes = data.iconRes,
|
||||
showDivider = data.showDivider,
|
||||
modifier = Modifier.background(TangemTheme.colors.background.action),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun InputRowDefaultPreview_Dark(
|
||||
@PreviewParameter(InputRowDefaultPreviewDataProvider::class) data: InputRowDefaultPreviewData,
|
||||
) {
|
||||
TangemTheme(isDark = true) {
|
||||
InputRowDefault(
|
||||
title = TextReference.Str(data.title),
|
||||
text = TextReference.Str(data.text),
|
||||
iconRes = data.iconRes,
|
||||
showDivider = data.showDivider,
|
||||
modifier = Modifier.background(TangemTheme.colors.background.action),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private data class InputRowDefaultPreviewData(
|
||||
val title: String,
|
||||
val text: String,
|
||||
val iconRes: Int?,
|
||||
val showDivider: Boolean,
|
||||
)
|
||||
|
||||
private class InputRowDefaultPreviewDataProvider : PreviewParameterProvider<InputRowDefaultPreviewData> {
|
||||
override val values: Sequence<InputRowDefaultPreviewData>
|
||||
get() = sequenceOf(
|
||||
InputRowDefaultPreviewData(
|
||||
title = "title",
|
||||
text = "text",
|
||||
iconRes = null,
|
||||
showDivider = true,
|
||||
),
|
||||
InputRowDefaultPreviewData(
|
||||
title = "title",
|
||||
text = "text",
|
||||
iconRes = R.drawable.ic_chevron_right_24,
|
||||
showDivider = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
//endregion
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
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.text.KeyboardOptions
|
||||
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.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.inputrow.inner.DividerContainer
|
||||
import com.tangem.core.ui.components.fields.SimpleTextField
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
/**
|
||||
* [InputRowEnter](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=2100-799&mode=design&t=IQ5lBJEkFGU4WSvi-4)
|
||||
*
|
||||
* @param title title reference
|
||||
* @param text primary text reference
|
||||
* @param onValueChange text change callback
|
||||
* @param modifier modifier
|
||||
* @param titleColor title color
|
||||
* @param textColor text color
|
||||
* @param isSingleLine text
|
||||
* @param visualTransformation applied transformation to text
|
||||
* @param keyboardOptions keyboard options for field
|
||||
* @param iconRes action icon
|
||||
* @param iconTint action icon tint
|
||||
* @param onIconClick click on action icon
|
||||
* @param showDivider show divider
|
||||
* @see [InputRowDefault] for read only version
|
||||
*/
|
||||
@Composable
|
||||
fun InputRowEnter(
|
||||
title: TextReference,
|
||||
text: String,
|
||||
onValueChange: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
titleColor: Color = TangemTheme.colors.text.secondary,
|
||||
textColor: Color = TangemTheme.colors.text.primary1,
|
||||
isSingleLine: Boolean = false,
|
||||
visualTransformation: VisualTransformation = VisualTransformation.None,
|
||||
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
||||
iconRes: Int? = null,
|
||||
iconTint: Color = TangemTheme.colors.icon.informative,
|
||||
onIconClick: (() -> Unit)? = null,
|
||||
showDivider: Boolean = false,
|
||||
) {
|
||||
DividerContainer(
|
||||
modifier = modifier,
|
||||
showDivider = showDivider,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = title.resolveReference(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = titleColor,
|
||||
)
|
||||
SimpleTextField(
|
||||
value = text,
|
||||
onValueChange = onValueChange,
|
||||
color = textColor,
|
||||
singleLine = isSingleLine,
|
||||
visualTransformation = visualTransformation,
|
||||
keyboardOptions = keyboardOptions,
|
||||
modifier = Modifier
|
||||
.padding(top = TangemTheme.dimens.spacing8),
|
||||
)
|
||||
}
|
||||
iconRes?.let {
|
||||
Icon(
|
||||
painter = painterResource(id = iconRes),
|
||||
contentDescription = null,
|
||||
tint = iconTint,
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
top = TangemTheme.dimens.spacing10,
|
||||
bottom = TangemTheme.dimens.spacing10,
|
||||
)
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = rememberRipple(bounded = false),
|
||||
) { onIconClick?.invoke() },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//region preview
|
||||
@Preview
|
||||
@Composable
|
||||
private fun InputRowEnterPreview_Light(
|
||||
@PreviewParameter(InputRowEnterPreviewDataProvider::class) data: InputRowEnterPreviewData,
|
||||
) {
|
||||
TangemTheme {
|
||||
InputRowEnter(
|
||||
title = TextReference.Str(data.title),
|
||||
text = data.text,
|
||||
iconRes = data.iconRes,
|
||||
showDivider = data.showDivider,
|
||||
onValueChange = {},
|
||||
modifier = Modifier.background(TangemTheme.colors.background.action),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun InputRowEnterPreview_Dark(
|
||||
@PreviewParameter(InputRowEnterPreviewDataProvider::class) data: InputRowEnterPreviewData,
|
||||
) {
|
||||
TangemTheme(isDark = true) {
|
||||
InputRowEnter(
|
||||
title = TextReference.Str(data.title),
|
||||
text = data.text,
|
||||
iconRes = data.iconRes,
|
||||
showDivider = data.showDivider,
|
||||
onValueChange = {},
|
||||
modifier = Modifier.background(TangemTheme.colors.background.action),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private data class InputRowEnterPreviewData(
|
||||
val title: String,
|
||||
val text: String,
|
||||
val iconRes: Int?,
|
||||
val showDivider: Boolean,
|
||||
)
|
||||
|
||||
private class InputRowEnterPreviewDataProvider :
|
||||
PreviewParameterProvider<InputRowEnterPreviewData> {
|
||||
override val values: Sequence<InputRowEnterPreviewData>
|
||||
get() = sequenceOf(
|
||||
InputRowEnterPreviewData(
|
||||
title = "title",
|
||||
text = "text",
|
||||
iconRes = null,
|
||||
showDivider = true,
|
||||
),
|
||||
InputRowEnterPreviewData(
|
||||
title = "title",
|
||||
text = "text",
|
||||
iconRes = R.drawable.ic_chevron_right_24,
|
||||
showDivider = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
//endregion
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
package com.tangem.core.ui.components.inputrow
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import com.tangem.core.ui.components.inputrow.inner.DividerContainer
|
||||
import com.tangem.core.ui.components.fields.SimpleTextField
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
/**
|
||||
* Input Row Enter with Info variation.
|
||||
* [Input Row Enter](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=2100-799&mode=design&t=IQ5lBJEkFGU4WSvi-4)
|
||||
* [Input Row Enter Info](https://www.figma.com/file/Vs6SkVsFnUPsSCNwlnVf5U/Android-%E2%80%93-UI?type=design&node-id=7854-33577&mode=design&t=6o23sqF8fDQdn4C5-4)
|
||||
*
|
||||
* @param title title reference
|
||||
* @param text primary text reference
|
||||
* @param onValueChange text change callback
|
||||
* @param modifier modifier
|
||||
* @param titleColor title color
|
||||
* @param textColor text color
|
||||
* @param isSingleLine text
|
||||
* @param visualTransformation applied transformation to text
|
||||
* @param keyboardOptions keyboard options for field
|
||||
* @param showDivider show divider
|
||||
*/
|
||||
@Composable
|
||||
fun InputRowEnterInfo(
|
||||
title: TextReference,
|
||||
text: String,
|
||||
onValueChange: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
info: TextReference? = null,
|
||||
titleColor: Color = TangemTheme.colors.text.secondary,
|
||||
textColor: Color = TangemTheme.colors.text.primary1,
|
||||
infoColor: Color = TangemTheme.colors.text.tertiary,
|
||||
isSingleLine: Boolean = false,
|
||||
visualTransformation: VisualTransformation = VisualTransformation.None,
|
||||
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
||||
showDivider: Boolean = false,
|
||||
) {
|
||||
DividerContainer(
|
||||
modifier = modifier,
|
||||
showDivider = showDivider,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
Text(
|
||||
text = title.resolveReference(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = titleColor,
|
||||
)
|
||||
Row {
|
||||
SimpleTextField(
|
||||
value = text,
|
||||
onValueChange = onValueChange,
|
||||
singleLine = isSingleLine,
|
||||
color = textColor,
|
||||
visualTransformation = visualTransformation,
|
||||
keyboardOptions = keyboardOptions,
|
||||
modifier = Modifier
|
||||
.padding(top = TangemTheme.dimens.spacing8)
|
||||
.weight(1f),
|
||||
)
|
||||
info?.let {
|
||||
Text(
|
||||
text = it.resolveReference(),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = infoColor,
|
||||
modifier = Modifier
|
||||
.padding(start = TangemTheme.dimens.spacing8)
|
||||
.align(Alignment.Bottom),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//region preview
|
||||
@Preview
|
||||
@Composable
|
||||
private fun InputRowEnterInfoPreview_Light(
|
||||
@PreviewParameter(InputRowEnterInfoPreviewDataProvider::class) data: InputRowEnterInfoPreviewData,
|
||||
) {
|
||||
TangemTheme {
|
||||
InputRowEnterInfo(
|
||||
title = data.title,
|
||||
text = data.text,
|
||||
info = data.info,
|
||||
showDivider = data.showDivider,
|
||||
onValueChange = {},
|
||||
modifier = Modifier.background(TangemTheme.colors.background.action),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun InputRowEnterInfoPreview_Dark(
|
||||
@PreviewParameter(InputRowEnterInfoPreviewDataProvider::class) data: InputRowEnterInfoPreviewData,
|
||||
) {
|
||||
TangemTheme(isDark = true) {
|
||||
InputRowEnterInfo(
|
||||
title = data.title,
|
||||
text = data.text,
|
||||
info = data.info,
|
||||
showDivider = data.showDivider,
|
||||
onValueChange = {},
|
||||
modifier = Modifier.background(TangemTheme.colors.background.action),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private data class InputRowEnterInfoPreviewData(
|
||||
val title: TextReference,
|
||||
val text: String,
|
||||
val showDivider: Boolean,
|
||||
val info: TextReference?,
|
||||
)
|
||||
|
||||
private class InputRowEnterInfoPreviewDataProvider :
|
||||
PreviewParameterProvider<InputRowEnterInfoPreviewData> {
|
||||
override val values: Sequence<InputRowEnterInfoPreviewData>
|
||||
get() = sequenceOf(
|
||||
InputRowEnterInfoPreviewData(
|
||||
title = TextReference.Str("title"),
|
||||
text = "text",
|
||||
showDivider = true,
|
||||
info = TextReference.Str("info"),
|
||||
),
|
||||
InputRowEnterInfoPreviewData(
|
||||
title = TextReference.Str("title"),
|
||||
text = "text",
|
||||
showDivider = false,
|
||||
info = null,
|
||||
),
|
||||
)
|
||||
}
|
||||
//endregion
|
||||
|
|
@ -0,0 +1,196 @@
|
|||
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.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.Companion.CenterVertically
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.inputrow.inner.DividerContainer
|
||||
import com.tangem.core.ui.components.currency.tokenicon.TokenIcon
|
||||
import com.tangem.core.ui.components.currency.tokenicon.TokenIconState
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
/**
|
||||
* [Input Row Image](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=2100-813&mode=design&t=IQ5lBJEkFGU4WSvi-4)
|
||||
*
|
||||
* @param title title reference
|
||||
* @param subtitle subtitle reference
|
||||
* @param caption caption reference
|
||||
* @param tokenIconState token icon state [TokenIconState]
|
||||
* @param modifier modifier
|
||||
* @param titleColor title color
|
||||
* @param subtitleColor subtitle color
|
||||
* @param captionColor caption color
|
||||
* @param iconRes action icon
|
||||
* @param iconTint action icon tint
|
||||
* @param onIconClick click on action icon
|
||||
* @param showNetworkIcon show token network icon
|
||||
* @param showDivider show divider
|
||||
*/
|
||||
@Composable
|
||||
fun InputRowImage(
|
||||
title: TextReference,
|
||||
subtitle: TextReference,
|
||||
caption: TextReference,
|
||||
tokenIconState: TokenIconState,
|
||||
modifier: Modifier = Modifier,
|
||||
titleColor: Color = TangemTheme.colors.text.secondary,
|
||||
subtitleColor: Color = TangemTheme.colors.text.primary1,
|
||||
captionColor: Color = TangemTheme.colors.text.tertiary,
|
||||
iconRes: Int? = null,
|
||||
iconTint: Color = TangemTheme.colors.icon.informative,
|
||||
onIconClick: (() -> Unit)? = null,
|
||||
showNetworkIcon: Boolean = false,
|
||||
showDivider: Boolean = false,
|
||||
) {
|
||||
DividerContainer(
|
||||
modifier = modifier,
|
||||
showDivider = showDivider,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
Text(
|
||||
text = title.resolveReference(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = titleColor,
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
top = TangemTheme.dimens.spacing6,
|
||||
),
|
||||
) {
|
||||
TokenIcon(
|
||||
state = tokenIconState,
|
||||
shouldDisplayNetwork = showNetworkIcon,
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size36),
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(start = TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
Text(
|
||||
text = subtitle.resolveReference(),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = subtitleColor,
|
||||
)
|
||||
Text(
|
||||
text = caption.resolveReference(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = captionColor,
|
||||
modifier = Modifier.padding(top = TangemTheme.dimens.spacing2),
|
||||
)
|
||||
}
|
||||
iconRes?.let {
|
||||
Icon(
|
||||
painter = painterResource(id = iconRes),
|
||||
contentDescription = null,
|
||||
tint = iconTint,
|
||||
modifier = Modifier
|
||||
.align(CenterVertically)
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = rememberRipple(bounded = false),
|
||||
) { onIconClick?.invoke() },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//region preview
|
||||
@Preview
|
||||
@Composable
|
||||
private fun InputRowInputEnterInfoPreview_Light(
|
||||
@PreviewParameter(InputRowImagePreviewDataProvider::class) data: InputRowImagePreviewData,
|
||||
) {
|
||||
TangemTheme {
|
||||
InputRowImage(
|
||||
title = data.title,
|
||||
modifier = Modifier.background(TangemTheme.colors.background.action),
|
||||
subtitle = data.subtitle,
|
||||
caption = data.caption,
|
||||
tokenIconState = data.iconState,
|
||||
iconRes = data.actionIconRes,
|
||||
onIconClick = {},
|
||||
showNetworkIcon = false,
|
||||
showDivider = data.showDivider,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun InputRowImagePreview_Dark(
|
||||
@PreviewParameter(InputRowImagePreviewDataProvider::class) data: InputRowImagePreviewData,
|
||||
) {
|
||||
TangemTheme(isDark = true) {
|
||||
InputRowImage(
|
||||
title = data.title,
|
||||
modifier = Modifier.background(TangemTheme.colors.background.action),
|
||||
subtitle = data.subtitle,
|
||||
caption = data.caption,
|
||||
tokenIconState = data.iconState,
|
||||
iconRes = data.actionIconRes,
|
||||
onIconClick = {},
|
||||
showNetworkIcon = false,
|
||||
showDivider = data.showDivider,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private data class InputRowImagePreviewData(
|
||||
val title: TextReference,
|
||||
val subtitle: TextReference,
|
||||
val caption: TextReference,
|
||||
val iconState: TokenIconState,
|
||||
val showDivider: Boolean,
|
||||
val actionIconRes: Int?,
|
||||
val showNetworkIcon: Boolean = false,
|
||||
)
|
||||
|
||||
private class InputRowImagePreviewDataProvider :
|
||||
PreviewParameterProvider<InputRowImagePreviewData> {
|
||||
override val values: Sequence<InputRowImagePreviewData>
|
||||
get() = sequenceOf(
|
||||
InputRowImagePreviewData(
|
||||
title = TextReference.Str("title"),
|
||||
subtitle = TextReference.Str("subtitle"),
|
||||
caption = TextReference.Str("caption"),
|
||||
iconState = TokenIconState.Locked,
|
||||
actionIconRes = null,
|
||||
showDivider = false,
|
||||
showNetworkIcon = false,
|
||||
),
|
||||
InputRowImagePreviewData(
|
||||
title = TextReference.Str("title"),
|
||||
subtitle = TextReference.Str("subtitle"),
|
||||
caption = TextReference.Str("caption"),
|
||||
iconState = TokenIconState.Locked,
|
||||
actionIconRes = R.drawable.ic_chevron_right_24,
|
||||
showDivider = true,
|
||||
showNetworkIcon = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
//endregion
|
||||
|
|
@ -0,0 +1,168 @@
|
|||
package com.tangem.core.ui.components.inputrow
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment.Companion.CenterVertically
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.fields.SimpleTextField
|
||||
import com.tangem.core.ui.components.icons.identicon.IdentIcon
|
||||
import com.tangem.core.ui.components.inputrow.inner.DividerContainer
|
||||
import com.tangem.core.ui.components.inputrow.inner.PasteButton
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
/**
|
||||
* [Input Row Recipient](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=2100-826&mode=design&t=IQ5lBJEkFGU4WSvi-4)
|
||||
*
|
||||
* @param title title reference
|
||||
* @param value recipient address
|
||||
* @param placeholder placeholder
|
||||
* @param onValueChange callback for value change
|
||||
* @param onPasteClick callback for paste
|
||||
* @param modifier composable modifier
|
||||
* @param singleLine is single line text
|
||||
* @param error error text
|
||||
* @param isError is error flag
|
||||
* @param showDivider show divider
|
||||
*
|
||||
* @see InputRowRecipientDefault for readonly version
|
||||
*/
|
||||
@Composable
|
||||
fun InputRowRecipient(
|
||||
title: TextReference,
|
||||
value: String,
|
||||
placeholder: TextReference,
|
||||
onValueChange: (String) -> Unit,
|
||||
onPasteClick: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
singleLine: Boolean = false,
|
||||
error: TextReference? = null,
|
||||
isError: Boolean = false,
|
||||
showDivider: Boolean = false,
|
||||
) {
|
||||
val (titleText, color) = if (isError && error != null) {
|
||||
error to TangemTheme.colors.text.warning
|
||||
} else {
|
||||
title to TangemTheme.colors.text.secondary
|
||||
}
|
||||
DividerContainer(
|
||||
modifier = modifier,
|
||||
showDivider = showDivider,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
Text(
|
||||
text = titleText.resolveReference(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = color,
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(top = TangemTheme.dimens.spacing8),
|
||||
) {
|
||||
IdentIcon(
|
||||
address = value,
|
||||
modifier = Modifier
|
||||
.align(CenterVertically)
|
||||
.clip(RoundedCornerShape(TangemTheme.dimens.radius18))
|
||||
.size(TangemTheme.dimens.size36)
|
||||
.background(TangemTheme.colors.background.tertiary),
|
||||
)
|
||||
SimpleTextField(
|
||||
value = value,
|
||||
placeholder = placeholder,
|
||||
onValueChange = onValueChange,
|
||||
singleLine = singleLine,
|
||||
modifier = Modifier
|
||||
.padding(start = TangemTheme.dimens.spacing12)
|
||||
.weight(1f)
|
||||
.align(CenterVertically),
|
||||
)
|
||||
PasteButton(
|
||||
isPasteButtonVisible = value.isBlank(),
|
||||
onClick = onPasteClick,
|
||||
modifier = Modifier
|
||||
.align(CenterVertically)
|
||||
.padding(start = TangemTheme.dimens.spacing8),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//region preview
|
||||
@Preview
|
||||
@Composable
|
||||
private fun InputRowRecipientPreview_Light(
|
||||
@PreviewParameter(InputRowRecipientPreviewDataProvider::class) value: InputRowRecipientPreviewData,
|
||||
) {
|
||||
TangemTheme {
|
||||
InputRowRecipient(
|
||||
value = value.value,
|
||||
title = TextReference.Res(R.string.send_recipient),
|
||||
placeholder = TextReference.Res(R.string.send_optional_field),
|
||||
error = TextReference.Str("Error"),
|
||||
isError = value.isError,
|
||||
showDivider = true,
|
||||
onValueChange = {},
|
||||
onPasteClick = {},
|
||||
modifier = Modifier.background(TangemTheme.colors.background.primary),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun InputRowRecipientPreview_Dark(
|
||||
@PreviewParameter(InputRowRecipientPreviewDataProvider::class) value: InputRowRecipientPreviewData,
|
||||
) {
|
||||
TangemTheme(isDark = true) {
|
||||
InputRowRecipient(
|
||||
value = value.value,
|
||||
title = TextReference.Res(R.string.send_recipient),
|
||||
placeholder = TextReference.Res(R.string.send_optional_field),
|
||||
error = TextReference.Str("Error"),
|
||||
isError = value.isError,
|
||||
showDivider = true,
|
||||
onValueChange = {},
|
||||
onPasteClick = {},
|
||||
modifier = Modifier.background(TangemTheme.colors.background.primary),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private data class InputRowRecipientPreviewData(
|
||||
val value: String,
|
||||
val isError: Boolean,
|
||||
)
|
||||
|
||||
private class InputRowRecipientPreviewDataProvider : PreviewParameterProvider<InputRowRecipientPreviewData> {
|
||||
override val values: Sequence<InputRowRecipientPreviewData>
|
||||
get() = sequenceOf(
|
||||
InputRowRecipientPreviewData(
|
||||
value = "",
|
||||
isError = false,
|
||||
),
|
||||
InputRowRecipientPreviewData(
|
||||
value = "0x391316d97a07027a0702c8A002c8A0C25d8470",
|
||||
isError = false,
|
||||
),
|
||||
InputRowRecipientPreviewData(
|
||||
value = "0x391316d97a07027a0702c8A002c8A0C25d8470",
|
||||
isError = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
//endregion
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
package com.tangem.core.ui.components.inputrow
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.icons.identicon.IdentIcon
|
||||
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
|
||||
|
||||
/**
|
||||
* Read only version of [InputRowRecipient].
|
||||
* [Input Row Recipient](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=2100-826&mode
|
||||
* =design&t=IQ5lBJEkFGU4WSvi-4)
|
||||
*
|
||||
* @param title title reference
|
||||
* @param value recipient address
|
||||
* @param modifier composable modifier
|
||||
* @param titleColor title color
|
||||
* @param showDivider show divider
|
||||
* @see InputRowRecipient for editable version
|
||||
*/
|
||||
@Composable
|
||||
fun InputRowRecipientDefault(
|
||||
title: TextReference,
|
||||
value: String,
|
||||
modifier: Modifier = Modifier,
|
||||
titleColor: Color = TangemTheme.colors.text.secondary,
|
||||
showDivider: Boolean = false,
|
||||
) {
|
||||
DividerContainer(
|
||||
modifier = modifier,
|
||||
showDivider = showDivider,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
Text(
|
||||
text = title.resolveReference(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = titleColor,
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(top = TangemTheme.dimens.spacing8),
|
||||
) {
|
||||
IdentIcon(
|
||||
address = value,
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterVertically)
|
||||
.clip(RoundedCornerShape(TangemTheme.dimens.radius18))
|
||||
.size(TangemTheme.dimens.size36)
|
||||
.background(TangemTheme.colors.background.tertiary),
|
||||
)
|
||||
Text(
|
||||
text = value,
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
modifier = Modifier
|
||||
.padding(start = TangemTheme.dimens.spacing12)
|
||||
.weight(1f)
|
||||
.align(Alignment.CenterVertically),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//region preview
|
||||
@Preview
|
||||
@Composable
|
||||
private fun InputRowRecipientPreview_Light() {
|
||||
TangemTheme {
|
||||
InputRowRecipientDefault(
|
||||
value = "0x391316d97a07027a0702c8A002c8A0C25d8470",
|
||||
title = TextReference.Res(R.string.send_recipient),
|
||||
showDivider = true,
|
||||
modifier = Modifier.background(TangemTheme.colors.background.primary),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun InputRowRecipientPreview_Dark() {
|
||||
TangemTheme(isDark = true) {
|
||||
InputRowRecipientDefault(
|
||||
value = "0x391316d97a07027a0702c8A002c8A0C25d8470",
|
||||
title = TextReference.Res(R.string.send_recipient),
|
||||
showDivider = false,
|
||||
modifier = Modifier.background(TangemTheme.colors.background.primary),
|
||||
)
|
||||
}
|
||||
}
|
||||
//endregion
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.core.ui.components.inputrow.inner
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
@Composable
|
||||
fun DividerContainer(
|
||||
modifier: Modifier = Modifier,
|
||||
showDivider: Boolean = false,
|
||||
paddingValues: PaddingValues = PaddingValues(start = TangemTheme.dimens.spacing12),
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
Box(modifier = modifier) {
|
||||
content()
|
||||
if (showDivider) {
|
||||
Divider(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.padding(paddingValues),
|
||||
color = TangemTheme.colors.stroke.primary,
|
||||
thickness = TangemTheme.dimens.size0_5,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
package com.tangem.core.ui.components.inputrow.inner
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
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.Modifier
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
||||
/**
|
||||
* Paste button with cross icon. Retrieves text from clipboard.
|
||||
* [Paste button](https://www.figma.com/file/Vs6SkVsFnUPsSCNwlnVf5U/Android-%E2%80%93-UI?type=design&node-id=7853-33535&mode=design&t=6o23sqF8fDQdn4C5-4)
|
||||
*
|
||||
* @param isPasteButtonVisible is paste button visible
|
||||
* @param onClick action callback
|
||||
* @param modifier composable modifier
|
||||
*/
|
||||
@Composable
|
||||
fun PasteButton(isPasteButtonVisible: Boolean, onClick: (String) -> Unit, modifier: Modifier = Modifier) {
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
val hapticFeedback = LocalHapticFeedback.current
|
||||
|
||||
if (isPasteButtonVisible) {
|
||||
Box(modifier = modifier) {
|
||||
Text(
|
||||
text = "Paste",
|
||||
style = TangemTheme.typography.button,
|
||||
color = TangemTheme.colors.text.primary2,
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = TangemTheme.colors.button.primary,
|
||||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
)
|
||||
.padding(
|
||||
horizontal = TangemTheme.dimens.spacing10,
|
||||
vertical = TangemTheme.dimens.spacing2,
|
||||
)
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = rememberRipple(radius = TangemTheme.dimens.radius8),
|
||||
onClick = {
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
onClick(
|
||||
clipboardManager
|
||||
.getText()
|
||||
?.toString()
|
||||
.orEmpty(),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_close_24),
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
contentDescription = stringResource(R.string.common_close),
|
||||
modifier = modifier
|
||||
.size(TangemTheme.dimens.size20)
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = rememberRipple(radius = TangemTheme.dimens.radius10),
|
||||
onClick = { onClick("") },
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
10
core/ui/src/main/res/drawable/ic_no_token_44.xml
Normal file
10
core/ui/src/main/res/drawable/ic_no_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:pathData="M6.31,26.499C6.508,25.666 6.773,24.813 7.102,23.949C8.493,26.484 10.368,29 12.684,31.316C15.001,33.632 17.516,35.507 20.051,36.898C19.187,37.227 18.334,37.492 17.501,37.69C13.387,38.669 10.043,37.991 8.026,35.974C6.009,33.957 5.331,30.613 6.31,26.499ZM6.862,37.139C3.507,33.784 3.448,27.963 6.13,22C3.448,16.037 3.507,10.216 6.862,6.862C10.216,3.507 16.037,3.448 22,6.13C27.963,3.448 33.784,3.507 37.139,6.862C40.493,10.216 40.552,16.037 37.87,22C40.553,27.963 40.493,33.784 37.139,37.139C33.784,40.493 27.963,40.552 22,37.87C16.037,40.552 10.216,40.493 6.862,37.139ZM13.849,30.152C11.273,27.576 9.292,24.765 7.948,22C9.292,19.235 11.273,16.424 13.849,13.849C16.424,11.273 19.235,9.292 22,7.948C24.765,9.292 27.576,11.273 30.152,13.849C32.728,16.424 34.708,19.235 36.052,22C34.708,24.765 32.727,27.576 30.152,30.152C27.576,32.727 24.765,34.708 22,36.052C19.235,34.708 16.425,32.727 13.849,30.152ZM12.684,12.684C15,10.368 17.515,8.493 20.051,7.102C19.187,6.773 18.334,6.508 17.502,6.31C13.388,5.331 10.044,6.009 8.026,8.026C6.009,10.043 5.331,13.387 6.31,17.501C6.509,18.334 6.773,19.187 7.102,20.051C8.493,17.515 10.368,15 12.684,12.684ZM31.316,12.684C29,10.368 26.485,8.493 23.949,7.102C24.813,6.773 25.666,6.508 26.499,6.31C30.613,5.331 33.957,6.009 35.974,8.026C37.991,10.043 38.669,13.387 37.69,17.501C37.492,18.334 37.227,19.187 36.898,20.051C35.507,17.515 33.632,15 31.316,12.684ZM31.316,31.316C33.632,29 35.507,26.484 36.898,23.949C37.227,24.813 37.492,25.666 37.69,26.499C38.67,30.613 37.992,33.957 35.974,35.974C33.957,37.991 30.613,38.669 26.499,37.69C25.666,37.492 24.813,37.227 23.949,36.898C26.485,35.507 29,33.632 31.316,31.316ZM22.539,19.035L21.919,14.838L21.299,19.035C21.112,20.303 20.116,21.299 18.848,21.486L14.65,22.106L18.848,22.726C20.116,22.914 21.112,23.91 21.299,25.178L21.919,29.375L22.539,25.178C22.726,23.91 23.722,22.914 24.991,22.726L29.188,22.106L24.991,21.486C23.722,21.299 22.726,20.303 22.539,19.035Z"
|
||||
android:fillColor="#EBEBEB"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
||||
|
|
@ -67,6 +67,14 @@ internal class DefaultQuotesRepository(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun getQuoteSync(currencyId: CryptoCurrency.ID): Quote {
|
||||
return withContext(dispatchers.io) {
|
||||
val quote = quotesStore.getSync(setOf(currencyId)).firstOrNull()
|
||||
requireNotNull(quote) { "Unable to get quote for $currencyId" }
|
||||
quotesConverter.convert(quote)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun fetchExpiredQuotes(
|
||||
currenciesIds: Set<CryptoCurrency.ID>,
|
||||
appCurrencyId: String,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
package com.tangem.domain.tokens
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.tokens.error.mapper.mapToTokenListError
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
||||
class GetCryptoCurrencyStatusSyncUseCase(
|
||||
internal val currenciesRepository: CurrenciesRepository,
|
||||
internal val quotesRepository: QuotesRepository,
|
||||
internal val networksRepository: NetworksRepository,
|
||||
internal val dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencyId: CryptoCurrency.ID,
|
||||
): Either<TokenListError, CryptoCurrencyStatus> {
|
||||
val operations = CurrenciesStatusesOperations(
|
||||
userWalletId = userWalletId,
|
||||
currenciesRepository = currenciesRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
networksRepository = networksRepository,
|
||||
)
|
||||
|
||||
return operations.getCurrencyStatusSync(cryptoCurrencyId)
|
||||
.mapLeft { error -> error.mapToTokenListError() }
|
||||
}
|
||||
}
|
||||
|
|
@ -85,6 +85,28 @@ internal class CurrenciesStatusesOperations(
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun getCurrencyStatusSync(cryptoCurrencyId: CryptoCurrency.ID): Either<Error, CryptoCurrencyStatus> {
|
||||
return either {
|
||||
catch(
|
||||
block = {
|
||||
val currency =
|
||||
currenciesRepository.getMultiCurrencyWalletCurrency(userWalletId, cryptoCurrencyId)
|
||||
val quotes = quotesRepository.getQuoteSync(cryptoCurrencyId).right()
|
||||
val networkStatuses =
|
||||
networksRepository.getNetworkStatusesSync(
|
||||
userWalletId,
|
||||
setOf(currency.network),
|
||||
false,
|
||||
).firstOrNull {
|
||||
it.network == currency.network
|
||||
}.right()
|
||||
return createCurrencyStatus(currency, quotes, networkStatuses)
|
||||
},
|
||||
catch = { raise(Error.DataError(it)) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun getCardCurrenciesStatusesFlow(): Flow<Either<Error, List<CryptoCurrencyStatus>>> {
|
||||
return flow {
|
||||
val nonEmptyCurrencies = recover(
|
||||
|
|
|
|||
|
|
@ -29,4 +29,6 @@ interface QuotesRepository {
|
|||
* @return A [Flow] emitting a set of quotes corresponding to the specified cryptocurrencies.
|
||||
*/
|
||||
suspend fun getQuotesSync(currenciesIds: Set<CryptoCurrency.ID>, refresh: Boolean): Set<Quote>
|
||||
|
||||
suspend fun getQuoteSync(currencyId: CryptoCurrency.ID): Quote
|
||||
}
|
||||
|
|
@ -1,12 +1,9 @@
|
|||
package com.tangem.feature.swap.domain
|
||||
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.feature.swap.domain.models.domain.NetworkInfo
|
||||
|
||||
interface BlockchainInteractor {
|
||||
|
||||
fun getTokenDecimals(token: CryptoCurrency): Int
|
||||
|
||||
/**
|
||||
* In app blockchain id, actual in blockchain sdk, not the same as networkId
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package com.tangem.feature.swap.domain
|
||||
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.feature.swap.domain.models.domain.NetworkInfo
|
||||
import com.tangem.lib.crypto.TransactionManager
|
||||
import javax.inject.Inject
|
||||
|
|
@ -22,12 +21,4 @@ internal class DefaultBlockchainInteractor @Inject constructor(
|
|||
override fun getExplorerTransactionLink(networkId: String, txAddress: String): String {
|
||||
return transactionManager.getExplorerTransactionLink(networkId, txAddress)
|
||||
}
|
||||
|
||||
override fun getTokenDecimals(token: CryptoCurrency): Int {
|
||||
return if (token is CryptoCurrency.Token) {
|
||||
token.decimals
|
||||
} else {
|
||||
transactionManager.getNativeTokenDecimals(token.network.backendId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.feature.swap.domain
|
|||
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.swap.domain.models.SwapAmount
|
||||
import com.tangem.feature.swap.domain.models.domain.*
|
||||
import com.tangem.feature.swap.domain.models.ui.*
|
||||
|
|
@ -111,4 +112,6 @@ interface SwapInteractor {
|
|||
networkId: String,
|
||||
fromToken: CryptoCurrency,
|
||||
): Boolean
|
||||
|
||||
fun getSelectedWallet(): UserWallet?
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
|||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.feature.swap.domain.cache.SwapDataCache
|
||||
import com.tangem.feature.swap.domain.converters.SwapCurrencyConverter
|
||||
|
|
@ -97,6 +98,10 @@ internal class SwapInteractorImpl @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
override fun getSelectedWallet(): UserWallet? {
|
||||
return getSelectedWalletSyncUseCase().getOrNull()
|
||||
}
|
||||
|
||||
private fun getToCurrenciesGroup(
|
||||
currency: CryptoCurrency,
|
||||
leastPairs: List<SwapPairLeast>,
|
||||
|
|
|
|||
|
|
@ -66,13 +66,6 @@ class SwapDomainModule {
|
|||
return GetSelectedWalletSyncUseCase(walletsStateHolder = walletsStateHolder)
|
||||
}
|
||||
|
||||
@SwapScope
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesGetCryptoCurrenciesUseCase(currenciesRepository: CurrenciesRepository): GetCryptoCurrenciesUseCase {
|
||||
return GetCryptoCurrenciesUseCase(currenciesRepository = currenciesRepository)
|
||||
}
|
||||
|
||||
@SwapScope
|
||||
@Provides
|
||||
@Singleton
|
||||
|
|
|
|||
|
|
@ -21,6 +21,10 @@ dependencies {
|
|||
implementation(projects.domain.appCurrency.models)
|
||||
implementation(projects.domain.balanceHiding)
|
||||
implementation(projects.domain.balanceHiding.models)
|
||||
implementation(projects.domain.tokens)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.wallets.models)
|
||||
|
||||
/** AndroidX */
|
||||
implementation(deps.androidx.activity.compose)
|
||||
|
|
|
|||
|
|
@ -32,13 +32,17 @@ class TokensDataConverter(
|
|||
availableTokens = value.available.map { tokenWithBalanceToTokenToSelect(it, true) }
|
||||
.toMutableList()
|
||||
.apply {
|
||||
this.add(0, availableTitle)
|
||||
if (this.isNotEmpty()) {
|
||||
this.add(0, availableTitle)
|
||||
}
|
||||
}
|
||||
.toImmutableList(),
|
||||
unavailableTokens = value.unavailable.map { tokenWithBalanceToTokenToSelect(it, false) }
|
||||
.toMutableList()
|
||||
.apply {
|
||||
this.add(0, unavailableTitle)
|
||||
if (this.isNotEmpty()) {
|
||||
this.add(0, unavailableTitle)
|
||||
}
|
||||
}
|
||||
.toImmutableList(),
|
||||
onSearchEntered = onSearchEntered,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
package com.tangem.feature.swap.di
|
||||
|
||||
import com.tangem.domain.tokens.GetCryptoCurrencyStatusSyncUseCase
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.tokens.repository.NetworksRepository
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import com.tangem.feature.swap.domain.*
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.components.ViewModelComponent
|
||||
import dagger.hilt.android.scopes.ViewModelScoped
|
||||
|
||||
@Module
|
||||
@InstallIn(ViewModelComponent::class)
|
||||
class SwapPresentationModule {
|
||||
|
||||
@ViewModelScoped
|
||||
@Provides
|
||||
fun providesGetCryptoCurrenciesUseCase(
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
dispatcherProvider: CoroutineDispatcherProvider,
|
||||
quotesRepository: QuotesRepository,
|
||||
networksRepository: NetworksRepository,
|
||||
): GetCryptoCurrencyStatusSyncUseCase {
|
||||
return GetCryptoCurrencyStatusSyncUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
quotesRepository = quotesRepository,
|
||||
networksRepository = networksRepository,
|
||||
dispatchers = dispatcherProvider,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -7,8 +7,8 @@ import com.tangem.core.ui.components.states.SelectableItemsState
|
|||
import com.tangem.feature.swap.domain.models.ui.TxFee
|
||||
|
||||
data class SwapStateHolder(
|
||||
val sendCardData: SwapCardData,
|
||||
val receiveCardData: SwapCardData,
|
||||
val sendCardData: SwapCardState,
|
||||
val receiveCardData: SwapCardState,
|
||||
val networkCurrency: String,
|
||||
val networkId: String,
|
||||
val blockchainId: String, // not the same as networkId, its local id in app
|
||||
|
|
@ -33,18 +33,28 @@ data class SwapStateHolder(
|
|||
val onCancelPermissionBottomSheet: () -> Unit = {},
|
||||
)
|
||||
|
||||
data class SwapCardData(
|
||||
val type: TransactionCardType,
|
||||
val amountEquivalent: String?,
|
||||
val coinId: String?,
|
||||
val amountTextFieldValue: TextFieldValue?,
|
||||
val tokenIconUrl: String?,
|
||||
val tokenCurrency: String,
|
||||
val balance: String,
|
||||
val isBalanceHidden: Boolean,
|
||||
val isNotNativeToken: Boolean,
|
||||
val canSelectAnotherToken: Boolean = false,
|
||||
)
|
||||
sealed class SwapCardState {
|
||||
|
||||
data class SwapCardData(
|
||||
val type: TransactionCardType,
|
||||
val amountEquivalent: String?,
|
||||
val coinId: String?,
|
||||
val amountTextFieldValue: TextFieldValue?,
|
||||
val tokenIconUrl: String?,
|
||||
val tokenCurrency: String,
|
||||
val balance: String,
|
||||
val isBalanceHidden: Boolean,
|
||||
val isNotNativeToken: Boolean,
|
||||
val canSelectAnotherToken: Boolean = false,
|
||||
) : SwapCardState()
|
||||
|
||||
data class Empty(
|
||||
val type: TransactionCardType,
|
||||
val amountEquivalent: String?,
|
||||
val amountTextFieldValue: TextFieldValue?,
|
||||
val canSelectAnotherToken: Boolean = false,
|
||||
) : SwapCardState()
|
||||
}
|
||||
|
||||
data class SwapButton(
|
||||
val enabled: Boolean,
|
||||
|
|
|
|||
|
|
@ -8,9 +8,12 @@ import com.tangem.core.ui.components.states.Item
|
|||
import com.tangem.core.ui.components.states.SelectableItemsState
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.feature.swap.converters.TokensDataConverter
|
||||
import com.tangem.feature.swap.domain.models.DataError
|
||||
import com.tangem.feature.swap.domain.models.domain.NetworkInfo
|
||||
|
|
@ -28,7 +31,7 @@ import kotlinx.collections.immutable.toImmutableList
|
|||
internal class StateBuilder(
|
||||
private val actions: UiActions,
|
||||
private val isBalanceHiddenProvider: Provider<Boolean>,
|
||||
appCurrencyProvider: Provider<AppCurrency>,
|
||||
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||
) {
|
||||
|
||||
private val tokensDataConverter = TokensDataConverter(
|
||||
|
|
@ -42,7 +45,7 @@ internal class StateBuilder(
|
|||
return SwapStateHolder(
|
||||
networkId = initialCurrency.network.backendId,
|
||||
blockchainId = networkInfo.blockchainId,
|
||||
sendCardData = SwapCardData(
|
||||
sendCardData = SwapCardState.SwapCardData(
|
||||
type = TransactionCardType.SendCard(actions.onAmountChanged, actions.onAmountSelected),
|
||||
amountEquivalent = null,
|
||||
amountTextFieldValue = null,
|
||||
|
|
@ -54,7 +57,7 @@ internal class StateBuilder(
|
|||
balance = "",
|
||||
isBalanceHidden = true,
|
||||
),
|
||||
receiveCardData = SwapCardData(
|
||||
receiveCardData = SwapCardState.SwapCardData(
|
||||
type = TransactionCardType.ReceiveCard(),
|
||||
amountEquivalent = null,
|
||||
tokenIconUrl = "",
|
||||
|
|
@ -79,6 +82,53 @@ internal class StateBuilder(
|
|||
)
|
||||
}
|
||||
|
||||
fun createNoAvailableTokensToSwapState(
|
||||
uiStateHolder: SwapStateHolder,
|
||||
fromToken: CryptoCurrencyStatus,
|
||||
): SwapStateHolder {
|
||||
if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
||||
return uiStateHolder.copy(
|
||||
sendCardData = SwapCardState.SwapCardData(
|
||||
type = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.SendCard),
|
||||
amountTextFieldValue = TextFieldValue(
|
||||
text = "0",
|
||||
),
|
||||
amountEquivalent = "0 ${appCurrencyProvider.invoke().symbol}",
|
||||
tokenIconUrl = uiStateHolder.sendCardData.tokenIconUrl,
|
||||
coinId = uiStateHolder.sendCardData.coinId,
|
||||
isNotNativeToken = uiStateHolder.sendCardData.isNotNativeToken,
|
||||
tokenCurrency = uiStateHolder.sendCardData.tokenCurrency,
|
||||
canSelectAnotherToken = uiStateHolder.sendCardData.canSelectAnotherToken,
|
||||
balance = fromToken.getFormattedAmount(),
|
||||
isBalanceHidden = isBalanceHiddenProvider(),
|
||||
),
|
||||
receiveCardData = SwapCardState.Empty(
|
||||
type = TransactionCardType.ReceiveCard(),
|
||||
amountEquivalent = "0 ${appCurrencyProvider.invoke().symbol}",
|
||||
amountTextFieldValue = TextFieldValue(
|
||||
text = "0",
|
||||
),
|
||||
canSelectAnotherToken = true,
|
||||
),
|
||||
warnings = listOf(
|
||||
SwapWarning.NoAvailableTokensToSwap(
|
||||
notificationConfig = NotificationConfig(
|
||||
title = stringReference("No tokens"),
|
||||
subtitle = stringReference("Swap tokens not available"),
|
||||
iconResId = R.drawable.ic_alert_24,
|
||||
),
|
||||
),
|
||||
),
|
||||
fee = FeeState.Empty,
|
||||
swapButton = SwapButton(
|
||||
enabled = false,
|
||||
loading = false,
|
||||
onClick = { },
|
||||
),
|
||||
updateInProgress = false,
|
||||
)
|
||||
}
|
||||
|
||||
fun createQuotesLoadingState(
|
||||
uiStateHolder: SwapStateHolder,
|
||||
fromToken: CryptoCurrency,
|
||||
|
|
@ -87,8 +137,10 @@ internal class StateBuilder(
|
|||
): SwapStateHolder {
|
||||
val canSelectSendToken = mainTokenId != fromToken.id.value // TODO look at id matching
|
||||
val canSelectReceiveToken = mainTokenId != toToken.id.value // TODO look at id matching
|
||||
if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
||||
if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
||||
return uiStateHolder.copy(
|
||||
sendCardData = SwapCardData(
|
||||
sendCardData = SwapCardState.SwapCardData(
|
||||
type = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.SendCard),
|
||||
amountTextFieldValue = uiStateHolder.sendCardData.amountTextFieldValue,
|
||||
amountEquivalent = null,
|
||||
|
|
@ -100,7 +152,7 @@ internal class StateBuilder(
|
|||
balance = if (!canSelectSendToken) uiStateHolder.sendCardData.balance else "",
|
||||
isBalanceHidden = isBalanceHiddenProvider(),
|
||||
),
|
||||
receiveCardData = SwapCardData(
|
||||
receiveCardData = SwapCardState.SwapCardData(
|
||||
type = TransactionCardType.ReceiveCard(),
|
||||
amountTextFieldValue = null,
|
||||
amountEquivalent = null,
|
||||
|
|
@ -128,12 +180,15 @@ internal class StateBuilder(
|
|||
* @param onFeeSetup callback for reset fee after auto update
|
||||
* @return updated whole screen state
|
||||
*/
|
||||
@Suppress("LongMethod")
|
||||
fun createQuotesLoadedState(
|
||||
uiStateHolder: SwapStateHolder,
|
||||
quoteModel: SwapState.QuotesLoadedState,
|
||||
fromToken: CryptoCurrency,
|
||||
onFeeSetup: (TxFee) -> Unit,
|
||||
): SwapStateHolder {
|
||||
if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
||||
if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
||||
val warnings = mutableListOf<SwapWarning>()
|
||||
if (!quoteModel.preparedSwapConfigState.isAllowedToSpend &&
|
||||
quoteModel.preparedSwapConfigState.isFeeEnough &&
|
||||
|
|
@ -159,7 +214,7 @@ internal class StateBuilder(
|
|||
}
|
||||
val feeState = createFeeState(quoteModel, uiStateHolder, onFeeSetup)
|
||||
return uiStateHolder.copy(
|
||||
sendCardData = SwapCardData(
|
||||
sendCardData = SwapCardState.SwapCardData(
|
||||
type = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.SendCard),
|
||||
amountTextFieldValue = uiStateHolder.sendCardData.amountTextFieldValue,
|
||||
amountEquivalent = quoteModel.fromTokenInfo.tokenFiatBalance,
|
||||
|
|
@ -171,7 +226,7 @@ internal class StateBuilder(
|
|||
balance = quoteModel.fromTokenInfo.tokenWalletBalance,
|
||||
isBalanceHidden = isBalanceHiddenProvider(),
|
||||
),
|
||||
receiveCardData = SwapCardData(
|
||||
receiveCardData = SwapCardState.SwapCardData(
|
||||
type = TransactionCardType.ReceiveCard(),
|
||||
amountTextFieldValue = TextFieldValue(quoteModel.toTokenInfo.tokenAmount.formatToUIRepresentation()),
|
||||
amountEquivalent = quoteModel.toTokenInfo.tokenFiatBalance,
|
||||
|
|
@ -208,8 +263,10 @@ internal class StateBuilder(
|
|||
uiStateHolder: SwapStateHolder,
|
||||
emptyAmountState: SwapState.EmptyAmountState,
|
||||
): SwapStateHolder {
|
||||
if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
||||
if (uiStateHolder.receiveCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
||||
return uiStateHolder.copy(
|
||||
sendCardData = SwapCardData(
|
||||
sendCardData = SwapCardState.SwapCardData(
|
||||
type = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.SendCard),
|
||||
amountTextFieldValue = uiStateHolder.sendCardData.amountTextFieldValue,
|
||||
amountEquivalent = emptyAmountState.zeroAmountEquivalent,
|
||||
|
|
@ -221,7 +278,7 @@ internal class StateBuilder(
|
|||
balance = emptyAmountState.fromTokenWalletBalance,
|
||||
isBalanceHidden = isBalanceHiddenProvider(),
|
||||
),
|
||||
receiveCardData = SwapCardData(
|
||||
receiveCardData = SwapCardState.SwapCardData(
|
||||
type = TransactionCardType.ReceiveCard(),
|
||||
amountTextFieldValue = TextFieldValue("0"),
|
||||
amountEquivalent = emptyAmountState.zeroAmountEquivalent,
|
||||
|
|
@ -268,6 +325,7 @@ internal class StateBuilder(
|
|||
}
|
||||
|
||||
fun updateSwapAmount(uiState: SwapStateHolder, amount: String): SwapStateHolder {
|
||||
if (uiState.sendCardData !is SwapCardState.SwapCardData) return uiState
|
||||
return uiState.copy(
|
||||
sendCardData = uiState.sendCardData.copy(
|
||||
amountTextFieldValue = TextFieldValue(
|
||||
|
|
@ -279,6 +337,8 @@ internal class StateBuilder(
|
|||
}
|
||||
|
||||
fun updateBalanceHiddenState(uiState: SwapStateHolder, isBalanceHidden: Boolean): SwapStateHolder {
|
||||
if (uiState.sendCardData !is SwapCardState.SwapCardData) return uiState
|
||||
if (uiState.receiveCardData !is SwapCardState.SwapCardData) return uiState
|
||||
val patchedSendCardData = uiState.sendCardData.copy(
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
)
|
||||
|
|
@ -666,11 +726,26 @@ internal class StateBuilder(
|
|||
return "$firstAddressPart...$secondAddressPart"
|
||||
}
|
||||
|
||||
private fun CryptoCurrencyStatus.getFormattedAmount(): String {
|
||||
val amount = value.amount ?: return UNKNOWN_AMOUNT_SIGN
|
||||
|
||||
return BigDecimalFormatter.formatCryptoAmount(amount, currency.symbol, currency.decimals)
|
||||
}
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
private fun CryptoCurrencyStatus.getFormattedFiatAmount(): String {
|
||||
val fiatAmount = value.fiatAmount ?: return UNKNOWN_AMOUNT_SIGN
|
||||
val appCurrency = appCurrencyProvider()
|
||||
|
||||
return BigDecimalFormatter.formatFiatAmount(fiatAmount, appCurrency.code, appCurrency.symbol)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val ADDRESS_MIN_LENGTH = 11
|
||||
const val ADDRESS_FIRST_PART_LENGTH = 7
|
||||
const val ADDRESS_SECOND_PART_LENGTH = 4
|
||||
private const val PRICE_IMPACT_THRESHOLD = 0.1
|
||||
private const val HUNDRED_PERCENTS = 100
|
||||
private const val UNKNOWN_AMOUNT_SIGN = "—"
|
||||
}
|
||||
}
|
||||
|
|
@ -145,48 +145,24 @@ private fun MainInfo(state: SwapStateHolder) {
|
|||
}
|
||||
val (topCard, bottomCard, button) = createRefs()
|
||||
val priceImpactWarning = state.warnings.filterIsInstance<SwapWarning.HighPriceImpact>().firstOrNull()
|
||||
TransactionCard(
|
||||
type = state.sendCardData.type,
|
||||
balance = if (state.sendCardData.isBalanceHidden) {
|
||||
STARS
|
||||
} else {
|
||||
state.sendCardData.balance
|
||||
},
|
||||
textFieldValue = state.sendCardData.amountTextFieldValue,
|
||||
amountEquivalent = state.sendCardData.amountEquivalent,
|
||||
tokenIconUrl = state.sendCardData.tokenIconUrl ?: "",
|
||||
tokenCurrency = state.sendCardData.tokenCurrency,
|
||||
priceImpact = priceImpactWarning,
|
||||
networkIconRes = if (state.sendCardData.isNotNativeToken) networkIconRes else null,
|
||||
iconPlaceholder = state.sendCardData.coinId?.let {
|
||||
getActiveIconResByCoinId(it)
|
||||
},
|
||||
onChangeTokenClick = if (state.sendCardData.canSelectAnotherToken) state.onSelectTokenClick else null,
|
||||
TransactionCardData(
|
||||
priceImpactWarning = priceImpactWarning,
|
||||
networkIconRes = networkIconRes,
|
||||
swapCardState = state.sendCardData,
|
||||
modifier = Modifier.constrainAs(topCard) {
|
||||
top.linkTo(parent.top)
|
||||
},
|
||||
onSelectTokenClick = state.onSelectTokenClick,
|
||||
)
|
||||
val marginCard = TangemTheme.dimens.spacing16
|
||||
TransactionCard(
|
||||
type = state.receiveCardData.type,
|
||||
balance = if (state.receiveCardData.isBalanceHidden) STARS else state.receiveCardData.balance,
|
||||
textFieldValue = state.receiveCardData.amountTextFieldValue,
|
||||
amountEquivalent = state.receiveCardData.amountEquivalent,
|
||||
tokenIconUrl = state.receiveCardData.tokenIconUrl ?: "",
|
||||
tokenCurrency = state.receiveCardData.tokenCurrency,
|
||||
priceImpact = priceImpactWarning,
|
||||
networkIconRes = if (state.receiveCardData.isNotNativeToken) networkIconRes else null,
|
||||
iconPlaceholder = state.receiveCardData.coinId?.let {
|
||||
getActiveIconResByCoinId(it)
|
||||
},
|
||||
onChangeTokenClick = if (state.receiveCardData.canSelectAnotherToken) {
|
||||
state.onSelectTokenClick
|
||||
} else {
|
||||
null
|
||||
},
|
||||
TransactionCardData(
|
||||
priceImpactWarning = priceImpactWarning,
|
||||
networkIconRes = networkIconRes,
|
||||
swapCardState = state.receiveCardData,
|
||||
modifier = Modifier.constrainAs(bottomCard) {
|
||||
top.linkTo(topCard.bottom, margin = marginCard)
|
||||
},
|
||||
onSelectTokenClick = state.onSelectTokenClick,
|
||||
)
|
||||
val marginButton = TangemTheme.dimens.spacing32
|
||||
SwapButton(
|
||||
|
|
@ -200,6 +176,48 @@ private fun MainInfo(state: SwapStateHolder) {
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TransactionCardData(
|
||||
priceImpactWarning: SwapWarning.HighPriceImpact?,
|
||||
networkIconRes: Int?,
|
||||
swapCardState: SwapCardState,
|
||||
onSelectTokenClick: (() -> Unit)?,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
when (swapCardState) {
|
||||
is SwapCardState.Empty -> {
|
||||
TransactionCardEmpty(
|
||||
type = swapCardState.type,
|
||||
amountEquivalent = swapCardState.amountEquivalent,
|
||||
textFieldValue = swapCardState.amountTextFieldValue,
|
||||
onChangeTokenClick = if (swapCardState.canSelectAnotherToken) onSelectTokenClick else null,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
is SwapCardState.SwapCardData -> {
|
||||
TransactionCard(
|
||||
type = swapCardState.type,
|
||||
balance = if (swapCardState.isBalanceHidden) {
|
||||
STARS
|
||||
} else {
|
||||
swapCardState.balance
|
||||
},
|
||||
textFieldValue = swapCardState.amountTextFieldValue,
|
||||
amountEquivalent = swapCardState.amountEquivalent,
|
||||
tokenIconUrl = swapCardState.tokenIconUrl ?: "",
|
||||
tokenCurrency = swapCardState.tokenCurrency,
|
||||
priceImpact = priceImpactWarning,
|
||||
networkIconRes = if (swapCardState.isNotNativeToken) networkIconRes else null,
|
||||
iconPlaceholder = swapCardState.coinId?.let {
|
||||
getActiveIconResByCoinId(it)
|
||||
},
|
||||
onChangeTokenClick = if (swapCardState.canSelectAnotherToken) onSelectTokenClick else null,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
private fun SwapButton(state: SwapStateHolder, modifier: Modifier = Modifier) {
|
||||
|
|
@ -268,7 +286,8 @@ private fun FeeItem(feeState: FeeState, currency: String) {
|
|||
}
|
||||
}
|
||||
is FeeState.Empty -> {
|
||||
SmallInfoCard(startText = titleString, endText = "")
|
||||
// show nothing
|
||||
// SmallInfoCard(startText = titleString, endText = "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -355,7 +374,7 @@ private fun MainButton(state: SwapStateHolder, onPermissionWarningClick: () -> U
|
|||
|
||||
// region preview
|
||||
|
||||
private val sendCard = SwapCardData(
|
||||
private val sendCard = SwapCardState.SwapCardData(
|
||||
type = TransactionCardType.SendCard({}) {},
|
||||
amountTextFieldValue = TextFieldValue(),
|
||||
amountEquivalent = "1 000 000",
|
||||
|
|
@ -368,7 +387,7 @@ private val sendCard = SwapCardData(
|
|||
isBalanceHidden = false,
|
||||
)
|
||||
|
||||
private val receiveCard = SwapCardData(
|
||||
private val receiveCard = SwapCardState.SwapCardData(
|
||||
type = TransactionCardType.ReceiveCard(),
|
||||
amountTextFieldValue = TextFieldValue(),
|
||||
amountEquivalent = "1 000 000",
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ fun TransactionCard(
|
|||
verticalArrangement = Arrangement.Top,
|
||||
horizontalAlignment = Alignment.Start,
|
||||
) {
|
||||
Header(balance = balance, type = type)
|
||||
Header(balance = stringResource(R.string.common_balance, balance), type = type)
|
||||
|
||||
Content(
|
||||
type = type,
|
||||
|
|
@ -106,6 +106,67 @@ fun TransactionCard(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TransactionCardEmpty(
|
||||
type: TransactionCardType,
|
||||
amountEquivalent: String?,
|
||||
textFieldValue: TextFieldValue?,
|
||||
modifier: Modifier = Modifier,
|
||||
onChangeTokenClick: (() -> Unit)? = null,
|
||||
) {
|
||||
Card(
|
||||
shape = RoundedCornerShape(TangemTheme.dimens.radius12),
|
||||
backgroundColor = TangemTheme.colors.background.primary,
|
||||
elevation = TangemTheme.dimens.elevation2,
|
||||
modifier = modifier,
|
||||
) {
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.Top,
|
||||
horizontalAlignment = Alignment.Start,
|
||||
) {
|
||||
Header(
|
||||
balance = stringResource(id = R.string.swapping_token_not_available),
|
||||
type = type,
|
||||
)
|
||||
|
||||
Content(
|
||||
type = type,
|
||||
amountEquivalent = amountEquivalent,
|
||||
textFieldValue = textFieldValue,
|
||||
priceImpact = null,
|
||||
)
|
||||
}
|
||||
|
||||
Box(modifier = Modifier.align(Alignment.BottomEnd)) {
|
||||
Token(
|
||||
tokenIconUrl = "",
|
||||
tokenCurrency = "",
|
||||
iconPlaceholder = R.drawable.ic_no_token_44,
|
||||
)
|
||||
}
|
||||
|
||||
if (onChangeTokenClick != null) {
|
||||
Box(modifier = Modifier.align(Alignment.CenterEnd)) {
|
||||
ChangeTokenSelector()
|
||||
}
|
||||
Box(
|
||||
Modifier
|
||||
.align(Alignment.CenterEnd)
|
||||
.height(TangemTheme.dimens.size116)
|
||||
.width(TangemTheme.dimens.size102)
|
||||
.clickable(
|
||||
indication = rememberRipple(bounded = false),
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
) { onChangeTokenClick() },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Header(type: TransactionCardType, balance: String, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
|
|
@ -134,7 +195,7 @@ private fun Header(type: TransactionCardType, balance: String, modifier: Modifie
|
|||
SpacerW16()
|
||||
if (balance.isNotBlank()) {
|
||||
Text(
|
||||
text = stringResource(R.string.common_balance, balance),
|
||||
text = balance,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = MaterialTheme.typography.body2,
|
||||
modifier = Modifier
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package com.tangem.feature.swap.viewmodels
|
||||
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.feature.swap.domain.models.domain.Currency
|
||||
import com.tangem.feature.swap.domain.models.ui.RequestApproveStateData
|
||||
import com.tangem.feature.swap.domain.models.ui.SwapStateData
|
||||
|
|
@ -14,8 +14,8 @@ data class SwapProcessDataState(
|
|||
val fromCurrency: Currency? = null,
|
||||
@Deprecated("used in old swap mechanism")
|
||||
val toCurrency: Currency? = null,
|
||||
val fromCryptoCurrency: CryptoCurrency? = null,
|
||||
val toCryptoCurrency: CryptoCurrency? = null,
|
||||
val fromCryptoCurrency: CryptoCurrencyStatus? = null,
|
||||
val toCryptoCurrency: CryptoCurrencyStatus? = null,
|
||||
// Amount from input
|
||||
val amount: String? = null,
|
||||
val approveDataModel: RequestApproveStateData? = null,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,9 @@ import com.tangem.core.ui.utils.InputNumberFormatter
|
|||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.tokens.GetCryptoCurrencyStatusSyncUseCase
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.feature.swap.analytics.SwapEvents
|
||||
import com.tangem.feature.swap.domain.BlockchainInteractor
|
||||
import com.tangem.feature.swap.domain.SwapInteractor
|
||||
|
|
@ -51,11 +53,13 @@ internal class SwapViewModel @Inject constructor(
|
|||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val getCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusSyncUseCase,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel(), DefaultLifecycleObserver {
|
||||
|
||||
private val initialCryptoCurrency: CryptoCurrency = savedStateHandle[SwapFragment.CURRENCY_BUNDLE_KEY]
|
||||
?: error("no expected parameter CryptoCurrency found`")
|
||||
private lateinit var initialCryptoCurrencyStatus: CryptoCurrencyStatus
|
||||
|
||||
private var isBalanceHidden = true
|
||||
|
||||
|
|
@ -90,11 +94,20 @@ internal class SwapViewModel @Inject constructor(
|
|||
get() = swapRouter.currentScreen
|
||||
|
||||
init {
|
||||
swapInteractor.initDerivationPathAndNetwork(
|
||||
derivationPath = initialCryptoCurrency.network.derivationPath.value,
|
||||
network = initialCryptoCurrency.network,
|
||||
)
|
||||
initTokens()
|
||||
viewModelScope.launch(dispatchers.io) {
|
||||
swapInteractor.getSelectedWallet()?.let {
|
||||
initialCryptoCurrencyStatus =
|
||||
requireNotNull(getCryptoCurrencyStatusUseCase(it.walletId, initialCryptoCurrency.id).getOrNull()) {
|
||||
"Failed to get initial crypto currency status"
|
||||
}
|
||||
|
||||
swapInteractor.initDerivationPathAndNetwork(
|
||||
derivationPath = initialCryptoCurrency.network.derivationPath.value,
|
||||
network = initialCryptoCurrency.network,
|
||||
)
|
||||
initTokens()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(owner: LifecycleOwner) {
|
||||
|
|
@ -139,47 +152,38 @@ internal class SwapViewModel @Inject constructor(
|
|||
runCatching(dispatchers.io) {
|
||||
swapInteractor.getTokensDataState(initialCryptoCurrency)
|
||||
}.onSuccess { state ->
|
||||
dataState = dataState.copy(
|
||||
fromCryptoCurrency = initialCryptoCurrency,
|
||||
toCryptoCurrency = state.toGroup.available.first().currencyStatus.currency,
|
||||
tokensDataState = state,
|
||||
)
|
||||
|
||||
// updateTokensState(state)
|
||||
|
||||
val toToken = state.toGroup.available.first()
|
||||
startLoadingQuotes(
|
||||
fromToken = initialCryptoCurrency,
|
||||
toToken = state.toGroup.available.first().currencyStatus.currency,
|
||||
amount = lastAmount.value,
|
||||
toProvidersList = toToken.providers,
|
||||
)
|
||||
updateTokensState(state)
|
||||
applyInitialTokenChoice(state, selectInitialCurrencyToSwap(state))
|
||||
}.onFailure {
|
||||
Timber.tag(loggingTag).e(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// old flow
|
||||
// viewModelScope.launch(dispatchers.main) {
|
||||
// runCatching(dispatchers.io) {
|
||||
// swapInteractor.initTokensToSwap(currency)
|
||||
// }
|
||||
// .onSuccess { state ->
|
||||
// // dataState = dataState.copy(
|
||||
// // fromCurrency = state.preselectTokens.fromToken,
|
||||
// // toCurrency = state.preselectTokens.toToken,
|
||||
// // )
|
||||
// // updateTokensState(dataState = state.foundTokensState)
|
||||
// // startLoadingQuotes(
|
||||
// // fromToken = state.preselectTokens.fromToken,
|
||||
// // toToken = state.preselectTokens.toToken,
|
||||
// // amount = lastAmount.value,
|
||||
// // )
|
||||
// }
|
||||
// .onFailure {
|
||||
// Timber.tag(loggingTag).e(it)
|
||||
// }
|
||||
// }
|
||||
private fun applyInitialTokenChoice(state: TokensDataStateExpress, selectedCurrency: CryptoCurrencyStatus?) {
|
||||
val fromCurrencyStatus = initialCryptoCurrencyStatus
|
||||
dataState = dataState.copy(
|
||||
fromCryptoCurrency = fromCurrencyStatus,
|
||||
toCryptoCurrency = selectedCurrency,
|
||||
tokensDataState = state,
|
||||
)
|
||||
if (selectedCurrency == null) {
|
||||
uiState = stateBuilder.createNoAvailableTokensToSwapState(
|
||||
uiStateHolder = uiState,
|
||||
fromToken = fromCurrencyStatus,
|
||||
)
|
||||
} else {
|
||||
startLoadingQuotes(
|
||||
fromToken = fromCurrencyStatus.currency,
|
||||
toToken = selectedCurrency.currency,
|
||||
amount = lastAmount.value,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectInitialCurrencyToSwap(state: TokensDataStateExpress): CryptoCurrencyStatus? {
|
||||
// todo add algorithm to select initial currency
|
||||
return state.toGroup.available.firstOrNull()?.currencyStatus
|
||||
}
|
||||
|
||||
private fun updateTokensState(dataState: TokensDataStateExpress) {
|
||||
|
|
@ -214,7 +218,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
val toCurrency = dataState.toCryptoCurrency
|
||||
val amount = dataState.amount
|
||||
if (fromCurrency != null && toCurrency != null && amount != null) {
|
||||
startLoadingQuotes(fromCurrency, toCurrency, amount)
|
||||
startLoadingQuotes(fromCurrency.currency, toCurrency.currency, amount)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -298,8 +302,8 @@ internal class SwapViewModel @Inject constructor(
|
|||
swapInteractor.onSwap(
|
||||
networkId = dataState.networkId,
|
||||
swapStateData = requireNotNull(dataState.swapDataModel),
|
||||
currencyToSend = requireNotNull(dataState.fromCryptoCurrency),
|
||||
currencyToGet = requireNotNull(dataState.toCryptoCurrency),
|
||||
currencyToSend = requireNotNull(dataState.fromCryptoCurrency?.currency),
|
||||
currencyToGet = requireNotNull(dataState.toCryptoCurrency?.currency),
|
||||
amountToSwap = requireNotNull(dataState.amount),
|
||||
fee = requireNotNull(dataState.selectedFee),
|
||||
)
|
||||
|
|
@ -348,10 +352,10 @@ internal class SwapViewModel @Inject constructor(
|
|||
approveData = requireNotNull(dataState.approveDataModel) {
|
||||
"dataState.approveDataModel might not be null"
|
||||
},
|
||||
forTokenContractAddress = (dataState.fromCryptoCurrency as? CryptoCurrency.Token)
|
||||
forTokenContractAddress = (dataState.fromCryptoCurrency?.currency as? CryptoCurrency.Token)
|
||||
?.contractAddress
|
||||
?: "",
|
||||
fromToken = requireNotNull(dataState.fromCryptoCurrency) {
|
||||
fromToken = requireNotNull(dataState.fromCryptoCurrency?.currency) {
|
||||
"dataState.fromCurrency might not be null"
|
||||
},
|
||||
approveType = requireNotNull(uiState.permissionState as? SwapPermissionState.ReadyForRequest) {
|
||||
|
|
@ -397,28 +401,34 @@ internal class SwapViewModel @Inject constructor(
|
|||
private fun onTokenSelect(id: String) {
|
||||
val tokens = dataState.tokensDataState ?: return
|
||||
|
||||
val foundToken = tokens.toGroup.available.firstOrNull {
|
||||
it.currencyStatus.currency.id.value == id
|
||||
val foundToken = if (isOrderReversed) {
|
||||
tokens.fromGroup.available.firstOrNull {
|
||||
it.currencyStatus.currency.id.value == id
|
||||
}
|
||||
} else {
|
||||
tokens.toGroup.available.firstOrNull {
|
||||
it.currencyStatus.currency.id.value == id
|
||||
}
|
||||
}
|
||||
analyticsEventHandler.send(
|
||||
event = SwapEvents.SearchTokenClicked(currencySymbol = foundToken?.currencyStatus?.currency?.symbol),
|
||||
)
|
||||
|
||||
if (foundToken != null) {
|
||||
val fromToken: CryptoCurrency
|
||||
val toToken: CryptoCurrency
|
||||
val fromToken: CryptoCurrencyStatus
|
||||
val toToken: CryptoCurrencyStatus
|
||||
if (isOrderReversed) {
|
||||
fromToken = foundToken.currencyStatus.currency
|
||||
toToken = initialCryptoCurrency
|
||||
fromToken = foundToken.currencyStatus
|
||||
toToken = initialCryptoCurrencyStatus
|
||||
} else {
|
||||
fromToken = initialCryptoCurrency
|
||||
toToken = foundToken.currencyStatus.currency
|
||||
fromToken = initialCryptoCurrencyStatus
|
||||
toToken = foundToken.currencyStatus
|
||||
}
|
||||
dataState = dataState.copy(
|
||||
fromCryptoCurrency = fromToken,
|
||||
toCryptoCurrency = toToken,
|
||||
)
|
||||
startLoadingQuotes(fromToken, toToken, lastAmount.value)
|
||||
startLoadingQuotes(fromToken.currency, toToken.currency, lastAmount.value)
|
||||
swapRouter.openScreen(SwapNavScreen.Main)
|
||||
}
|
||||
}
|
||||
|
|
@ -432,13 +442,13 @@ internal class SwapViewModel @Inject constructor(
|
|||
toCryptoCurrency = newToToken,
|
||||
)
|
||||
isOrderReversed = !isOrderReversed
|
||||
val decimals = blockchainInteractor.getTokenDecimals(newFromToken)
|
||||
val decimals = newFromToken.currency.decimals
|
||||
lastAmount.value = cutAmountWithDecimals(decimals, lastAmount.value)
|
||||
uiState = stateBuilder.updateSwapAmount(
|
||||
uiState,
|
||||
inputNumberFormatter.formatWithThousands(lastAmount.value, decimals),
|
||||
)
|
||||
startLoadingQuotes(newFromToken, newToToken, lastAmount.value)
|
||||
startLoadingQuotes(newFromToken.currency, newToToken.currency, lastAmount.value)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -446,20 +456,20 @@ internal class SwapViewModel @Inject constructor(
|
|||
val fromToken = dataState.fromCryptoCurrency
|
||||
val toToken = dataState.toCryptoCurrency
|
||||
if (fromToken != null && toToken != null) {
|
||||
val decimals = blockchainInteractor.getTokenDecimals(fromToken)
|
||||
val decimals = fromToken.currency.decimals
|
||||
val cutValue = cutAmountWithDecimals(decimals, value)
|
||||
lastAmount.value = cutValue
|
||||
uiState =
|
||||
stateBuilder.updateSwapAmount(uiState, inputNumberFormatter.formatWithThousands(cutValue, decimals))
|
||||
amountDebouncer.debounce(viewModelScope, DEBOUNCE_AMOUNT_DELAY) {
|
||||
startLoadingQuotes(fromToken, toToken, lastAmount.value)
|
||||
startLoadingQuotes(fromToken.currency, toToken.currency, lastAmount.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onMaxAmountClicked() {
|
||||
dataState.fromCryptoCurrency?.let {
|
||||
val balance = swapInteractor.getTokenBalance(initialCryptoCurrency.network.id.value, it)
|
||||
val balance = swapInteractor.getTokenBalance(initialCryptoCurrency.network.id.value, it.currency)
|
||||
onAmountChanged(balance.formatToUIRepresentation())
|
||||
}
|
||||
}
|
||||
|
|
@ -535,7 +545,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
dataState = dataState.copy(selectedFee = feeItem.data)
|
||||
val spendAmount = dataState.amount?.let { amount ->
|
||||
val fromToken = dataState.fromCryptoCurrency ?: return@let null
|
||||
swapInteractor.getSwapAmountForToken(amount, fromToken)
|
||||
swapInteractor.getSwapAmountForToken(amount, fromToken.currency)
|
||||
} ?: dataState.approveDataModel?.fromTokenAmount
|
||||
spendAmount ?: return@UiActions
|
||||
val fromToken = dataState.fromCryptoCurrency ?: return@UiActions
|
||||
|
|
@ -544,7 +554,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
fee = feeItem.data.feeValue,
|
||||
spendAmount = spendAmount,
|
||||
networkId = dataState.networkId,
|
||||
fromToken = fromToken,
|
||||
fromToken = fromToken.currency,
|
||||
)
|
||||
uiState = stateBuilder.updateFeeSelectedItem(uiState, feeItem, isFeeEnough)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue