feat: implement auto-scrolling and resizing enhancements for last column in ListDetailScreen
This commit is contained in:
+45
-4
@@ -90,6 +90,9 @@ import java.text.SimpleDateFormat
|
|||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -410,6 +413,7 @@ fun ListDetailScreen(
|
|||||||
fieldWidths[field.id] = newWidth.dp
|
fieldWidths[field.id] = newWidth.dp
|
||||||
},
|
},
|
||||||
scrollState = horizontalScrollState,
|
scrollState = horizontalScrollState,
|
||||||
|
isLast = (field.id == stableFields.lastOrNull()?.id),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -602,9 +606,11 @@ fun FieldHeader(
|
|||||||
width: Dp,
|
width: Dp,
|
||||||
onWidthChange: (Float) -> Unit,
|
onWidthChange: (Float) -> Unit,
|
||||||
scrollState: androidx.compose.foundation.ScrollState,
|
scrollState: androidx.compose.foundation.ScrollState,
|
||||||
|
isLast: Boolean,
|
||||||
) {
|
) {
|
||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
var autoScrollJob by remember { mutableStateOf<Job?>(null) }
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier =
|
modifier =
|
||||||
@@ -636,21 +642,56 @@ fun FieldHeader(
|
|||||||
Modifier
|
Modifier
|
||||||
.size(24.dp)
|
.size(24.dp)
|
||||||
.pointerInput(Unit) {
|
.pointerInput(Unit) {
|
||||||
detectDragGestures { change, dragAmount ->
|
detectDragGestures(
|
||||||
|
onDragStart = {
|
||||||
|
// no-op
|
||||||
|
},
|
||||||
|
onDragEnd = {
|
||||||
|
autoScrollJob?.cancel()
|
||||||
|
autoScrollJob = null
|
||||||
|
},
|
||||||
|
onDragCancel = {
|
||||||
|
autoScrollJob?.cancel()
|
||||||
|
autoScrollJob = null
|
||||||
|
},
|
||||||
|
) { change, dragAmount ->
|
||||||
change.consume()
|
change.consume()
|
||||||
with(density) {
|
with(density) {
|
||||||
onWidthChange(dragAmount.x.toDp().value)
|
onWidthChange(dragAmount.x.toDp().value)
|
||||||
}
|
}
|
||||||
// Auto-scroll when resizing near the edges to keep the handle visible
|
val leftCancelThreshold = -4f
|
||||||
|
val autoStepDp = 2f
|
||||||
|
if (isLast) {
|
||||||
|
// Start or maintain continuous expand+scroll when at/right of the handle
|
||||||
|
if (dragAmount.x >= 0f || autoScrollJob != null) {
|
||||||
|
if (autoScrollJob == null) {
|
||||||
|
autoScrollJob = scope.launch {
|
||||||
|
while (isActive) {
|
||||||
|
// Keep expanding a bit and reveal the end as space grows
|
||||||
|
onWidthChange(autoStepDp)
|
||||||
|
scrollState.scrollTo(scrollState.maxValue)
|
||||||
|
delay(16)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Cancel only on a clear leftward move to avoid jitter stopping expansion
|
||||||
|
if (dragAmount.x < leftCancelThreshold) {
|
||||||
|
autoScrollJob?.cancel()
|
||||||
|
autoScrollJob = null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Non-last columns: gentle nudge near edges
|
||||||
val edgePx = with(density) { 32.dp.toPx() }
|
val edgePx = with(density) { 32.dp.toPx() }
|
||||||
if (dragAmount.x > 0 && scrollState.value.toFloat() >= (scrollState.maxValue - edgePx)) {
|
if (dragAmount.x > 0f && scrollState.value.toFloat() >= (scrollState.maxValue - edgePx)) {
|
||||||
val target = (scrollState.value.toFloat() + edgePx / 2f).coerceAtMost(scrollState.maxValue.toFloat())
|
val target = (scrollState.value.toFloat() + edgePx / 2f).coerceAtMost(scrollState.maxValue.toFloat())
|
||||||
scope.launch { scrollState.scrollTo(target.toInt()) }
|
scope.launch { scrollState.scrollTo(target.toInt()) }
|
||||||
} else if (dragAmount.x < 0 && scrollState.value.toFloat() <= edgePx) {
|
} else if (dragAmount.x < 0f && scrollState.value.toFloat() <= edgePx) {
|
||||||
val target = (scrollState.value.toFloat() - edgePx / 2f).coerceAtLeast(0f)
|
val target = (scrollState.value.toFloat() - edgePx / 2f).coerceAtLeast(0f)
|
||||||
scope.launch { scrollState.scrollTo(target.toInt()) }
|
scope.launch { scrollState.scrollTo(target.toInt()) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
|
|||||||
Reference in New Issue
Block a user