Updated on 2026-08-14

This commit is contained in:
Tangem 2023-10-22 09:55:02 +08:00
parent 1a5401519e
commit e881eaa3de
17 changed files with 75 additions and 110 deletions

View file

@ -3,29 +3,52 @@ package com.tangem.core.ui.components.bottomsheets
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.SheetState
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.*
import com.tangem.core.ui.res.TangemTheme
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
/**
* Tangem bottom sheet with custom draggable header and config
*
* @param config data model containing logic and ui models
* @param config data model containing logic and ui models
* @param content custom bottom sheet content
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TangemBottomSheet(
inline fun <reified T : TangemBottomSheetConfigContent> TangemBottomSheet(
config: TangemBottomSheetConfig,
content: @Composable ColumnScope.(TangemBottomSheetConfigContent) -> Unit,
crossinline content: @Composable ColumnScope.(T) -> Unit,
) {
ModalBottomSheet(
onDismissRequest = config.onDismissRequest,
sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true),
containerColor = TangemTheme.colors.background.primary,
shape = TangemTheme.shapes.bottomSheetLarge,
dragHandle = { TangemBottomSheetDraggableHeader() },
) {
content(config.content)
var isVisible by remember { mutableStateOf(value = config.isShow) }
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
if (isVisible && config.content is T) {
ModalBottomSheet(
onDismissRequest = config.onDismissRequest,
sheetState = sheetState,
containerColor = TangemTheme.colors.background.primary,
shape = TangemTheme.shapes.bottomSheetLarge,
dragHandle = { TangemBottomSheetDraggableHeader() },
) {
content(config.content)
}
}
LaunchedEffect(key1 = config.isShow) {
if (config.isShow) {
isVisible = true
} else {
sheetState.collapse { isVisible = false }
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
suspend fun SheetState.collapse(onCollapsed: () -> Unit) {
coroutineScope {
launch { hide() }.invokeOnCompletion { onCollapsed() }
}
}

View file

@ -1,6 +1,9 @@
package com.tangem.core.ui.components.bottomsheets
import androidx.compose.runtime.Immutable
/**
* General interface for bottom sheet config model
*/
@Immutable
interface TangemBottomSheetConfigContent

View file

@ -12,12 +12,8 @@ import com.tangem.core.ui.res.TangemTheme
@Composable
fun ChooseAddressBottomSheet(config: TangemBottomSheetConfig) {
if (config.content is ChooseAddressBottomSheetConfig && config.isShow) {
TangemBottomSheet(config) { content ->
ChooseAddressBottomSheetContent(
content = content as ChooseAddressBottomSheetConfig,
)
}
TangemBottomSheet(config) { content: ChooseAddressBottomSheetConfig ->
ChooseAddressBottomSheetContent(content = content)
}
}

View file

@ -2,8 +2,9 @@ package com.tangem.core.ui.components.bottomsheets.chooseaddress
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.components.bottomsheets.tokenreceive.AddressModel
import kotlinx.collections.immutable.ImmutableList
class ChooseAddressBottomSheetConfig(
val addressModels: List<AddressModel>,
val addressModels: ImmutableList<AddressModel>,
val onClick: (AddressModel) -> Unit,
) : TangemBottomSheetConfigContent

View file

@ -33,12 +33,8 @@ import com.tangem.core.ui.res.TangemTheme
@Composable
fun TokenReceiveBottomSheet(config: TangemBottomSheetConfig) {
if (config.content is TokenReceiveBottomSheetConfig && config.isShow) {
TangemBottomSheet(config) { content ->
TokenReceiveBottomSheetContent(
content = content as TokenReceiveBottomSheetConfig,
)
}
TangemBottomSheet(config) { content: TokenReceiveBottomSheetConfig ->
TokenReceiveBottomSheetContent(content = content)
}
}

View file

@ -23,6 +23,7 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.component
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.txhistory.TokenDetailsLoadedTxHistoryConverter
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.txhistory.TokenDetailsLoadingTxHistoryConverter
import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@ -188,12 +189,14 @@ internal class TokenDetailsStateFactory(
isShow = true,
onDismissRequest = clickIntents::onDismissBottomSheet,
content = ChooseAddressBottomSheetConfig(
addressModels = addresses.map {
AddressModel(
value = it.value,
type = AddressModel.Type.valueOf(it.type.name),
)
},
addressModels = addresses
.map { address ->
AddressModel(
value = address.value,
type = AddressModel.Type.valueOf(address.type.name),
)
}
.toImmutableList(),
onClick = clickIntents::onAddressTypeSelected,
),
),

View file

@ -308,8 +308,6 @@ internal object WalletPreviewData {
}
val actionsBottomSheet = ActionsBottomSheetConfig(
isShow = true,
onDismissRequest = {},
actions = listOf(
TokenActionButtonConfig(
text = TextReference.Str("Send"),
@ -386,7 +384,6 @@ internal object WalletPreviewData {
WalletNotification.Warning.NetworksUnreachable,
),
bottomSheetConfig = bottomSheet,
tokenActionsBottomSheet = actionsBottomSheet,
onManageTokensClick = {},
event = consumedEvent(),
isBalanceHidden = false,

View file

@ -1,17 +1,13 @@
package com.tangem.feature.wallet.presentation.wallet.state
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import kotlinx.collections.immutable.ImmutableList
/**
* Config for the token actions bottom sheet
*
* @property isShow flag that determine if bottom sheet is shown
* @property onDismissRequest lambda be invoked when bottom sheet is dismissed
* @property actions actions
*
* @property actions actions
*/
internal data class ActionsBottomSheetConfig(
val isShow: Boolean,
val onDismissRequest: () -> Unit,
val actions: ImmutableList<TokenActionButtonConfig>,
)
) : TangemBottomSheetConfigContent

View file

@ -28,7 +28,6 @@ internal sealed class WalletMultiCurrencyState : WalletState.ContentState() {
override val event: StateEvent<WalletEvent> = consumedEvent(),
override val isBalanceHidden: Boolean,
val isManageTokensAvailable: Boolean = true,
val tokenActionsBottomSheet: ActionsBottomSheetConfig?,
val onManageTokensClick: () -> Unit,
) : WalletMultiCurrencyState()

View file

@ -56,7 +56,6 @@ internal class WalletSkeletonStateConverter(
tokensListState = WalletTokensListState.Loading(),
notifications = persistentListOf(),
bottomSheetConfig = null,
tokenActionsBottomSheet = null,
onManageTokensClick = clickIntents::onManageTokensClick,
isBalanceHidden = isBalanceHiddenProvider(),
)

View file

@ -220,16 +220,9 @@ internal class WalletStateFactory(
}
fun getStateWithTokenActionBottomSheet(tokenActions: TokenActionsState): WalletState {
return when (val state = currentStateProvider() as WalletState.ContentState) {
is WalletMultiCurrencyState.Content -> state.copy(
tokenActionsBottomSheet = ActionsBottomSheetConfig(
isShow = true,
actions = tokenActionsProvider.provideActions(tokenActions),
onDismissRequest = clickIntents::onDismissActionsBottomSheet,
),
)
else -> state
}
return getStateWithOpenWalletBottomSheet(
content = ActionsBottomSheetConfig(actions = tokenActionsProvider.provideActions(tokenActions)),
)
}
fun getLoadingTxHistoryState(itemsCountEither: Either<TxHistoryStateError, Int>): WalletState {

View file

@ -52,7 +52,6 @@ internal class WalletsUnlockStateConverter(
tokensListState = WalletTokensListState.Loading(),
notifications = persistentListOf(),
bottomSheetConfig = null,
tokenActionsBottomSheet = null,
onManageTokensClick = clickIntents::onManageTokensClick,
isBalanceHidden = isBalanceHidden,
)

View file

@ -28,10 +28,7 @@ import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
import com.tangem.feature.wallet.presentation.wallet.state.WalletAlertState
import com.tangem.feature.wallet.presentation.wallet.state.WalletMultiCurrencyState
import com.tangem.feature.wallet.presentation.wallet.state.WalletSingleCurrencyState
import com.tangem.feature.wallet.presentation.wallet.state.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.*
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletBottomSheetConfig
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletPullToRefreshConfig
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTokensListState
@ -242,20 +239,11 @@ private fun ManageTokensButton(onManageTokensClick: () -> Unit) {
@Composable
private fun WalletBottomSheets(state: WalletState) {
val bottomSheetConfig = (state as? WalletState.ContentState)?.bottomSheetConfig
if (bottomSheetConfig != null && bottomSheetConfig.isShow) {
if (bottomSheetConfig != null) {
when (bottomSheetConfig.content) {
is WalletBottomSheetConfig -> {
WalletBottomSheet(config = bottomSheetConfig)
}
is TokenReceiveBottomSheetConfig -> {
TokenReceiveBottomSheet(config = bottomSheetConfig)
}
}
}
(state as? WalletMultiCurrencyState.Content)?.let { multiCurrencyState ->
if (multiCurrencyState.tokenActionsBottomSheet != null && multiCurrencyState.tokenActionsBottomSheet.isShow) {
TokenActionsBottomSheet(config = multiCurrencyState.tokenActionsBottomSheet)
is WalletBottomSheetConfig -> WalletBottomSheet(config = bottomSheetConfig)
is TokenReceiveBottomSheetConfig -> TokenReceiveBottomSheet(config = bottomSheetConfig)
is ActionsBottomSheetConfig -> TokenActionsBottomSheet(config = bottomSheetConfig)
}
}
}

View file

@ -2,16 +2,14 @@ package com.tangem.feature.wallet.presentation.wallet.ui.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.BottomSheetDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.components.SimpleSettingsRow
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
@ -19,24 +17,16 @@ import com.tangem.feature.wallet.presentation.wallet.state.ActionsBottomSheetCon
import com.tangem.feature.wallet.presentation.wallet.state.TokenActionButtonConfig
import kotlinx.collections.immutable.ImmutableList
@OptIn(ExperimentalMaterial3Api::class)
@Composable
internal fun TokenActionsBottomSheet(config: ActionsBottomSheetConfig) {
ModalBottomSheet(
onDismissRequest = config.onDismissRequest,
sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true),
containerColor = TangemTheme.colors.background.primary,
dragHandle = { BottomSheetDefaults.DragHandle() },
) {
ActionsBottomSheetContent(config.actions)
internal fun TokenActionsBottomSheet(config: TangemBottomSheetConfig) {
TangemBottomSheet<ActionsBottomSheetConfig>(config = config) {
ActionsBottomSheetContent(actions = it.actions)
}
}
@Composable
private fun ActionsBottomSheetContent(actions: ImmutableList<TokenActionButtonConfig>) {
Column(
modifier = Modifier.background(TangemTheme.colors.background.primary),
) {
Column(modifier = Modifier.background(TangemTheme.colors.background.primary)) {
actions.forEach { action ->
if (action.enabled) {
SimpleSettingsRow(

View file

@ -30,10 +30,8 @@ import com.tangem.feature.wallet.presentation.wallet.state.components.WalletBott
*/
@Composable
internal fun WalletBottomSheet(config: TangemBottomSheetConfig) {
TangemBottomSheet(config) { content ->
BottomSheetContent(
config = content as WalletBottomSheetConfig,
)
TangemBottomSheet(config) { content: WalletBottomSheetConfig ->
BottomSheetContent(config = content)
}
}

View file

@ -41,8 +41,6 @@ internal interface WalletClickIntents {
fun onTokenItemLongClick(cryptoCurrencyStatus: CryptoCurrencyStatus)
fun onDismissActionsBottomSheet()
fun onRenameClick(userWalletId: UserWalletId, name: String)
fun onDeleteBeforeConfirmationClick(userWalletId: UserWalletId)

View file

@ -907,12 +907,8 @@ internal class WalletViewModel @Inject constructor(
viewModelScope.launch(dispatchers.io) {
removeCurrencyUseCase(userWallet.walletId, cryptoCurrencyStatus.currency)
.fold(
ifLeft = {
showSnackbar(resourceReference(R.string.common_error))
},
ifRight = {
onDismissActionsBottomSheet()
},
ifLeft = { showToast(resourceReference(R.string.common_error)) },
ifRight = { uiState = stateFactory.getStateWithClosedBottomSheet() },
)
}
}
@ -937,16 +933,6 @@ internal class WalletViewModel @Inject constructor(
uiState = stateFactory.getStateWithClosedBottomSheet()
}
override fun onDismissActionsBottomSheet() {
(uiState as? WalletMultiCurrencyState.Content)?.let { state ->
uiState = state.copy(
tokenActionsBottomSheet = state.tokenActionsBottomSheet?.copy(
isShow = false,
),
)
}
}
override fun onTransactionClick(txHash: String) {
viewModelScope.launch(dispatchers.io) {
val wallet = getWallet(
@ -1005,7 +991,7 @@ internal class WalletViewModel @Inject constructor(
}
}
private fun showSnackbar(message: TextReference) {
private fun showToast(message: TextReference) {
uiState = stateFactory.getStateAndTriggerEvent(
state = uiState,
event = WalletEvent.ShowToast(message),