Updated on 2026-08-14

This commit is contained in:
Tangem 2026-03-11 15:16:56 +05:00
parent 5f4dfefc2e
commit 8411911466
13 changed files with 858 additions and 304 deletions

View file

@ -15,8 +15,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
@ -28,10 +26,10 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.Dp
import com.tangem.core.ui.components.BottomFade
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
import com.tangem.core.ui.components.haze.hazeEffectTangem
import com.tangem.core.ui.components.haze.hazeSourceTangem
import com.tangem.core.ui.ds.button.TangemButton
@ -75,7 +73,6 @@ internal fun OrganizeTokensContent(
onDismissRequest = onDismiss,
content = TangemBottomSheetConfigContent.Empty,
),
addBottomInsets = false,
containerColor = TangemTheme.colors2.surface.level2,
title = {
TangemTopBar(
@ -99,11 +96,14 @@ internal fun OrganizeTokensContent(
},
)
},
footer = {
BottomButtons(organizeTokensUM = organizeTokensUM)
},
content = {
TokenList(
organizeTokensUM = organizeTokensUM,
dragAndDropIntents = dragAndDropIntents,
modifier = Modifier.hazeSourceTangem(hazeState),
modifier = Modifier.hazeSourceTangem(hazeState, zIndex = -1f),
)
},
)
@ -121,9 +121,7 @@ private fun TokenList(
val hapticFeedback = LocalHapticFeedback.current
val tokenList = organizeTokensUM.tokenList
Box(
modifier = modifier
.fillMaxSize()
.background(TangemTheme.colors2.surface.level2),
modifier = modifier.background(TangemTheme.colors2.surface.level2),
) {
val onDragEnd: (Int, Int) -> Unit = remember {
{ _, _ ->
@ -175,20 +173,11 @@ private fun TokenList(
isBalanceHidden = organizeTokensUM.isBalanceHidden,
)
}
item {
SpacerH(TangemTheme.dimens2.x20)
}
}
BottomFade(
gradientBrush = Brush.verticalGradient(
colors = listOf(
Color.Transparent,
TangemTheme.colors2.surface.level2.copy(0.9f),
TangemTheme.colors2.surface.level2,
),
),
modifier = Modifier.align(Alignment.BottomCenter),
)
BottomButtons(organizeTokensUM = organizeTokensUM)
}
}

View file

@ -11,6 +11,7 @@ import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
@ -20,16 +21,17 @@ import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.geometry.*
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.RoundRect
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Outline
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
@ -40,19 +42,16 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.*
import androidx.paging.compose.collectAsLazyPagingItems
import com.tangem.common.ui.bottomsheet.chooseaddress.ChooseAddressBottomSheet
import com.tangem.common.ui.bottomsheet.chooseaddress.ChooseAddressBottomSheetConfig
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheet
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig
import com.tangem.common.ui.expressStatus.expressTransactionsItems
import com.tangem.core.ui.components.atoms.Hand
import com.tangem.core.ui.components.atoms.handComposableComponentHeight
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheetDraggableHeaderLegacy
import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState
import com.tangem.core.ui.components.containers.pullToRefresh.TangemPullToRefreshContainer
import com.tangem.core.ui.components.rememberIsKeyboardVisible
@ -61,6 +60,7 @@ import com.tangem.core.ui.components.snackbar.CopiedTextSnackbar
import com.tangem.core.ui.components.snackbar.TangemSnackbar
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.core.ui.event.StateEvent
import com.tangem.core.ui.extensions.softLayerShadow
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.LocalMainBottomSheetColor
import com.tangem.core.ui.res.LocalWindowSize
@ -290,15 +290,8 @@ private inline fun BaseScaffoldWithMarkets(
crossinline bottomSheetContent: @Composable () -> Unit,
crossinline content: @Composable (PaddingValues) -> Unit,
) {
val bottomSheetState = rememberTangemStandardBottomSheetState()
val isKeyboardVisible by rememberIsKeyboardVisible()
val scaffoldState = rememberTangemBottomSheetScaffoldState(
bottomSheetState = bottomSheetState,
snackbarHostState = snackbarHostState,
)
val density = LocalDensity.current
val bottomBarHeight = with(density) { WindowInsets.systemBars.getBottom(density = this).toDp() }
val statusBarHeight = with(density) { WindowInsets.statusBars.getTop(density = this).toDp() }
@ -308,6 +301,12 @@ private inline fun BaseScaffoldWithMarkets(
val coroutineScope = rememberCoroutineScope()
val background = TangemTheme.colors.background.tertiary
val bottomSheetState = rememberTangemStandardBottomSheetState()
val scaffoldState = rememberTangemBottomSheetScaffoldState(
bottomSheetState = bottomSheetState,
snackbarHostState = snackbarHostState,
)
val showMarketsHint by remember {
derivedStateOf {
// Show hint only when there are items in the list
@ -321,7 +320,7 @@ private inline fun BaseScaffoldWithMarkets(
CompositionLocalProvider(
LocalMainBottomSheetColor provides remember(background) { mutableStateOf(background) },
) {
val backgroundColor = LocalMainBottomSheetColor.current
val backgroundColor by LocalMainBottomSheetColor.current
var isSearchFieldFocused by remember { mutableStateOf(false) }
val isNavBarVisible = remember { mutableStateOf(true) }
@ -343,43 +342,61 @@ private inline fun BaseScaffoldWithMarkets(
.navigationBarsPadding(),
)
},
containerColor = TangemTheme.colors.background.secondary,
sheetContainerColor = backgroundColor.value,
scaffoldState = scaffoldState,
sheetPeekHeight = peekHeight,
sheetShape = TangemTheme.shapes.bottomSheetLarge,
sheetContent = {
// hide bottom sheet when back pressed
BackHandler(
isKeyboardVisible.not() &&
bottomSheetState.currentValue == TangemSheetValue.Expanded,
) {
coroutineScope.launch { bottomSheetState.partialExpand() }
}
Column(
containerColor = TangemTheme.colors.background.secondary,
scaffoldState = scaffoldState,
bottomSheet = {
CustomBottomSheet(
state = scaffoldState.bottomSheetState,
peekHeight = peekHeight,
modifier = Modifier
// expand bottom sheet when clicked on the header
.clickable(
enabled = bottomSheetState.currentValue == TangemSheetValue.PartiallyExpanded,
indication = null,
interactionSource = null,
.softLayerShadow(
radius = 8.dp,
color = Color.Black.copy(
alpha = if (isSystemInDarkTheme()) .16f else .08f,
),
shape = TangemTheme.shapes.bottomSheetLarge,
offset = DpOffset(x = 0.dp, y = (-4).dp),
isAlphaContentClip = true,
)
.clip(TangemTheme.shapes.bottomSheetLarge)
.background(backgroundColor),
content = {
// hide bottom sheet when back pressed
BackHandler(
isKeyboardVisible.not() &&
bottomSheetState.currentValue == TangemSheetValue.Expanded,
) {
coroutineScope.launch { bottomSheetState.expand() }
coroutineScope.launch { bottomSheetState.partialExpand() }
}
.sizeIn(maxHeight = maxHeight - statusBarHeight),
) {
Hand(Modifier.drawBehind { drawRect(backgroundColor.value) })
Box(
modifier = Modifier
.onFocusChanged {
isSearchFieldFocused = it.isFocused
},
) {
bottomSheetContent()
}
}
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxWidth()
// expand bottom sheet when clicked on the header
.clickable(
enabled = bottomSheetState.currentValue == TangemSheetValue.PartiallyExpanded,
indication = null,
interactionSource = null,
) {
coroutineScope.launch { bottomSheetState.expand() }
}
.sizeIn(maxHeight = maxHeight - statusBarHeight),
) {
TangemBottomSheetDraggableHeaderLegacy(backgroundColor)
Box(
modifier = Modifier
.onFocusChanged {
isSearchFieldFocused = it.isFocused
},
) {
bottomSheetContent()
}
}
},
)
},
content = { paddingValues ->
Box {
@ -428,7 +445,7 @@ private inline fun BaseScaffoldWithMarkets(
Box(
Modifier
.align(Alignment.BottomCenter)
.background(backgroundColor.value)
.background(backgroundColor)
.height(bottomBarHeight)
.fillMaxWidth(),
)

View file

@ -17,6 +17,7 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.PagerState
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
@ -25,8 +26,10 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusState
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.input.pointer.pointerInput
@ -39,9 +42,10 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.arkivanov.decompose.ExperimentalDecomposeApi
import com.tangem.core.ui.components.atoms.Hand
import com.tangem.core.ui.components.BottomFade
import com.tangem.core.ui.components.atoms.handComposableComponentHeight
import com.tangem.core.ui.components.background.northernlights.NorthernLightsBackground
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheetDraggableHeader
import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState
import com.tangem.core.ui.components.containers.pullToRefresh.TangemPullToRefreshSlidingContainer
import com.tangem.core.ui.components.haze.hazeSourceTangem
@ -295,25 +299,20 @@ private inline fun BaseScaffoldWithMarkets(
crossinline bottomSheetContent: @Composable () -> Unit,
crossinline content: @Composable (PaddingValues, TangemSheetState) -> Unit,
) {
val bottomSheetState = rememberTangemStandardBottomSheetState()
val isKeyboardVisible by rememberIsKeyboardVisible()
val scaffoldState = rememberTangemBottomSheetScaffoldState(bottomSheetState = bottomSheetState)
val density = LocalDensity.current
val bottomBarHeight = with(density) { WindowInsets.systemBars.getBottom(density = this).toDp() }
val statusBarHeight = with(density) { WindowInsets.statusBars.getTop(density = this).toDp() }
val peekHeight = bottomSheetHeaderHeightProvider() + handComposableComponentHeight + bottomBarHeight
val maxHeight = LocalWindowSize.current.height
val peekHeight = bottomSheetHeaderHeightProvider() + TangemTheme.dimens2.x3 + bottomBarHeight
val coroutineScope = rememberCoroutineScope()
val background = TangemTheme.colors2.surface.level3
val bottomSheetState = rememberTangemStandardBottomSheetState()
val scaffoldState = rememberTangemBottomSheetScaffoldState(bottomSheetState = bottomSheetState)
CompositionLocalProvider(
LocalMainBottomSheetColor provides remember(background) { mutableStateOf(background) },
) {
val backgroundColor = LocalMainBottomSheetColor.current
val backgroundColor by LocalMainBottomSheetColor.current
var isSearchFieldFocused by remember { mutableStateOf(false) }
val isNavBarVisible = remember { mutableStateOf(true) }
@ -327,41 +326,18 @@ private inline fun BaseScaffoldWithMarkets(
Box(modifier = modifier) {
TangemBottomSheetScaffold(
containerColor = Color.Unspecified,
sheetContainerColor = backgroundColor.value,
scaffoldState = scaffoldState,
sheetPeekHeight = peekHeight,
sheetShape = TangemTheme.shapes.bottomSheetLarge,
sheetContent = {
// hide bottom sheet when back pressed
BackHandler(
isKeyboardVisible.not() &&
bottomSheetState.currentValue == TangemSheetValue.Expanded,
bottomSheet = {
BottomSheet(
bottomSheetState = bottomSheetState,
backgroundColor = backgroundColor,
peekHeight = peekHeight,
onFocusChange = { focusState ->
isSearchFieldFocused = focusState.isFocused
},
) {
coroutineScope.launch { bottomSheetState.partialExpand() }
}
Column(
modifier = Modifier
// expand bottom sheet when clicked on the header
.clickable(
enabled = bottomSheetState.currentValue == TangemSheetValue.PartiallyExpanded,
indication = null,
interactionSource = null,
) {
coroutineScope.launch { bottomSheetState.expand() }
}
.sizeIn(maxHeight = maxHeight - statusBarHeight),
) {
Hand(Modifier.drawBehind { drawRect(backgroundColor.value) })
Box(
modifier = Modifier
.onFocusChanged {
isSearchFieldFocused = it.isFocused
},
) {
bottomSheetContent()
}
bottomSheetContent()
}
},
content = { paddingValues ->
@ -385,7 +361,7 @@ private inline fun BaseScaffoldWithMarkets(
Box(
Modifier
.align(Alignment.BottomCenter)
.background(backgroundColor.value)
.background(backgroundColor)
.height(bottomBarHeight)
.fillMaxWidth(),
)
@ -400,6 +376,81 @@ private inline fun BaseScaffoldWithMarkets(
}
}
@Composable
private fun BottomSheet(
bottomSheetState: TangemSheetState,
backgroundColor: Color,
peekHeight: Dp,
onFocusChange: (FocusState) -> Unit,
bottomSheetContent: @Composable () -> Unit,
) {
val isKeyboardVisible by rememberIsKeyboardVisible()
val coroutineScope = rememberCoroutineScope()
val density = LocalDensity.current
val statusBarHeight = with(density) { WindowInsets.statusBars.getTop(density = this).toDp() }
val maxHeight = LocalWindowSize.current.height
val shape = RoundedCornerShape(
topStart = TangemTheme.dimens2.x8,
topEnd = TangemTheme.dimens2.x8,
)
CustomBottomSheet(
state = bottomSheetState,
peekHeight = peekHeight,
content = {
// hide bottom sheet when back pressed
BackHandler(
isKeyboardVisible.not() &&
bottomSheetState.currentValue == TangemSheetValue.Expanded,
) {
coroutineScope.launch { bottomSheetState.partialExpand() }
}
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
// expand bottom sheet when clicked on the header
.clickable(
enabled = bottomSheetState.currentValue == TangemSheetValue.PartiallyExpanded,
indication = null,
interactionSource = null,
) {
coroutineScope.launch { bottomSheetState.expand() }
}
.sizeIn(maxHeight = maxHeight - statusBarHeight),
) {
Box(modifier = Modifier.fillMaxWidth()) {
BottomFade(
gradientBrush = Brush.verticalGradient(
colors = listOf(
TangemTheme.colors2.shadow.min,
TangemTheme.colors2.shadow.max,
),
),
modifier = Modifier
.offset(y = TangemTheme.dimens2.x5.unaryMinus()),
)
Column(
horizontalAlignment = Alignment.CenterHorizontally,
) {
TangemBottomSheetDraggableHeader()
Box(
modifier = Modifier
.fillMaxWidth()
.clip(shape)
.background(backgroundColor)
.onFocusChanged(onFocusChange),
) {
bottomSheetContent()
}
}
}
}
},
)
}
@Composable
private fun BottomSheetScrim(color: Color, visible: Boolean, onDismissRequest: () -> Unit) {
val alpha by animateFloatAsState(