Skip to content

Commit 143bb94

Browse files
committed
style(region-selection): update selection UI to match new UI/UX in ContactList for swipe-to-reveal/dismiss
Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent cb3f94d commit 143bb94

3 files changed

Lines changed: 148 additions & 186 deletions

File tree

  • apps/flipcash
    • features/direct-send/src/main/kotlin/com/flipcash/app/directsend/internal/screens
    • shared/currency-selection/ui/src/main/kotlin/com/flipcash/app/currency/internal/components
  • ui/components/src/main/kotlin/com/getcode/ui/components

apps/flipcash/features/direct-send/src/main/kotlin/com/flipcash/app/directsend/internal/screens/ContactListScreen.kt

Lines changed: 1 addition & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,6 @@ import androidx.compose.animation.fadeIn
55
import androidx.compose.animation.fadeOut
66
import androidx.compose.animation.togetherWith
77
import androidx.compose.foundation.background
8-
import androidx.compose.foundation.gestures.AnchoredDraggableState
9-
import androidx.compose.foundation.gestures.DraggableAnchors
10-
import androidx.compose.foundation.gestures.Orientation
11-
import androidx.compose.foundation.gestures.anchoredDraggable
12-
import androidx.compose.foundation.gestures.snapTo
138
import androidx.compose.foundation.layout.Arrangement
149
import androidx.compose.foundation.layout.Box
1510
import androidx.compose.foundation.layout.BoxScope
@@ -19,15 +14,13 @@ import androidx.compose.foundation.layout.Row
1914
import androidx.compose.foundation.layout.fillMaxSize
2015
import androidx.compose.foundation.layout.fillMaxWidth
2116
import androidx.compose.foundation.layout.height
22-
import androidx.compose.foundation.layout.offset
2317
import androidx.compose.foundation.layout.padding
2418
import androidx.compose.foundation.layout.requiredSize
2519
import androidx.compose.foundation.layout.size
2620
import androidx.compose.foundation.lazy.LazyColumn
2721
import androidx.compose.foundation.lazy.itemsIndexed
2822
import androidx.compose.foundation.lazy.rememberLazyListState
2923
import androidx.compose.foundation.shape.CircleShape
30-
import androidx.compose.foundation.shape.RoundedCornerShape
3124
import androidx.compose.material.icons.Icons
3225
import androidx.compose.material.icons.filled.GroupAdd
3326
import androidx.compose.material3.HorizontalDivider
@@ -36,24 +29,16 @@ import androidx.compose.material3.Text
3629
import androidx.compose.runtime.Composable
3730
import androidx.compose.runtime.LaunchedEffect
3831
import androidx.compose.runtime.getValue
39-
import androidx.compose.runtime.mutableFloatStateOf
4032
import androidx.compose.runtime.mutableStateOf
4133
import androidx.compose.runtime.remember
42-
import androidx.compose.runtime.rememberCoroutineScope
43-
import androidx.compose.runtime.rememberUpdatedState
4434
import androidx.compose.runtime.saveable.rememberSaveable
4535
import androidx.compose.runtime.setValue
4636
import androidx.compose.ui.Alignment
4737
import androidx.compose.ui.Modifier
4838
import androidx.compose.ui.draw.clip
49-
import androidx.compose.ui.draw.clipToBounds
5039
import androidx.compose.ui.graphics.Brush
5140
import androidx.compose.ui.graphics.Color
5241
import androidx.compose.ui.platform.LocalContext
53-
import androidx.compose.ui.platform.LocalDensity
54-
import androidx.compose.ui.layout.layout
55-
import androidx.compose.ui.layout.onSizeChanged
56-
import androidx.compose.ui.unit.IntOffset
5742
import androidx.compose.ui.res.painterResource
5843
import androidx.compose.ui.res.stringResource
5944
import androidx.compose.ui.text.style.TextAlign
@@ -84,14 +69,13 @@ import com.getcode.ui.components.AppBarDefaults
8469
import com.getcode.ui.components.AppBarWithTitle
8570
import com.getcode.ui.components.CircularIconButton
8671
import com.getcode.ui.components.SearchInput
72+
import com.getcode.ui.components.SwipeToRevealItem
8773
import com.getcode.ui.core.rememberedClickable
8874
import com.getcode.ui.core.verticalScrollStateGradient
8975
import com.getcode.ui.theme.CodeCircularProgressIndicator
9076
import com.getcode.ui.theme.CodeScaffold
9177
import com.getcode.view.LoadingSuccessState
92-
import kotlin.math.abs
9378
import kotlinx.coroutines.flow.filterIsInstance
94-
import kotlinx.coroutines.launch
9579

9680

9781
@Composable
@@ -290,101 +274,6 @@ private fun ContactList(
290274
}
291275
}
292276

293-
private enum class RevealValue { Settled, Revealed, Dismissed }
294-
295-
@Composable
296-
private fun SwipeToRevealItem(
297-
onDelete: () -> Unit,
298-
modifier: Modifier = Modifier,
299-
content: @Composable () -> Unit,
300-
) {
301-
val density = LocalDensity.current
302-
val scope = rememberCoroutineScope()
303-
val actionWidth = CodeTheme.dimens.staticGrid.x10 + CodeTheme.dimens.inset * 2
304-
val actionWidthPx = with(density) { actionWidth.toPx() }
305-
306-
val state = remember { AnchoredDraggableState(initialValue = RevealValue.Settled) }
307-
var rowWidthPx by remember { mutableFloatStateOf(0f) }
308-
309-
LaunchedEffect(rowWidthPx, actionWidthPx) {
310-
if (rowWidthPx > 0f) {
311-
state.updateAnchors(DraggableAnchors {
312-
RevealValue.Settled at 0f
313-
RevealValue.Revealed at -actionWidthPx
314-
RevealValue.Dismissed at -rowWidthPx
315-
})
316-
}
317-
}
318-
319-
val currentOnDelete by rememberUpdatedState(onDelete)
320-
LaunchedEffect(state.currentValue) {
321-
if (state.currentValue == RevealValue.Dismissed) {
322-
currentOnDelete()
323-
}
324-
}
325-
326-
val actionPadding = CodeTheme.dimens.inset
327-
val minActionSize = CodeTheme.dimens.staticGrid.x10
328-
329-
Box(
330-
modifier = modifier
331-
.clipToBounds()
332-
.onSizeChanged { rowWidthPx = it.width.toFloat() },
333-
) {
334-
// Action area — grows from circle to rounded rect as swipe progresses
335-
Box(
336-
modifier = Modifier
337-
.matchParentSize()
338-
.layout { measurable, constraints ->
339-
val absOffset = abs(state.offset)
340-
val paddingPx = actionPadding.roundToPx()
341-
val minPx = minActionSize.roundToPx()
342-
343-
val w = (absOffset.toInt() - paddingPx * 2).coerceAtLeast(minPx)
344-
val h = (constraints.maxHeight - paddingPx * 2).coerceAtLeast(minPx)
345-
346-
val placeable = measurable.measure(
347-
constraints.copy(
348-
minWidth = w, maxWidth = w,
349-
minHeight = h, maxHeight = h,
350-
)
351-
)
352-
layout(constraints.maxWidth, constraints.maxHeight) {
353-
placeable.place(
354-
constraints.maxWidth - placeable.width - paddingPx,
355-
(constraints.maxHeight - placeable.height) / 2,
356-
)
357-
}
358-
}
359-
.clip(RoundedCornerShape(50))
360-
.background(CodeTheme.colors.error)
361-
.rememberedClickable {
362-
scope.launch {
363-
state.snapTo(RevealValue.Settled)
364-
}
365-
onDelete()
366-
},
367-
contentAlignment = Alignment.Center,
368-
) {
369-
Icon(
370-
painter = painterResource(R.drawable.ic_delete),
371-
contentDescription = stringResource(R.string.action_remove),
372-
tint = Color.White,
373-
modifier = Modifier.requiredSize(CodeTheme.dimens.staticGrid.x5),
374-
)
375-
}
376-
377-
// Foreground content that slides
378-
Box(
379-
modifier = Modifier
380-
.offset { IntOffset(state.offset.toInt(), 0) }
381-
.anchoredDraggable(state, Orientation.Horizontal),
382-
) {
383-
content()
384-
}
385-
}
386-
}
387-
388277
@Composable
389278
private fun ContactGroupHeader(text: String, modifier: Modifier = Modifier) {
390279
Box(

apps/flipcash/shared/currency-selection/ui/src/main/kotlin/com/flipcash/app/currency/internal/components/ListRowItem.kt

Lines changed: 11 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package com.flipcash.app.currency.internal.components
22

33
import androidx.compose.foundation.Image
44
import androidx.compose.foundation.background
5-
import androidx.compose.foundation.layout.Arrangement
65
import androidx.compose.foundation.layout.Box
76
import androidx.compose.foundation.layout.Column
87
import androidx.compose.foundation.layout.Row
@@ -11,23 +10,10 @@ import androidx.compose.foundation.layout.fillMaxWidth
1110
import androidx.compose.foundation.layout.height
1211
import androidx.compose.foundation.layout.padding
1312
import androidx.compose.foundation.layout.requiredSize
14-
import androidx.compose.foundation.layout.size
1513
import androidx.compose.foundation.layout.wrapContentWidth
16-
import androidx.compose.material.DismissDirection
17-
import androidx.compose.material.DismissState
18-
import androidx.compose.material.DismissValue
1914
import androidx.compose.material.Divider
20-
import androidx.compose.material.ExperimentalMaterialApi
21-
import androidx.compose.material.FixedThreshold
22-
import androidx.compose.material.Icon
23-
import androidx.compose.material.SwipeToDismiss
2415
import androidx.compose.material.Text
2516
import androidx.compose.runtime.Composable
26-
import androidx.compose.runtime.LaunchedEffect
27-
import androidx.compose.runtime.getValue
28-
import androidx.compose.runtime.mutableStateOf
29-
import androidx.compose.runtime.remember
30-
import androidx.compose.runtime.setValue
3117
import androidx.compose.ui.Alignment
3218
import androidx.compose.ui.Modifier
3319
import androidx.compose.ui.draw.alpha
@@ -37,10 +23,9 @@ import androidx.compose.ui.unit.dp
3723
import com.flipcash.app.currency.internal.CurrencyListItem
3824
import com.flipcash.features.currency.R
3925
import com.getcode.theme.CodeTheme
26+
import com.getcode.ui.components.SwipeToRevealItem
4027
import com.getcode.ui.core.rememberedClickable
41-
import kotlinx.coroutines.delay
4228

43-
@OptIn(ExperimentalMaterialApi::class)
4429
@Composable
4530
internal fun ListRowItem(
4631
item: CurrencyListItem.RegionCurrencyItem,
@@ -49,34 +34,7 @@ internal fun ListRowItem(
4934
onRemoved: () -> Unit,
5035
onClick: () -> Unit
5136
) {
52-
53-
var removed by remember(item) {
54-
mutableStateOf(false)
55-
}
56-
57-
val dismissState = remember(item) {
58-
DismissState(
59-
initialValue = DismissValue.Default,
60-
confirmStateChange = {
61-
if (it == DismissValue.DismissedToStart) {
62-
removed = true
63-
true
64-
} else false
65-
}
66-
)
67-
}
68-
69-
SwipeToDismiss(
70-
modifier = modifier,
71-
state = dismissState,
72-
dismissThresholds = { FixedThreshold(150.dp) },
73-
directions = if (item.isRecent) setOf(DismissDirection.EndToStart) else emptySet(),
74-
background = {
75-
if (item.isRecent) {
76-
DismissBackground(dismissState)
77-
}
78-
}
79-
) {
37+
val rowContent: @Composable () -> Unit = {
8038
Box(
8139
modifier = Modifier
8240
.fillMaxSize()
@@ -145,37 +103,16 @@ internal fun ListRowItem(
145103
}
146104
}
147105

148-
LaunchedEffect(removed) {
149-
if (removed) {
150-
delay(200)
151-
onRemoved()
106+
if (item.isRecent) {
107+
SwipeToRevealItem(
108+
modifier = modifier,
109+
onDelete = onRemoved,
110+
) {
111+
rowContent()
152112
}
153-
}
154-
}
155-
156-
@OptIn(ExperimentalMaterialApi::class)
157-
@Composable
158-
private fun DismissBackground(dismissState: DismissState) {
159-
val color = when (dismissState.dismissDirection) {
160-
DismissDirection.EndToStart -> CodeTheme.colors.error
161-
else -> CodeTheme.colors.background
162-
}
163-
val direction = dismissState.dismissDirection
164-
165-
Row(
166-
modifier = Modifier
167-
.fillMaxSize()
168-
.background(color)
169-
.padding(end = CodeTheme.dimens.inset),
170-
verticalAlignment = Alignment.CenterVertically,
171-
horizontalArrangement = Arrangement.End
172-
) {
173-
if (direction == DismissDirection.EndToStart) {
174-
Icon(
175-
modifier = Modifier.size(CodeTheme.dimens.staticGrid.x6),
176-
painter = painterResource(id = R.drawable.ic_delete),
177-
contentDescription = "delete"
178-
)
113+
} else {
114+
Box(modifier = modifier) {
115+
rowContent()
179116
}
180117
}
181118
}

0 commit comments

Comments
 (0)