Merge pull request #1768 from vitorpamplona/claude/add-drag-drop-reactions-43YJ2
Replace move buttons with drag-to-reorder in reactions settings
This commit is contained in:
+128
-51
@@ -20,6 +20,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.settings
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.settings
|
||||||
|
|
||||||
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@@ -32,25 +34,30 @@ import androidx.compose.foundation.layout.size
|
|||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
import androidx.compose.material.icons.filled.DragHandle
|
||||||
import androidx.compose.material.icons.filled.KeyboardArrowUp
|
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Switch
|
import androidx.compose.material3.Switch
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
|
import androidx.compose.runtime.mutableStateMapOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.zIndex
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.ReactionRowAction
|
import com.vitorpamplona.amethyst.model.ReactionRowAction
|
||||||
@@ -87,6 +94,10 @@ fun ReactionsSettingsContent(accountViewModel: AccountViewModel) {
|
|||||||
accountViewModel.changeReactionRowItems(newItems)
|
accountViewModel.changeReactionRowItems(newItems)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var draggedItemIndex by remember { mutableIntStateOf(-1) }
|
||||||
|
var dragOffset by remember { mutableFloatStateOf(0f) }
|
||||||
|
val itemHeights = remember { mutableStateMapOf<Int, Float>() }
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
@@ -104,10 +115,18 @@ fun ReactionsSettingsContent(accountViewModel: AccountViewModel) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
items.forEachIndexed { index, item ->
|
items.forEachIndexed { index, item ->
|
||||||
|
val isDragging = draggedItemIndex == index
|
||||||
|
val targetElevation = if (isDragging) 8f else 0f
|
||||||
|
val animatedElevation by animateFloatAsState(
|
||||||
|
targetValue = targetElevation,
|
||||||
|
label = "dragElevation",
|
||||||
|
)
|
||||||
|
|
||||||
ReactionRowItemCard(
|
ReactionRowItemCard(
|
||||||
item = item,
|
item = item,
|
||||||
canMoveUp = index > 0,
|
isDragging = isDragging,
|
||||||
canMoveDown = index < items.lastIndex,
|
dragOffsetY = if (isDragging) dragOffset else 0f,
|
||||||
|
elevation = animatedElevation,
|
||||||
onToggleEnabled = {
|
onToggleEnabled = {
|
||||||
val newItems = items.toMutableList()
|
val newItems = items.toMutableList()
|
||||||
newItems[index] = item.copy(enabled = !item.enabled)
|
newItems[index] = item.copy(enabled = !item.enabled)
|
||||||
@@ -118,24 +137,73 @@ fun ReactionsSettingsContent(accountViewModel: AccountViewModel) {
|
|||||||
newItems[index] = item.copy(showCounter = !item.showCounter)
|
newItems[index] = item.copy(showCounter = !item.showCounter)
|
||||||
save(newItems)
|
save(newItems)
|
||||||
},
|
},
|
||||||
onMoveUp = {
|
onMeasured = { height ->
|
||||||
if (index > 0) {
|
itemHeights[index] = height
|
||||||
val newItems = items.toMutableList()
|
},
|
||||||
val temp = newItems[index - 1]
|
onDragStart = {
|
||||||
newItems[index - 1] = newItems[index]
|
draggedItemIndex = index
|
||||||
newItems[index] = temp
|
dragOffset = 0f
|
||||||
save(newItems)
|
},
|
||||||
|
onDrag = { dragAmount ->
|
||||||
|
dragOffset += dragAmount
|
||||||
|
|
||||||
|
val currentIndex = draggedItemIndex
|
||||||
|
if (currentIndex < 0) return@ReactionRowItemCard
|
||||||
|
|
||||||
|
// Check if we should swap with the item above
|
||||||
|
if (dragOffset < 0 && currentIndex > 0) {
|
||||||
|
val aboveHeight = itemHeights[currentIndex - 1] ?: 0f
|
||||||
|
if (-dragOffset > aboveHeight / 2f) {
|
||||||
|
val newItems = items.toMutableList()
|
||||||
|
val temp = newItems[currentIndex - 1]
|
||||||
|
newItems[currentIndex - 1] = newItems[currentIndex]
|
||||||
|
newItems[currentIndex] = temp
|
||||||
|
items = newItems
|
||||||
|
|
||||||
|
// Transfer heights
|
||||||
|
val h1 = itemHeights[currentIndex]
|
||||||
|
val h2 = itemHeights[currentIndex - 1]
|
||||||
|
if (h1 != null) itemHeights[currentIndex - 1] = h1
|
||||||
|
if (h2 != null) itemHeights[currentIndex] = h2
|
||||||
|
|
||||||
|
dragOffset += aboveHeight
|
||||||
|
draggedItemIndex = currentIndex - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we should swap with the item below
|
||||||
|
if (dragOffset > 0 && currentIndex < items.lastIndex) {
|
||||||
|
val belowHeight = itemHeights[currentIndex + 1] ?: 0f
|
||||||
|
if (dragOffset > belowHeight / 2f) {
|
||||||
|
val newItems = items.toMutableList()
|
||||||
|
val temp = newItems[currentIndex + 1]
|
||||||
|
newItems[currentIndex + 1] = newItems[currentIndex]
|
||||||
|
newItems[currentIndex] = temp
|
||||||
|
items = newItems
|
||||||
|
|
||||||
|
// Transfer heights
|
||||||
|
val h1 = itemHeights[currentIndex]
|
||||||
|
val h2 = itemHeights[currentIndex + 1]
|
||||||
|
if (h1 != null) itemHeights[currentIndex + 1] = h1
|
||||||
|
if (h2 != null) itemHeights[currentIndex] = h2
|
||||||
|
|
||||||
|
dragOffset -= belowHeight
|
||||||
|
draggedItemIndex = currentIndex + 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onMoveDown = {
|
onDragEnd = {
|
||||||
if (index < items.lastIndex) {
|
draggedItemIndex = -1
|
||||||
val newItems = items.toMutableList()
|
dragOffset = 0f
|
||||||
val temp = newItems[index + 1]
|
save(items)
|
||||||
newItems[index + 1] = newItems[index]
|
|
||||||
newItems[index] = temp
|
|
||||||
save(newItems)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
onDragCancel = {
|
||||||
|
draggedItemIndex = -1
|
||||||
|
dragOffset = 0f
|
||||||
|
},
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.zIndex(if (isDragging) 1f else 0f),
|
||||||
)
|
)
|
||||||
if (index < items.lastIndex) {
|
if (index < items.lastIndex) {
|
||||||
HorizontalDivider()
|
HorizontalDivider()
|
||||||
@@ -149,20 +217,36 @@ fun ReactionsSettingsContent(accountViewModel: AccountViewModel) {
|
|||||||
@Composable
|
@Composable
|
||||||
private fun ReactionRowItemCard(
|
private fun ReactionRowItemCard(
|
||||||
item: ReactionRowItem,
|
item: ReactionRowItem,
|
||||||
canMoveUp: Boolean,
|
isDragging: Boolean,
|
||||||
canMoveDown: Boolean,
|
dragOffsetY: Float,
|
||||||
|
elevation: Float,
|
||||||
onToggleEnabled: () -> Unit,
|
onToggleEnabled: () -> Unit,
|
||||||
onToggleCounter: () -> Unit,
|
onToggleCounter: () -> Unit,
|
||||||
onMoveUp: () -> Unit,
|
onMeasured: (Float) -> Unit,
|
||||||
onMoveDown: () -> Unit,
|
onDragStart: () -> Unit,
|
||||||
|
onDrag: (Float) -> Unit,
|
||||||
|
onDragEnd: () -> Unit,
|
||||||
|
onDragCancel: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val actionName = reactionActionName(item.action)
|
val actionName = reactionActionName(item.action)
|
||||||
val actionDescription = reactionActionDescription(item.action)
|
val actionDescription = reactionActionDescription(item.action)
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
.onGloballyPositioned { coordinates ->
|
||||||
|
onMeasured(coordinates.size.height.toFloat())
|
||||||
|
}
|
||||||
|
.graphicsLayer {
|
||||||
|
translationY = dragOffsetY
|
||||||
|
shadowElevation = elevation
|
||||||
|
if (isDragging) {
|
||||||
|
scaleX = 1.02f
|
||||||
|
scaleY = 1.02f
|
||||||
|
}
|
||||||
|
}
|
||||||
.padding(vertical = 8.dp),
|
.padding(vertical = 8.dp),
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
@@ -186,32 +270,25 @@ private fun ReactionRowItemCard(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
Icon(
|
||||||
IconButton(
|
Icons.Default.DragHandle,
|
||||||
onClick = onMoveUp,
|
contentDescription = stringRes(R.string.reactions_settings_reorder),
|
||||||
enabled = canMoveUp,
|
modifier =
|
||||||
modifier = Modifier.size(32.dp),
|
Modifier
|
||||||
) {
|
.size(32.dp)
|
||||||
Icon(
|
.pointerInput(Unit) {
|
||||||
Icons.Default.KeyboardArrowUp,
|
detectDragGesturesAfterLongPress(
|
||||||
contentDescription = stringRes(R.string.reactions_settings_move_up),
|
onDragStart = { onDragStart() },
|
||||||
modifier = Modifier.size(20.dp),
|
onDrag = { change, dragAmount ->
|
||||||
tint = if (canMoveUp) MaterialTheme.colorScheme.onSurface else Color.Gray,
|
change.consume()
|
||||||
)
|
onDrag(dragAmount.y)
|
||||||
}
|
},
|
||||||
IconButton(
|
onDragEnd = { onDragEnd() },
|
||||||
onClick = onMoveDown,
|
onDragCancel = { onDragCancel() },
|
||||||
enabled = canMoveDown,
|
)
|
||||||
modifier = Modifier.size(32.dp),
|
},
|
||||||
) {
|
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
Icon(
|
)
|
||||||
Icons.Default.KeyboardArrowDown,
|
|
||||||
contentDescription = stringRes(R.string.reactions_settings_move_down),
|
|
||||||
modifier = Modifier.size(20.dp),
|
|
||||||
tint = if (canMoveDown) MaterialTheme.colorScheme.onSurface else Color.Gray,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|||||||
@@ -1238,8 +1238,7 @@
|
|||||||
<string name="reactions_settings_description">Configure which reaction buttons are shown, their order, and whether to display counters.</string>
|
<string name="reactions_settings_description">Configure which reaction buttons are shown, their order, and whether to display counters.</string>
|
||||||
<string name="reactions_settings_enabled">Enabled</string>
|
<string name="reactions_settings_enabled">Enabled</string>
|
||||||
<string name="reactions_settings_show_counter">Show Count</string>
|
<string name="reactions_settings_show_counter">Show Count</string>
|
||||||
<string name="reactions_settings_move_up">Move up</string>
|
<string name="reactions_settings_reorder">Reorder</string>
|
||||||
<string name="reactions_settings_move_down">Move down</string>
|
|
||||||
<string name="reactions_settings_reply">Reply</string>
|
<string name="reactions_settings_reply">Reply</string>
|
||||||
<string name="reactions_settings_reply_description">Reply to this note</string>
|
<string name="reactions_settings_reply_description">Reply to this note</string>
|
||||||
<string name="reactions_settings_boost">Boost</string>
|
<string name="reactions_settings_boost">Boost</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user