mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-05-07 14:16:28 -04:00
feat: add drag-and-drop to channel editor
This commit is contained in:
@@ -8,13 +8,15 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.ButtonDefaults
|
||||
@@ -51,6 +53,7 @@ import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@@ -89,6 +92,9 @@ import com.geeksville.mesh.ui.components.DropDownPreference
|
||||
import com.geeksville.mesh.ui.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.components.config.ChannelCard
|
||||
import com.geeksville.mesh.ui.components.config.EditChannelDialog
|
||||
import com.geeksville.mesh.ui.components.dragContainer
|
||||
import com.geeksville.mesh.ui.components.dragDropItemsIndexed
|
||||
import com.geeksville.mesh.ui.components.rememberDragDropState
|
||||
import com.google.accompanist.themeadapter.appcompat.AppCompatTheme
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.journeyapps.barcodescanner.ScanContract
|
||||
@@ -159,6 +165,19 @@ fun ChannelScreen(
|
||||
}
|
||||
}
|
||||
|
||||
fun updateSettingsList(update: MutableList<ChannelProtos.ChannelSettings>.() -> Unit) {
|
||||
try {
|
||||
val list = channelSet.settingsList.toMutableList()
|
||||
list.update()
|
||||
channelSet = channelSet.copy {
|
||||
settings.clear()
|
||||
settings.addAll(list)
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
errormsg("Error updating ChannelSettings list:", ex)
|
||||
}
|
||||
}
|
||||
|
||||
fun zxingScan() {
|
||||
debug("Starting zxing QR code scanner")
|
||||
val zxingScan = ScanOptions()
|
||||
@@ -273,10 +292,18 @@ fun ChannelScreen(
|
||||
)
|
||||
}
|
||||
|
||||
val listState = rememberLazyListState()
|
||||
val dragDropState = rememberDragDropState(listState) { from, to ->
|
||||
updateSettingsList { add(to.index, removeAt(from.index)) }
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 24.dp, vertical = 16.dp),
|
||||
modifier = Modifier.dragContainer(
|
||||
dragDropState = dragDropState,
|
||||
haptics = LocalHapticFeedback.current,
|
||||
),
|
||||
state = listState,
|
||||
contentPadding = PaddingValues(horizontal = 24.dp, vertical = 16.dp),
|
||||
) {
|
||||
if (!showChannelEditor) item {
|
||||
ClickableTextField(
|
||||
@@ -288,18 +315,18 @@ fun ChannelScreen(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
} else {
|
||||
itemsIndexed(channelSet.settingsList) { index, channel ->
|
||||
dragDropItemsIndexed(
|
||||
items = channelSet.settingsList,
|
||||
dragDropState = dragDropState,
|
||||
) { index, channel, isDragging ->
|
||||
val elevation by animateDpAsState(if (isDragging) 8.dp else 4.dp, label = "drag")
|
||||
ChannelCard(
|
||||
elevation = elevation,
|
||||
index = index,
|
||||
title = channel.name.ifEmpty { modemPresetName },
|
||||
enabled = enabled,
|
||||
onEditClick = { showEditChannelDialog = index },
|
||||
onDeleteClick = {
|
||||
channelSet = channelSet.copy {
|
||||
settings.clear()
|
||||
settings.addAll(channelSet.settingsList.filterIndexed { i, _ -> i != index })
|
||||
}
|
||||
}
|
||||
onDeleteClick = { updateSettingsList { removeAt(index) } }
|
||||
)
|
||||
}
|
||||
item {
|
||||
|
||||
@@ -2,18 +2,20 @@ package com.geeksville.mesh.ui.components.config
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.slideInHorizontally
|
||||
import androidx.compose.animation.slideOutHorizontally
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.Chip
|
||||
import androidx.compose.material.ContentAlpha
|
||||
@@ -36,8 +38,10 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.geeksville.mesh.ChannelProtos.ChannelSettings
|
||||
import com.geeksville.mesh.R
|
||||
@@ -45,6 +49,9 @@ import com.geeksville.mesh.channelSettings
|
||||
import com.geeksville.mesh.model.Channel
|
||||
import com.geeksville.mesh.ui.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.components.dragContainer
|
||||
import com.geeksville.mesh.ui.components.dragDropItemsIndexed
|
||||
import com.geeksville.mesh.ui.components.rememberDragDropState
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
@@ -54,13 +61,14 @@ fun ChannelCard(
|
||||
enabled: Boolean,
|
||||
onEditClick: () -> Unit,
|
||||
onDeleteClick: () -> Unit,
|
||||
elevation: Dp = 4.dp,
|
||||
) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 2.dp)
|
||||
.clickable(enabled = enabled) { onEditClick() },
|
||||
elevation = 4.dp
|
||||
elevation = elevation,
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
@@ -96,6 +104,15 @@ fun ChannelSettingsItemList(
|
||||
val focusManager = LocalFocusManager.current
|
||||
val settingsListInput = remember(settingsList) { settingsList.toMutableStateList() }
|
||||
|
||||
val listState = rememberLazyListState()
|
||||
val dragDropState = rememberDragDropState(listState) { from, to ->
|
||||
settingsListInput.apply {
|
||||
val fromIndex = indexOfFirst { it.hashCode() == from.key }
|
||||
val toIndex = indexOfFirst { it.hashCode() == to.key }
|
||||
add(toIndex, removeAt(fromIndex))
|
||||
}
|
||||
}
|
||||
|
||||
val isEditing: Boolean = settingsList.size != settingsListInput.size
|
||||
|| settingsList.zip(settingsListInput).any { (item1, item2) -> item1 != item2 }
|
||||
|
||||
@@ -123,12 +140,22 @@ fun ChannelSettingsItemList(
|
||||
.clickable(onClick = { }, enabled = false)
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.padding(horizontal = 16.dp)
|
||||
modifier = Modifier.dragContainer(
|
||||
dragDropState = dragDropState,
|
||||
haptics = LocalHapticFeedback.current,
|
||||
),
|
||||
state = listState,
|
||||
contentPadding = PaddingValues(horizontal = 16.dp),
|
||||
) {
|
||||
item { PreferenceCategory(text = "Channels") }
|
||||
|
||||
itemsIndexed(settingsListInput) { index, channel ->
|
||||
dragDropItemsIndexed(
|
||||
items = settingsListInput,
|
||||
dragDropState = dragDropState,
|
||||
) { index, channel, isDragging ->
|
||||
val elevation by animateDpAsState(if (isDragging) 8.dp else 4.dp, label = "drag")
|
||||
ChannelCard(
|
||||
elevation = elevation,
|
||||
index = index,
|
||||
title = channel.name.ifEmpty { modemPresetName },
|
||||
enabled = enabled,
|
||||
|
||||
Reference in New Issue
Block a user