diff --git a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/AddCustomTokenScreen.kt b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/AddCustomTokenScreen.kt index 93066fc926..cfa3c7a5b8 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/AddCustomTokenScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/AddCustomTokenScreen.kt @@ -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) } } diff --git a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/HangingOverKeyboardView.kt b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/HangingOverKeyboardView.kt index 46e31151a8..4e251f0838 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/HangingOverKeyboardView.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/addCustomToken/compose/HangingOverKeyboardView.kt @@ -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] diff --git a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt index 149fd2b517..7148c6ecb7 100644 --- a/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/tokens/ui/compose/CurrenciesScreen.kt @@ -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 diff --git a/app/src/main/java/com/tangem/tap/common/compose/KeyboardState.kt b/core/ui/src/main/java/com/tangem/core/ui/components/KeybosardState.kt similarity index 75% rename from app/src/main/java/com/tangem/tap/common/compose/KeyboardState.kt rename to core/ui/src/main/java/com/tangem/core/ui/components/KeybosardState.kt index 2f08f3ae15..69d24e6980 100644 --- a/app/src/main/java/com/tangem/tap/common/compose/KeyboardState.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/KeybosardState.kt @@ -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 { val keyboardState: MutableState = remember { mutableStateOf(Keyboard.Closed) } @@ -25,7 +33,7 @@ fun keyboardAsState(): State { 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 } diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt index 3e0272c54c..f8e9dada94 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/models/SwapStateHolder.kt @@ -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 } \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt index 03249e7a4a..41c04fcfbd 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapScreenContent.kt @@ -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 = "", diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt index cf8bb208f0..f5448c3386 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/TransactionCard.kt @@ -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 = "", diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt index 06a0292de9..30004ba1ec 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt @@ -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 = "",