Updated on 2026-08-14
This commit is contained in:
parent
615fb8d718
commit
9aaf4bd3c4
8 changed files with 205 additions and 62 deletions
|
|
@ -38,6 +38,7 @@ import androidx.compose.ui.platform.LocalContext
|
|||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.keyboardAsState
|
||||
import com.tangem.domain.AddCustomTokenError
|
||||
import com.tangem.domain.common.form.DataField
|
||||
import com.tangem.domain.common.form.FieldId
|
||||
|
|
@ -56,7 +57,6 @@ import com.tangem.tap.common.compose.AddCustomTokenWarning
|
|||
import com.tangem.tap.common.compose.ClosePopupTrigger
|
||||
import com.tangem.tap.common.compose.ComposeDialogManager
|
||||
import com.tangem.tap.common.compose.ToggledRippleTheme
|
||||
import com.tangem.tap.common.compose.keyboardAsState
|
||||
import com.tangem.tap.domain.moduleMessage.ModuleMessageConverter
|
||||
import com.tangem.tap.features.tokens.addCustomToken.compose.test.TestCase
|
||||
import com.tangem.tap.features.tokens.addCustomToken.compose.test.TestCasesList
|
||||
|
|
@ -66,7 +66,7 @@ import kotlinx.coroutines.launch
|
|||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
private class AddCustomTokenScreen {} // for simple search
|
||||
private class AddCustomTokenScreen // for simple search
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
|
|
@ -91,11 +91,13 @@ fun AddCustomTokenScreen(
|
|||
},
|
||||
sheetPeekHeight = 0.dp,
|
||||
) {
|
||||
Column() {
|
||||
TestCasesList(onItemClick = {
|
||||
selectedTestCase.value = it
|
||||
toggleBottomSheet()
|
||||
})
|
||||
Column {
|
||||
TestCasesList(
|
||||
onItemClick = {
|
||||
selectedTestCase.value = it
|
||||
toggleBottomSheet()
|
||||
},
|
||||
)
|
||||
ScreenContent(state, closePopupTrigger)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.Keyboard
|
||||
import com.tangem.tangem_sdk_new.extensions.pxToDp
|
||||
import com.tangem.tap.common.compose.Keyboard
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
|
|||
|
|
@ -26,16 +26,16 @@ import androidx.compose.ui.res.colorResource
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.core.ui.components.Keyboard
|
||||
import com.tangem.core.ui.components.SystemBarsEffect
|
||||
import com.tangem.core.ui.components.keyboardAsState
|
||||
import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
|
||||
import com.tangem.domain.common.extensions.fromNetworkId
|
||||
import com.tangem.tap.common.analytics.Analytics
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.ManageTokens
|
||||
import com.tangem.tap.common.compose.Keyboard
|
||||
import com.tangem.tap.common.compose.extensions.addAndNotify
|
||||
import com.tangem.tap.common.compose.extensions.removeAndNotify
|
||||
import com.tangem.tap.common.compose.keyboardAsState
|
||||
import com.tangem.tap.common.extensions.dispatchDialogShow
|
||||
import com.tangem.tap.common.extensions.pixelsToDp
|
||||
import com.tangem.tap.common.redux.AppDialog
|
||||
|
|
|
|||
|
|
@ -1,8 +1,13 @@
|
|||
package com.tangem.tap.common.compose
|
||||
package com.tangem.core.ui.components
|
||||
|
||||
import android.graphics.Rect
|
||||
import android.view.ViewTreeObserver
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
|
||||
sealed class Keyboard {
|
||||
|
|
@ -10,6 +15,9 @@ sealed class Keyboard {
|
|||
object Closed : Keyboard()
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to subscribe to a soft keyboard to detect when it's open/closed
|
||||
*/
|
||||
@Composable
|
||||
fun keyboardAsState(): State<Keyboard> {
|
||||
val keyboardState: MutableState<Keyboard> = remember { mutableStateOf(Keyboard.Closed) }
|
||||
|
|
@ -25,7 +33,7 @@ fun keyboardAsState(): State<Keyboard> {
|
|||
val screenHeight = view.rootView.height
|
||||
val keypadHeight: Int = screenHeight - (rect.bottom + rect.top) - discrepancy.value
|
||||
if (discrepancy.value == 0) {
|
||||
discrepancy.value = keypadHeight;
|
||||
discrepancy.value = keypadHeight
|
||||
if (keypadHeight == 0) discrepancy.value = 1
|
||||
}
|
||||
|
||||
|
|
@ -20,6 +20,7 @@ data class SwapStateHolder(
|
|||
val onChangeCardsClicked: () -> Unit,
|
||||
val onSelectTokenClick: (() -> Unit)? = null,
|
||||
val onSuccess: (() -> Unit)? = null,
|
||||
val onMaxAmountSelected: (() -> Unit)? = null,
|
||||
)
|
||||
|
||||
data class SwapCardData(
|
||||
|
|
@ -53,6 +54,7 @@ sealed interface TransactionCardType {
|
|||
data class SendCard(
|
||||
val balance: String,
|
||||
val permissionIsGiven: Boolean,
|
||||
val onAmountChanged: ((String) -> Unit),
|
||||
) : TransactionCardType
|
||||
|
||||
data class ReceiveCard(
|
||||
|
|
@ -62,6 +64,7 @@ sealed interface TransactionCardType {
|
|||
|
||||
sealed interface SwapWarning {
|
||||
data class PermissionNeeded(val tokenCurrency: String) : SwapWarning
|
||||
data class GenericWarning(val message: String, val onClick: () -> Unit) : SwapWarning
|
||||
// data class RateExpired(val onClick: () -> Unit) : SwapWarning
|
||||
// object HighPriceImpact : SwapWarning
|
||||
}
|
||||
|
|
@ -1,9 +1,12 @@
|
|||
package com.tangem.feature.swap.ui
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
|
|
@ -14,17 +17,24 @@ import androidx.compose.material.Card
|
|||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.ClickableWarningCard
|
||||
import com.tangem.core.ui.components.Keyboard
|
||||
import com.tangem.core.ui.components.PrimaryButtonIconRight
|
||||
import com.tangem.core.ui.components.RefreshableWaringCard
|
||||
import com.tangem.core.ui.components.SmallInfoCard
|
||||
import com.tangem.core.ui.components.SmallInfoCardWithWarning
|
||||
import com.tangem.core.ui.components.SpacerH16
|
||||
import com.tangem.core.ui.components.keyboardAsState
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.swap.models.FeeState
|
||||
import com.tangem.feature.swap.models.SwapButton
|
||||
|
|
@ -40,40 +50,71 @@ internal fun SwapScreenContent(
|
|||
state: SwapStateHolder,
|
||||
onPermissionWarningClick: () -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.background(color = TangemTheme.colors.background.primary)
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(
|
||||
start = TangemTheme.dimens.spacing16,
|
||||
end = TangemTheme.dimens.spacing16,
|
||||
top = TangemTheme.dimens.spacing16,
|
||||
bottom = TangemTheme.dimens.spacing32,
|
||||
),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16),
|
||||
val keyboard by keyboardAsState()
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(color = Color.Transparent),
|
||||
) {
|
||||
|
||||
MainInfo(state)
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.background(color = TangemTheme.colors.background.primary)
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(
|
||||
start = TangemTheme.dimens.spacing16,
|
||||
end = TangemTheme.dimens.spacing16,
|
||||
top = TangemTheme.dimens.spacing16,
|
||||
bottom = TangemTheme.dimens.spacing32,
|
||||
),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing16),
|
||||
) {
|
||||
|
||||
FeeItem(feeState = state.fee, currency = state.networkCurrency)
|
||||
MainInfo(state)
|
||||
|
||||
if (state.warnings.isNotEmpty()) {
|
||||
SwapWarnings(
|
||||
warnings = state.warnings,
|
||||
onApproveWarningClick = onPermissionWarningClick,
|
||||
FeeItem(feeState = state.fee, currency = state.networkCurrency)
|
||||
|
||||
if (state.warnings.isNotEmpty()) {
|
||||
SwapWarnings(
|
||||
warnings = state.warnings,
|
||||
onApproveWarningClick = onPermissionWarningClick,
|
||||
)
|
||||
}
|
||||
|
||||
PrimaryButtonIconRight(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = stringResource(id = R.string.swapping_swap),
|
||||
icon = painterResource(id = R.drawable.ic_tangem_24),
|
||||
enabled = state.swapButton.enabled,
|
||||
showProgress = state.swapButton.loading,
|
||||
onClick = state.swapButton.onClick,
|
||||
)
|
||||
}
|
||||
|
||||
PrimaryButtonIconRight(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = stringResource(id = R.string.swapping_swap),
|
||||
icon = painterResource(id = R.drawable.ic_tangem_24),
|
||||
enabled = state.swapButton.enabled,
|
||||
showProgress = state.swapButton.loading,
|
||||
onClick = state.swapButton.onClick,
|
||||
)
|
||||
AnimatedVisibility(
|
||||
visible = keyboard is Keyboard.Opened,
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.send_max_amount_label),
|
||||
style = TangemTheme.typography.button,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
|
||||
.background(TangemTheme.colors.button.secondary)
|
||||
.clickable { state.onMaxAmountSelected }
|
||||
.padding(
|
||||
horizontal = TangemTheme.dimens.spacing14,
|
||||
vertical = TangemTheme.dimens.spacing16,
|
||||
),
|
||||
textAlign = TextAlign.Start,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -171,6 +212,13 @@ private fun SwapWarnings(
|
|||
onClick = onApproveWarningClick,
|
||||
)
|
||||
}
|
||||
is SwapWarning.GenericWarning -> {
|
||||
RefreshableWaringCard(
|
||||
title = stringResource(id = R.string.common_warning),
|
||||
description = warning.message,
|
||||
onClick = warning.onClick,
|
||||
)
|
||||
}
|
||||
// is SwapWarning.RateExpired -> {
|
||||
// RefreshableWaringCard(
|
||||
// title = stringResource(id = R.string.),
|
||||
|
|
@ -186,7 +234,7 @@ private fun SwapWarnings(
|
|||
// region preview
|
||||
|
||||
private val sendCard = SwapCardData(
|
||||
type = TransactionCardType.SendCard("123", false),
|
||||
type = TransactionCardType.SendCard("123", false, {}),
|
||||
amount = "1 000 000",
|
||||
amountEquivalent = "1 000 000",
|
||||
tokenIconUrl = "",
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import androidx.compose.foundation.background
|
|||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
|
|
@ -19,18 +20,31 @@ import androidx.compose.foundation.layout.size
|
|||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.ParagraphIntrinsics
|
||||
import androidx.compose.ui.text.font.createFontFamilyResolver
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
@ -192,24 +206,34 @@ private fun Content(
|
|||
horizontalAlignment = Alignment.Start,
|
||||
) {
|
||||
|
||||
if (amount != null) {
|
||||
ResizableText(
|
||||
text = amount,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
style = TangemTheme.typography.h2,
|
||||
fontSizeRange =
|
||||
FontSizeRange(min = 16.sp, max = TangemTheme.typography.h2.fontSize),
|
||||
)
|
||||
} else {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(width = TangemTheme.dimens.size102, height = TangemTheme.dimens.size24)
|
||||
.shimmer()
|
||||
.background(
|
||||
color = TangemTheme.colors.button.secondary,
|
||||
shape = RoundedCornerShape(TangemTheme.dimens.radius6),
|
||||
),
|
||||
)
|
||||
when (type) {
|
||||
is TransactionCardType.ReceiveCard -> {
|
||||
if (amount != null) {
|
||||
ResizableText(
|
||||
text = amount,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
style = TangemTheme.typography.h2,
|
||||
fontSizeRange =
|
||||
FontSizeRange(min = 16.sp, max = TangemTheme.typography.h2.fontSize),
|
||||
)
|
||||
} else {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(width = TangemTheme.dimens.size102, height = TangemTheme.dimens.size24)
|
||||
.shimmer()
|
||||
.background(
|
||||
color = TangemTheme.colors.button.secondary,
|
||||
shape = RoundedCornerShape(TangemTheme.dimens.radius6),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
is TransactionCardType.SendCard -> {
|
||||
AutoSizeTextField(
|
||||
amount = amount ?: "0",
|
||||
onAmoutChanged = { type.onAmountChanged(it) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(
|
||||
|
|
@ -241,6 +265,65 @@ private fun Content(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AutoSizeTextField(
|
||||
amount: String,
|
||||
onAmoutChanged: (String) -> Unit,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
val textFieldFocusRequester = remember { FocusRequester() }
|
||||
|
||||
BoxWithConstraints(modifier = Modifier.fillMaxWidth()) {
|
||||
|
||||
var shrunkFontSize = TangemTheme.typography.h2.fontSize
|
||||
val calculateIntrinsics = @Composable {
|
||||
ParagraphIntrinsics(
|
||||
text = amount,
|
||||
style = TangemTheme.typography.h2.copy(
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
fontSize = shrunkFontSize,
|
||||
),
|
||||
density = LocalDensity.current,
|
||||
fontFamilyResolver = createFontFamilyResolver(LocalContext.current),
|
||||
)
|
||||
}
|
||||
|
||||
var intrinsics = calculateIntrinsics()
|
||||
|
||||
with(LocalDensity.current) {
|
||||
while (intrinsics.maxIntrinsicWidth > maxWidth.toPx()) {
|
||||
shrunkFontSize *= 0.9f
|
||||
intrinsics = calculateIntrinsics()
|
||||
}
|
||||
}
|
||||
|
||||
BasicTextField(
|
||||
value = amount,
|
||||
onValueChange = {
|
||||
onAmoutChanged(it)
|
||||
},
|
||||
singleLine = true,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.focusRequester(textFieldFocusRequester),
|
||||
keyboardOptions = KeyboardOptions(
|
||||
imeAction = ImeAction.Done,
|
||||
keyboardType = KeyboardType.Decimal,
|
||||
),
|
||||
keyboardActions = KeyboardActions(
|
||||
onDone = {
|
||||
focusManager.clearFocus()
|
||||
},
|
||||
),
|
||||
textStyle = TangemTheme.typography.h2.copy(
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
fontSize = shrunkFontSize,
|
||||
),
|
||||
cursorBrush = SolidColor(TangemTheme.colors.text.primary1),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Token(
|
||||
tokenIconUrl: String,
|
||||
|
|
@ -370,7 +453,7 @@ fun Preview_SwapMainCard_InDarkTheme() {
|
|||
@Composable
|
||||
private fun TransactionCardPreview() {
|
||||
TransactionCard(
|
||||
type = TransactionCardType.SendCard("123", false),
|
||||
type = TransactionCardType.SendCard("123", false, {}),
|
||||
amount = "1 000 000",
|
||||
amountEquivalent = "1 000 000",
|
||||
tokenIconUrl = "",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import androidx.compose.runtime.setValue
|
|||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.tangem.feature.swap.domain.SwapInteractor
|
||||
import com.tangem.feature.swap.domain.models.data.Currency
|
||||
import com.tangem.feature.swap.models.ApprovePermissionButton
|
||||
import com.tangem.feature.swap.models.CancelPermissionButton
|
||||
import com.tangem.feature.swap.models.FeeState
|
||||
|
|
@ -72,7 +71,7 @@ internal class SwapViewModel @Inject constructor(
|
|||
|
||||
private fun createTestUiState(): SwapStateHolder {
|
||||
val sendCard = SwapCardData(
|
||||
type = TransactionCardType.SendCard("123", false),
|
||||
type = TransactionCardType.SendCard("123", false, {}),
|
||||
amount = "1 000 000 000 000 000 000",
|
||||
amountEquivalent = "1 000 000 000 000 000 000",
|
||||
tokenIconUrl = "",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue