Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,6 @@ import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.AnchoredDraggableState
import androidx.compose.foundation.gestures.DraggableAnchors
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.anchoredDraggable
import androidx.compose.foundation.gestures.snapTo
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
Expand All @@ -19,41 +14,32 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.GroupAdd
import androidx.compose.material.icons.rounded.PersonRemove
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.layout.layout
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
Expand Down Expand Up @@ -84,14 +70,14 @@ import com.getcode.ui.components.AppBarDefaults
import com.getcode.ui.components.AppBarWithTitle
import com.getcode.ui.components.CircularIconButton
import com.getcode.ui.components.SearchInput
import com.getcode.ui.components.SwipeAction
import com.getcode.ui.components.SwipeActionRow
import com.getcode.ui.core.rememberedClickable
import com.getcode.ui.core.verticalScrollStateGradient
import com.getcode.ui.theme.CodeCircularProgressIndicator
import com.getcode.ui.theme.CodeScaffold
import com.getcode.view.LoadingSuccessState
import kotlin.math.abs
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.launch


@Composable
Expand Down Expand Up @@ -264,16 +250,42 @@ private fun ContactList(
items[index + 1] is ContactListItem.Header

if (isPickerMode) {
SwipeToRevealItem(
modifier = Modifier.animateItem(),
onDelete = { onItemDismissed(item) },
) {
ContactRowItem(
contact = item.contact,
isOnFlipcash = item.isOnFlipcash,
showDivider = !isLastInSection,
onClick = { onItemClick(item) },
)
Column(modifier = Modifier.animateItem()) {
SwipeActionRow(
actions = listOf(
SwipeAction(
background = CodeTheme.colors.error,
onTriggered = { onItemDismissed(item) },
) {
Icon(
imageVector = Icons.Rounded.PersonRemove,
contentDescription = null,
tint = Color.White,
modifier = Modifier.requiredSize(CodeTheme.dimens.staticGrid.x5),
)
}
),
stateKey = item.contact.e164,
) {
ContactRowItem(
contact = item.contact,
isOnFlipcash = item.isOnFlipcash,
showDivider = false,
onClick = { onItemClick(item) },
)
}
if (!isLastInSection) {
HorizontalDivider(
color = CodeTheme.colors.divider,
modifier = Modifier
.fillMaxWidth()
.height(1.dp)
.padding(
start = CodeTheme.dimens.inset + CodeTheme.dimens.staticGrid.x8 + CodeTheme.dimens.grid.x3,
end = CodeTheme.dimens.inset,
),
)
}
}
} else {
ContactRowItem(
Expand All @@ -290,101 +302,6 @@ private fun ContactList(
}
}

private enum class RevealValue { Settled, Revealed, Dismissed }

@Composable
private fun SwipeToRevealItem(
onDelete: () -> Unit,
modifier: Modifier = Modifier,
content: @Composable () -> Unit,
) {
val density = LocalDensity.current
val scope = rememberCoroutineScope()
val actionWidth = CodeTheme.dimens.staticGrid.x10 + CodeTheme.dimens.inset * 2
val actionWidthPx = with(density) { actionWidth.toPx() }

val state = remember { AnchoredDraggableState(initialValue = RevealValue.Settled) }
var rowWidthPx by remember { mutableFloatStateOf(0f) }

LaunchedEffect(rowWidthPx, actionWidthPx) {
if (rowWidthPx > 0f) {
state.updateAnchors(DraggableAnchors {
RevealValue.Settled at 0f
RevealValue.Revealed at -actionWidthPx
RevealValue.Dismissed at -rowWidthPx
})
}
}

val currentOnDelete by rememberUpdatedState(onDelete)
LaunchedEffect(state.currentValue) {
if (state.currentValue == RevealValue.Dismissed) {
currentOnDelete()
}
}

val actionPadding = CodeTheme.dimens.inset
val minActionSize = CodeTheme.dimens.staticGrid.x10

Box(
modifier = modifier
.clipToBounds()
.onSizeChanged { rowWidthPx = it.width.toFloat() },
) {
// Action area — grows from circle to rounded rect as swipe progresses
Box(
modifier = Modifier
.matchParentSize()
.layout { measurable, constraints ->
val absOffset = abs(state.offset)
val paddingPx = actionPadding.roundToPx()
val minPx = minActionSize.roundToPx()

val w = (absOffset.toInt() - paddingPx * 2).coerceAtLeast(minPx)
val h = (constraints.maxHeight - paddingPx * 2).coerceAtLeast(minPx)

val placeable = measurable.measure(
constraints.copy(
minWidth = w, maxWidth = w,
minHeight = h, maxHeight = h,
)
)
layout(constraints.maxWidth, constraints.maxHeight) {
placeable.place(
constraints.maxWidth - placeable.width - paddingPx,
(constraints.maxHeight - placeable.height) / 2,
)
}
}
.clip(RoundedCornerShape(50))
.background(CodeTheme.colors.error)
.rememberedClickable {
scope.launch {
state.snapTo(RevealValue.Settled)
}
onDelete()
},
contentAlignment = Alignment.Center,
) {
Icon(
painter = painterResource(R.drawable.ic_delete),
contentDescription = stringResource(R.string.action_remove),
tint = Color.White,
modifier = Modifier.requiredSize(CodeTheme.dimens.staticGrid.x5),
)
}

// Foreground content that slides
Box(
modifier = Modifier
.offset { IntOffset(state.offset.toInt(), 0) }
.anchoredDraggable(state, Orientation.Horizontal),
) {
content()
}
}
}

@Composable
private fun ContactGroupHeader(text: String, modifier: Modifier = Modifier) {
Box(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.flipcash.app.currency.internal.components

import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
Expand All @@ -11,23 +10,10 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.material.DismissDirection
import androidx.compose.material.DismissState
import androidx.compose.material.DismissValue
import androidx.compose.material.Divider
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.FixedThreshold
import androidx.compose.material.Icon
import androidx.compose.material.SwipeToDismiss
import androidx.compose.material.Text
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
Expand All @@ -37,10 +23,9 @@ import androidx.compose.ui.unit.dp
import com.flipcash.app.currency.internal.CurrencyListItem
import com.flipcash.features.currency.R
import com.getcode.theme.CodeTheme
import com.getcode.ui.components.SwipeActionRow
import com.getcode.ui.core.rememberedClickable
import kotlinx.coroutines.delay

@OptIn(ExperimentalMaterialApi::class)
@Composable
internal fun ListRowItem(
item: CurrencyListItem.RegionCurrencyItem,
Expand All @@ -49,34 +34,7 @@ internal fun ListRowItem(
onRemoved: () -> Unit,
onClick: () -> Unit
) {

var removed by remember(item) {
mutableStateOf(false)
}

val dismissState = remember(item) {
DismissState(
initialValue = DismissValue.Default,
confirmStateChange = {
if (it == DismissValue.DismissedToStart) {
removed = true
true
} else false
}
)
}

SwipeToDismiss(
modifier = modifier,
state = dismissState,
dismissThresholds = { FixedThreshold(150.dp) },
directions = if (item.isRecent) setOf(DismissDirection.EndToStart) else emptySet(),
background = {
if (item.isRecent) {
DismissBackground(dismissState)
}
}
) {
val rowContent: @Composable () -> Unit = {
Box(
modifier = Modifier
.fillMaxSize()
Expand Down Expand Up @@ -133,48 +91,37 @@ internal fun ListRowItem(
contentDescription = ""
)
}
}
}

Divider(
if (item.isRecent) {
Column(modifier = modifier) {
SwipeActionRow(
modifier = Modifier.weight(1f),
onDelete = onRemoved,
stateKey = item.currency.code,
) {
rowContent()
}
HorizontalDivider(
color = CodeTheme.colors.dividerVariant,
modifier = Modifier
.fillMaxWidth()
.height(1.dp)
.align(Alignment.BottomCenter)
.padding(start = CodeTheme.dimens.inset)
)
}
}

LaunchedEffect(removed) {
if (removed) {
delay(200)
onRemoved()
}
}
}

@OptIn(ExperimentalMaterialApi::class)
@Composable
private fun DismissBackground(dismissState: DismissState) {
val color = when (dismissState.dismissDirection) {
DismissDirection.EndToStart -> CodeTheme.colors.error
else -> CodeTheme.colors.background
}
val direction = dismissState.dismissDirection

Row(
modifier = Modifier
.fillMaxSize()
.background(color)
.padding(end = CodeTheme.dimens.inset),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.End
) {
if (direction == DismissDirection.EndToStart) {
Icon(
modifier = Modifier.size(CodeTheme.dimens.staticGrid.x6),
painter = painterResource(id = R.drawable.ic_delete),
contentDescription = "delete"
} else {
Column(modifier = modifier) {
Box(modifier = Modifier.weight(1f)) {
rowContent()
}
HorizontalDivider(
color = CodeTheme.colors.dividerVariant,
modifier = Modifier
.fillMaxWidth()
.height(1.dp)
.padding(start = CodeTheme.dimens.inset)
)
}
}
Expand Down
Loading
Loading