Skip to content

Commit bcc1c97

Browse files
committed
dev(ui): add drag/drop event to swipeable card.
1 parent 52fb235 commit bcc1c97

File tree

3 files changed

+123
-28
lines changed

3 files changed

+123
-28
lines changed

app/src/main/java/com/flowintent/workspace/ui/SwipeableCard.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import androidx.compose.material3.CardDefaults
3030
import androidx.compose.material3.Icon
3131
import androidx.compose.material3.Text
3232
import androidx.compose.runtime.Composable
33+
import androidx.compose.runtime.LaunchedEffect
3334
import androidx.compose.runtime.getValue
3435
import androidx.compose.runtime.mutableStateOf
3536
import androidx.compose.runtime.remember
@@ -44,6 +45,7 @@ import androidx.compose.ui.text.style.TextAlign
4445
import androidx.compose.ui.unit.Dp
4546
import androidx.compose.ui.unit.IntOffset
4647
import androidx.compose.ui.unit.dp
48+
import androidx.hilt.navigation.compose.hiltViewModel
4749
import com.flowintent.core.db.Task
4850
import com.flowintent.workspace.nav.OpenTaskDialog
4951
import com.flowintent.workspace.ui.vm.TaskViewModel
@@ -54,9 +56,10 @@ import kotlin.math.roundToInt
5456
fun SwipeableCard(
5557
modifier: Modifier = Modifier,
5658
task: Task,
57-
viewModel: TaskViewModel,
59+
viewModel: TaskViewModel = hiltViewModel(),
5860
onDelete: () -> Unit,
5961
onEdit: () -> Unit,
62+
onHeightChange: (Dp) -> Unit,
6063
content: @Composable () -> Unit
6164
) {
6265
val isExpanded = viewModel.expandedMap[task.uid] ?: false
@@ -71,6 +74,10 @@ fun SwipeableCard(
7174
label = "cardHeight"
7275
)
7376

77+
LaunchedEffect(cardHeight) {
78+
onHeightChange(cardHeight)
79+
}
80+
7481
fun interpolateDp(offset: Float, maxSwipe: Float, start: Dp, end: Dp): Dp {
7582
val fraction = (-offset / maxSwipe).coerceIn(0f, 1f)
7683
return end + (start - end) * fraction
@@ -104,7 +111,6 @@ fun SwipeableCard(
104111
Box(
105112
modifier = modifier
106113
.fillMaxWidth()
107-
.padding(start = 12.dp, top = 12.dp, end = 12.dp)
108114
.height(cardHeight)
109115
.background(Color.Transparent)
110116
) {

app/src/main/java/com/flowintent/workspace/ui/TaskListScreen.kt

Lines changed: 95 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
package com.flowintent.workspace.ui
22

3+
import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress
34
import androidx.compose.foundation.layout.Arrangement
5+
import androidx.compose.foundation.layout.Box
46
import androidx.compose.foundation.layout.Column
57
import androidx.compose.foundation.layout.PaddingValues
68
import androidx.compose.foundation.layout.Row
79
import androidx.compose.foundation.layout.fillMaxSize
810
import androidx.compose.foundation.layout.fillMaxWidth
911
import androidx.compose.foundation.layout.padding
1012
import androidx.compose.foundation.lazy.LazyColumn
11-
import androidx.compose.foundation.lazy.items
13+
import androidx.compose.foundation.lazy.itemsIndexed
1214
import androidx.compose.material.icons.Icons
1315
import androidx.compose.material.icons.automirrored.filled.Label
1416
import androidx.compose.material.icons.filled.Category
@@ -20,16 +22,27 @@ import androidx.compose.material3.IconButton
2022
import androidx.compose.material3.Text
2123
import androidx.compose.runtime.Composable
2224
import androidx.compose.runtime.getValue
25+
import androidx.compose.runtime.mutableStateOf
26+
import androidx.compose.runtime.remember
27+
import androidx.compose.runtime.setValue
28+
import androidx.compose.runtime.toMutableStateList
2329
import androidx.compose.ui.Alignment
2430
import androidx.compose.ui.Modifier
2531
import androidx.compose.ui.graphics.RectangleShape
32+
import androidx.compose.ui.graphics.graphicsLayer
33+
import androidx.compose.ui.input.pointer.pointerInput
2634
import androidx.compose.ui.text.font.FontFamily
2735
import androidx.compose.ui.text.font.FontWeight
2836
import androidx.compose.ui.tooling.preview.Preview
2937
import androidx.compose.ui.unit.dp
3038
import androidx.compose.ui.unit.sp
39+
import androidx.compose.ui.zIndex
3140
import androidx.hilt.navigation.compose.hiltViewModel
3241
import androidx.lifecycle.compose.collectAsStateWithLifecycle
42+
import com.flowintent.core.db.DragInfo
43+
import com.flowintent.core.db.Task
44+
import com.flowintent.core.db.calculateNewIndex
45+
import com.flowintent.core.db.swap
3346
import com.flowintent.workspace.nav.ToDoNavTopBar
3447
import com.flowintent.workspace.ui.vm.TaskViewModel
3548
import com.flowintent.workspace.util.asString
@@ -101,36 +114,92 @@ fun ListActionBar(paddingTopOffset: PaddingValues) {
101114
@Composable
102115
private fun ListCardContent(viewModel: TaskViewModel = hiltViewModel()) {
103116
val taskList by viewModel.tasks.collectAsStateWithLifecycle()
117+
val list = remember(taskList) { taskList.toMutableStateList() }
118+
var draggingItem by remember { mutableStateOf<DragInfo?>(null) }
119+
var itemHeight by remember { mutableStateOf(50.dp) }
120+
104121
LazyColumn(
105-
modifier = Modifier
106-
.fillMaxSize(),
122+
modifier = Modifier.fillMaxSize(),
107123
horizontalAlignment = Alignment.CenterHorizontally
108124
) {
109-
items(taskList) { task ->
110-
SwipeableCard(
111-
task = task,
112-
onDelete = { viewModel.deleteTask(task) },
113-
onEdit = { viewModel.setUpdateTaskId(task.uid) },
114-
viewModel = viewModel
125+
itemsIndexed(list, key = { index: Int, task: Task -> task.uid }) { index, task ->
126+
val isDragging = draggingItem?.index == index
127+
128+
Box(
129+
modifier = Modifier
130+
.graphicsLayer {
131+
if (isDragging) {
132+
val heightPx = itemHeight.toPx()
133+
translationY = draggingItem?.let { drag ->
134+
drag.offsetY - drag.touchOffset + heightPx / 2f
135+
} ?: 0f
136+
shadowElevation = 16.dp.toPx()
137+
scaleX = 1.02f
138+
scaleY = 1.02f
139+
}
140+
}
141+
.zIndex(if (isDragging) 1f else 0f)
142+
.pointerInput(Unit) {
143+
detectDragGesturesAfterLongPress(
144+
onDragStart = { offset ->
145+
draggingItem = DragInfo(
146+
index = index,
147+
offsetY = 0f,
148+
touchOffset = offset.y
149+
)
150+
},
151+
onDrag = { change, dragAmount ->
152+
change.consume()
153+
val drag = draggingItem ?: return@detectDragGesturesAfterLongPress
154+
155+
draggingItem = drag.copy(
156+
offsetY = drag.offsetY + dragAmount.y
157+
)
158+
159+
val newIndex = calculateNewIndex(
160+
draggingItem!!,
161+
list.size,
162+
itemHeight = itemHeight.toPx()
163+
)
164+
if (newIndex != draggingItem!!.index) {
165+
list.swap(draggingItem!!.index, newIndex)
166+
draggingItem = draggingItem!!.copy(
167+
index = newIndex,
168+
offsetY = 0f
169+
)
170+
}
171+
},
172+
onDragEnd = { draggingItem = null },
173+
onDragCancel = { draggingItem = null }
174+
)
175+
}
115176
) {
116-
Column(
117-
modifier = Modifier
118-
.fillMaxWidth()
119-
.padding(12.dp)
177+
SwipeableCard(
178+
task = task,
179+
onDelete = { viewModel.deleteTask(task) },
180+
onEdit = { viewModel.setUpdateTaskId(task.uid) },
181+
onHeightChange = { itemHeight = it },
182+
modifier = Modifier.padding(start = 12.dp, top = 12.dp, end = 12.dp)
120183
) {
121-
Text(
122-
text = task.title,
123-
fontSize = 16.sp,
124-
fontFamily = FontFamily.SansSerif,
125-
fontWeight = FontWeight.Bold
126-
)
127-
Text(
128-
text = task.content.asString(),
129-
modifier = Modifier.padding(top = 12.dp),
130-
fontSize = 16.sp,
131-
fontFamily = FontFamily.SansSerif,
132-
fontWeight = FontWeight.Bold
133-
)
184+
Column(
185+
modifier = Modifier
186+
.fillMaxWidth()
187+
.padding(12.dp)
188+
) {
189+
Text(
190+
text = task.title,
191+
fontSize = 16.sp,
192+
fontFamily = FontFamily.SansSerif,
193+
fontWeight = FontWeight.Bold
194+
)
195+
Text(
196+
text = task.content.asString(),
197+
modifier = Modifier.padding(top = 12.dp),
198+
fontSize = 16.sp,
199+
fontFamily = FontFamily.SansSerif,
200+
fontWeight = FontWeight.Bold
201+
)
202+
}
134203
}
135204
}
136205
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.flowintent.core.db
2+
3+
data class DragInfo(
4+
val index: Int,
5+
val offsetY: Float,
6+
val touchOffset: Float = 0f
7+
)
8+
9+
fun calculateNewIndex(dragInfo: DragInfo, size: Int, itemHeight: Float): Int {
10+
val offsetItems = (dragInfo.offsetY / itemHeight).toInt()
11+
return (dragInfo.index + offsetItems).coerceIn(0, size - 1)
12+
}
13+
14+
fun <T> MutableList<T>.swap(from: Int, to: Int) {
15+
if (from == to) {
16+
return
17+
}
18+
val item = removeAt(from)
19+
add(to, item)
20+
}

0 commit comments

Comments
 (0)