This commit is contained in:
2026-02-25 12:49:07 +00:00
parent fd42bd65b0
commit f5d1eb210e
3 changed files with 48 additions and 24 deletions

View File

@@ -150,6 +150,7 @@ fun MainScreen(
isReorderMode = isReorderMode, isReorderMode = isReorderMode,
onToggleReorderMode = { viewModel.toggleReorderMode() }, onToggleReorderMode = { viewModel.toggleReorderMode() },
onMoveChannel = { from, to -> viewModel.moveChannel(from, to) }, onMoveChannel = { from, to -> viewModel.moveChannel(from, to) },
onMoveChannelById = { id, delta -> viewModel.moveChannelById(id, delta) },
onMoveChildSpace = { from, to -> viewModel.moveChildSpace(from, to) }, onMoveChildSpace = { from, to -> viewModel.moveChildSpace(from, to) },
) )
} }

View File

@@ -348,8 +348,13 @@ class MainViewModel(
// Child space sections // Child space sections
for ((csId, csName, csRoomIds) in childSpaces) { for ((csId, csName, csRoomIds) in childSpaces) {
val sectionChannels = csRoomIds.mapNotNull { channelMap[it] } val csChannels = csRoomIds.mapNotNull { channelMap[it] }
.filter { it.id !in usedIds } .filter { it.id !in usedIds }
// Respect saved channel order within child space sections
val sectionChannels = if (savedOrder != null) {
val idxMap = savedOrder.withIndex().associate { (i, id) -> id to i }
csChannels.sortedBy { idxMap[it.id] ?: Int.MAX_VALUE }
} else csChannels
if (sectionChannels.isNotEmpty()) { if (sectionChannels.isNotEmpty()) {
sectionList.add(ChannelSection(spaceId = csId, spaceName = csName, channels = sectionChannels)) sectionList.add(ChannelSection(spaceId = csId, spaceName = csName, channels = sectionChannels))
usedIds.addAll(sectionChannels.map { it.id }) usedIds.addAll(sectionChannels.map { it.id })
@@ -1128,6 +1133,23 @@ class MainViewModel(
} }
} }
fun moveChannelById(channelId: String, delta: Int) {
val current = _channels.value.toMutableList()
val fromIdx = current.indexOfFirst { it.id == channelId }
if (fromIdx < 0) return
val toIdx = (fromIdx + delta).coerceIn(0, current.lastIndex)
if (fromIdx == toIdx) return
val item = current.removeAt(fromIdx)
current.add(toIdx, item)
_channels.value = current
val spaceId = _selectedSpace.value ?: return
val roomIds = current.map { it.id }
_channelOrderMap.value = _channelOrderMap.value + (spaceId to roomIds)
viewModelScope.launch {
preferencesManager.saveChannelOrder(spaceId, roomIds)
}
}
fun moveChildSpace(from: Int, to: Int) { fun moveChildSpace(from: Int, to: Int) {
val spaceId = _selectedSpace.value ?: return val spaceId = _selectedSpace.value ?: return
val current = _directChildSpaces.value[spaceId]?.toMutableList() ?: return val current = _directChildSpaces.value[spaceId]?.toMutableList() ?: return

View File

@@ -74,14 +74,15 @@ fun ChannelList(
isReorderMode: Boolean = false, isReorderMode: Boolean = false,
onToggleReorderMode: () -> Unit = {}, onToggleReorderMode: () -> Unit = {},
onMoveChannel: (from: Int, to: Int) -> Unit = { _, _ -> }, onMoveChannel: (from: Int, to: Int) -> Unit = { _, _ -> },
onMoveChannelById: (channelId: String, delta: Int) -> Unit = { _, _ -> },
onMoveChildSpace: (from: Int, to: Int) -> Unit = { _, _ -> }, onMoveChildSpace: (from: Int, to: Int) -> Unit = { _, _ -> },
) { ) {
var showLogoutDialog by remember { mutableStateOf(false) } var showLogoutDialog by remember { mutableStateOf(false) }
val collapsedSections = remember { mutableStateMapOf<String, Boolean>() } val collapsedSections = remember { mutableStateMapOf<String, Boolean>() }
// Channel drag state // Channel drag state — track by ID, no visual offset (let LazyColumn handle positioning)
var draggingIndex by remember { mutableIntStateOf(-1) } var draggingChannelId by remember { mutableStateOf<String?>(null) }
var dragOffsetY by remember { mutableFloatStateOf(0f) } var dragAccumulator by remember { mutableFloatStateOf(0f) }
// Section drag state // Section drag state
var draggingSectionIndex by remember { mutableIntStateOf(-1) } var draggingSectionIndex by remember { mutableIntStateOf(-1) }
@@ -260,9 +261,7 @@ fun ChannelList(
itemsIndexed(section.channels, key = { _, ch -> ch.id }) { index, channel -> itemsIndexed(section.channels, key = { _, ch -> ch.id }) { index, channel ->
val isSelected = channel.id == selectedChannel val isSelected = channel.id == selectedChannel
val hasUnread = channel.unreadStatus != UnreadStatus.NONE val hasUnread = channel.unreadStatus != UnreadStatus.NONE
val globalIndex = channels.indexOfFirst { it.id == channel.id } val isDragging = draggingChannelId == channel.id
val isDragging = draggingIndex == globalIndex
val elevation by animateDpAsState(if (isDragging) 8.dp else 0.dp, label = "elevation")
Row( Row(
modifier = Modifier modifier = Modifier
@@ -270,8 +269,7 @@ fun ChannelList(
.then( .then(
if (isDragging) Modifier if (isDragging) Modifier
.zIndex(1f) .zIndex(1f)
.offset { IntOffset(0, dragOffsetY.roundToInt()) } .background(MaterialTheme.colorScheme.surfaceContainerHigh)
.shadow(elevation, RoundedCornerShape(8.dp))
else Modifier else Modifier
) )
.clip(RoundedCornerShape(8.dp)) .clip(RoundedCornerShape(8.dp))
@@ -287,7 +285,9 @@ fun ChannelList(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
if (isReorderMode) { if (isReorderMode) {
val currentGlobalIndex by rememberUpdatedState(globalIndex) val currentChannelId by rememberUpdatedState(channel.id)
val currentSectionSize by rememberUpdatedState(section.channels.size)
val currentLocalIndex by rememberUpdatedState(index)
Icon( Icon(
imageVector = Icons.Default.DragHandle, imageVector = Icons.Default.DragHandle,
contentDescription = "Drag to reorder", contentDescription = "Drag to reorder",
@@ -296,28 +296,29 @@ fun ChannelList(
.pointerInput(Unit) { .pointerInput(Unit) {
detectDragGesturesAfterLongPress( detectDragGesturesAfterLongPress(
onDragStart = { onDragStart = {
draggingIndex = currentGlobalIndex draggingChannelId = currentChannelId
dragOffsetY = 0f dragAccumulator = 0f
}, },
onDrag = { change, dragAmount -> onDrag = { change, dragAmount ->
change.consume() change.consume()
dragOffsetY += dragAmount.y dragAccumulator += dragAmount.y
val itemHeight = 40.dp.toPx() val itemHeight = 56.dp.toPx()
val draggedPositions = (dragOffsetY / itemHeight).roundToInt() val id = draggingChannelId ?: return@detectDragGesturesAfterLongPress
val targetIndex = (draggingIndex + draggedPositions).coerceIn(0, channels.lastIndex) if (dragAccumulator > itemHeight * 0.6f && currentLocalIndex < currentSectionSize - 1) {
if (targetIndex != draggingIndex) { onMoveChannelById(id, 1)
onMoveChannel(draggingIndex, targetIndex) dragAccumulator = 0f
dragOffsetY -= (targetIndex - draggingIndex) * itemHeight } else if (dragAccumulator < -itemHeight * 0.6f && currentLocalIndex > 0) {
draggingIndex = targetIndex onMoveChannelById(id, -1)
dragAccumulator = 0f
} }
}, },
onDragEnd = { onDragEnd = {
draggingIndex = -1 draggingChannelId = null
dragOffsetY = 0f dragAccumulator = 0f
}, },
onDragCancel = { onDragCancel = {
draggingIndex = -1 draggingChannelId = null
dragOffsetY = 0f dragAccumulator = 0f
}, },
) )
}, },