Updated on 2026-08-14

This commit is contained in:
Tangem 2023-01-25 13:32:20 +03:00
commit 8ef0c9f5a6
5 changed files with 118 additions and 124 deletions

View file

@ -53,6 +53,7 @@ dependencies {
implementation(project(":core:ui"))
/** AndroidX */
implementation(AndroidX.activityCompose)
implementation(AndroidX.appCompat)
implementation(AndroidX.fragmentKtx)
implementation(AndroidX.lifecycleViewModelKtx)

View file

@ -187,9 +187,7 @@ class StateBuilder(val actions: UiActions) {
}
fun addTokensToState(uiState: SwapStateHolder, dataState: FoundTokensState): SwapStateHolder {
return uiState.copy(
selectTokenState = tokensDataConverter.convert(dataState),
)
return uiState.copy(selectTokenState = tokensDataConverter.convert(dataState))
}
private fun convertPermissionState(
@ -203,8 +201,8 @@ class StateBuilder(val actions: UiActions) {
is PermissionDataState.PermissionReadyForRequest -> SwapPermissionState.ReadyForRequest(
currency = permissionDataState.currency,
amount = permissionDataState.amount,
walletAddress = permissionDataState.walletAddress,
spenderAddress = permissionDataState.spenderAddress,
walletAddress = getShortAddressValue(permissionDataState.walletAddress),
spenderAddress = getShortAddressValue(permissionDataState.spenderAddress),
fee = permissionDataState.fee,
approveButton = ApprovePermissionButton(
enabled = true,
@ -287,9 +285,21 @@ class StateBuilder(val actions: UiActions) {
)
}
fun clearAlert(uiState: SwapStateHolder): SwapStateHolder {
return uiState.copy(
alert = null,
fun clearAlert(uiState: SwapStateHolder): SwapStateHolder = uiState.copy(alert = null)
private fun getShortAddressValue(fullAddress: String): String {
check(fullAddress.length > ADDRESS_MIN_LENGTH) { "Invalid address" }
val firstAddressPart = fullAddress.substring(startIndex = 0, endIndex = ADDRESS_FIRST_PART_LENGTH)
val secondAddressPart = fullAddress.substring(
startIndex = fullAddress.length - ADDRESS_SECOND_PART_LENGTH,
endIndex = fullAddress.length,
)
return "$firstAddressPart...$secondAddressPart"
}
private companion object {
const val ADDRESS_MIN_LENGTH = 11
const val ADDRESS_FIRST_PART_LENGTH = 7
const val ADDRESS_SECOND_PART_LENGTH = 4
}
}

View file

@ -1,5 +1,6 @@
package com.tangem.feature.swap.ui
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@ -22,7 +23,6 @@ import com.tangem.core.ui.components.SpacerH10
import com.tangem.core.ui.components.SpacerH12
import com.tangem.core.ui.components.SpacerH16
import com.tangem.core.ui.components.SpacerH28
import com.tangem.core.ui.components.SpacerH32
import com.tangem.core.ui.components.atoms.Hand
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.swap.models.ApprovePermissionButton
@ -31,10 +31,9 @@ import com.tangem.feature.swap.models.SwapPermissionState
import com.tangem.feature.swap.presentation.R
@Composable
fun SwapPermissionBottomSheetContent(
data: SwapPermissionState.ReadyForRequest?,
onCancel: () -> Unit,
) {
fun SwapPermissionBottomSheetContent(data: SwapPermissionState.ReadyForRequest, onCancel: () -> Unit) {
BackHandler(onBack = onCancel)
Column(
modifier = Modifier
.background(color = TangemTheme.colors.background.primary)
@ -57,7 +56,7 @@ fun SwapPermissionBottomSheetContent(
Text(
text = stringResource(
id = R.string.swapping_permission_subheader,
data?.currency ?: "",
data.currency,
),
color = TangemTheme.colors.text.secondary,
style = TangemTheme.typography.body2,
@ -67,13 +66,7 @@ fun SwapPermissionBottomSheetContent(
SpacerH16()
ApprovalBottomSheetInfo(
currency = data?.currency ?: "",
amount = data?.amount ?: "",
walletAddress = data?.walletAddress ?: "",
spenderAddress = data?.spenderAddress ?: "",
fee = data?.fee ?: "",
)
ApprovalBottomSheetInfo(data)
SpacerH28()
@ -81,7 +74,7 @@ fun SwapPermissionBottomSheetContent(
text = stringResource(id = R.string.swapping_permission_buttons_approve),
icon = painterResource(id = R.drawable.ic_tangem_24),
modifier = Modifier.fillMaxWidth(),
onClick = { data?.approveButton?.onClick?.invoke() },
onClick = data.approveButton.onClick,
)
SpacerH12()
@ -90,23 +83,17 @@ fun SwapPermissionBottomSheetContent(
text = stringResource(id = R.string.common_cancel),
modifier = Modifier.fillMaxWidth(),
onClick = {
data?.cancelButton?.onClick?.invoke()
data.cancelButton.onClick()
onCancel()
},
)
SpacerH32()
SpacerH16()
}
}
@Composable
private fun ApprovalBottomSheetInfo(
currency: String,
amount: String,
walletAddress: String,
spenderAddress: String,
fee: String,
) {
private fun ApprovalBottomSheetInfo(data: SwapPermissionState.ReadyForRequest) {
Column(
modifier = Modifier
.background(color = TangemTheme.colors.background.primary)
@ -117,13 +104,13 @@ private fun ApprovalBottomSheetInfo(
),
horizontalAlignment = Alignment.CenterHorizontally,
) {
AmountItem(currency = currency, amount = amount)
AmountItem(currency = data.currency, amount = data.amount)
DividerBottomSheet()
WalletAddressItem(walletAddress = walletAddress)
WalletAddressItem(walletAddress = data.walletAddress)
DividerBottomSheet()
SpenderItem(spenderAddress = spenderAddress)
SpenderItem(spenderAddress = data.spenderAddress)
DividerBottomSheet()
FeeItem(fee = fee)
FeeItem(fee = data.fee)
}
}
@ -138,11 +125,11 @@ private fun DividerBottomSheet() {
@Composable
private fun InformationItem(subtitle: String, value: String) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding(TangemTheme.dimens.spacing16),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = subtitle,
@ -151,13 +138,11 @@ private fun InformationItem(subtitle: String, value: String) {
maxLines = 1,
)
SpacerH16()
MiddleEllipsisText(
text = value,
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.body2,
modifier = Modifier.padding(start = TangemTheme.dimens.spacing16),
modifier = Modifier.padding(start = TangemTheme.dimens.spacing16)
)
}
}

View file

@ -1,66 +1,56 @@
package com.tangem.feature.swap.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.BottomSheetScaffold
import androidx.compose.material.BottomSheetState
import androidx.compose.material.BottomSheetValue
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.rememberBottomSheetScaffoldState
import androidx.compose.material.ModalBottomSheetLayout
import androidx.compose.material.ModalBottomSheetValue
import androidx.compose.material.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.swap.models.SwapPermissionState
import com.tangem.feature.swap.models.SwapStateHolder
import com.tangem.feature.swap.presentation.R
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterialApi::class)
@Composable
internal fun SwapScreen(stateHolder: SwapStateHolder) {
val coroutineScope = rememberCoroutineScope()
val bottomSheetScaffoldState = rememberBottomSheetScaffoldState(
bottomSheetState = BottomSheetState(BottomSheetValue.Collapsed),
val bottomSheetState = rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Hidden,
skipHalfExpanded = true,
)
TangemTheme {
BottomSheetScaffold(
modifier = Modifier.background(TangemTheme.colors.background.secondary),
topBar = {
AppBarWithBackButton(
text = stringResource(R.string.swapping_swap),
onBackClick = stateHolder.onBackClicked,
iconRes = R.drawable.ic_close_24,
)
},
ModalBottomSheetLayout(
sheetContent = {
SwapPermissionBottomSheetContent(
data = stateHolder.permissionState as? SwapPermissionState.ReadyForRequest,
onCancel = { coroutineScope.launch { bottomSheetScaffoldState.bottomSheetState.collapse() } },
)
if (stateHolder.permissionState is SwapPermissionState.ReadyForRequest) {
SwapPermissionBottomSheetContent(
data = stateHolder.permissionState,
onCancel = { coroutineScope.launch { bottomSheetState.hide() } },
)
} else {
// Required "else" block to prevent compose crash
Box(modifier = Modifier.fillMaxSize())
}
},
scaffoldState = bottomSheetScaffoldState,
sheetState = bottomSheetState,
sheetShape = RoundedCornerShape(
topStart = TangemTheme.dimens.radius16,
topEnd = TangemTheme.dimens.radius16,
),
sheetElevation = TangemTheme.dimens.elevation24,
sheetPeekHeight = TangemTheme.dimens.size0,
content = {
SwapScreenContent(
state = stateHolder,
onPermissionWarningClick = {
val isBottomSheetReady = !bottomSheetState.isVisible &&
stateHolder.permissionState is SwapPermissionState.ReadyForRequest
coroutineScope.launch {
if (bottomSheetScaffoldState.bottomSheetState.isCollapsed &&
stateHolder.permissionState is SwapPermissionState.ReadyForRequest
) {
bottomSheetScaffoldState.bottomSheetState.expand()
} else {
bottomSheetScaffoldState.bottomSheetState.collapse()
}
if (isBottomSheetReady) bottomSheetState.show() else bottomSheetState.hide()
}
},
)

View file

@ -37,6 +37,7 @@ import com.tangem.core.ui.components.SimpleOkDialog
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.appbar.AppBarWithBackButton
import com.tangem.core.ui.components.keyboardAsState
import com.tangem.core.ui.extensions.getActiveIconRes
import com.tangem.core.ui.res.TangemTheme
@ -59,62 +60,69 @@ internal fun SwapScreenContent(state: SwapStateHolder, onPermissionWarningClick:
.fillMaxSize()
.background(color = TangemTheme.colors.background.secondary),
) {
Column(
modifier = Modifier
.fillMaxWidth()
.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),
) {
MainInfo(state)
Column {
AppBarWithBackButton(
text = stringResource(R.string.swapping_swap),
onBackClick = state.onBackClicked,
iconRes = R.drawable.ic_close_24,
)
Column(
modifier = Modifier
.fillMaxWidth()
.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),
) {
MainInfo(state)
FeeItem(feeState = state.fee, currency = state.networkCurrency)
FeeItem(feeState = state.fee, currency = state.networkCurrency)
if (state.warnings.isNotEmpty()) {
SwapWarnings(
warnings = state.warnings,
onPermissionWarningClick = onPermissionWarningClick,
)
}
if (state.warnings.isNotEmpty()) {
SwapWarnings(
warnings = state.warnings,
onPermissionWarningClick = onPermissionWarningClick,
)
}
if (state.permissionState is SwapPermissionState.InProgress) {
CardWithIcon(
title = stringResource(id = R.string.swapping_pending_transaction_title),
description = stringResource(id = R.string.swapping_pending_transaction_subtitle),
icon = {
CircularProgressIndicator(
modifier = Modifier
.size(TangemTheme.dimens.size16),
color = TangemTheme.colors.icon.primary1,
strokeWidth = TangemTheme.dimens.size2,
)
},
)
}
if (state.permissionState is SwapPermissionState.InProgress) {
CardWithIcon(
title = stringResource(id = R.string.swapping_pending_transaction_title),
description = stringResource(id = R.string.swapping_pending_transaction_subtitle),
icon = {
CircularProgressIndicator(
modifier = Modifier
.size(TangemTheme.dimens.size16),
color = TangemTheme.colors.icon.primary1,
strokeWidth = TangemTheme.dimens.size2,
)
},
)
}
if (state.warnings.any { it is SwapWarning.InsufficientFunds }) {
PrimaryButton(
modifier = Modifier.fillMaxWidth(),
text = stringResource(id = R.string.swapping_insufficient_funds),
enabled = false,
showProgress = state.swapButton.loading,
onClick = state.swapButton.onClick,
)
} else {
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,
)
if (state.warnings.any { it is SwapWarning.InsufficientFunds }) {
PrimaryButton(
modifier = Modifier.fillMaxWidth(),
text = stringResource(id = R.string.swapping_insufficient_funds),
enabled = false,
showProgress = state.swapButton.loading,
onClick = state.swapButton.onClick,
)
} else {
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,
)
}
}
}