mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
Remove NodeChip long-click (#3220)
This commit is contained in:
parent
98ef72d240
commit
48a27ba022
29 changed files with 248 additions and 588 deletions
|
|
@ -196,7 +196,6 @@ fun NodeDetailScreen(
|
|||
val environmentState by viewModel.environmentState.collectAsStateWithLifecycle()
|
||||
val lastTracerouteTime by nodeDetailViewModel.lastTraceRouteTime.collectAsStateWithLifecycle()
|
||||
val ourNode by nodeDetailViewModel.ourNodeInfo.collectAsStateWithLifecycle()
|
||||
val connectionState by nodeDetailViewModel.connectionState.collectAsStateWithLifecycle()
|
||||
|
||||
val availableLogs by
|
||||
remember(state, environmentState) {
|
||||
|
|
@ -225,12 +224,11 @@ fun NodeDetailScreen(
|
|||
MainAppBar(
|
||||
title = node?.user?.longName ?: "",
|
||||
ourNode = ourNode,
|
||||
isConnected = connectionState.isConnected(),
|
||||
showNodeChip = false,
|
||||
canNavigateUp = true,
|
||||
onNavigateUp = onNavigateUp,
|
||||
actions = {},
|
||||
onAction = {},
|
||||
onClickChip = {},
|
||||
)
|
||||
},
|
||||
) { paddingValues ->
|
||||
|
|
@ -642,7 +640,9 @@ private fun DeviceActions(
|
|||
displayIgnoreDialog = false
|
||||
displayRemoveDialog = false
|
||||
},
|
||||
onAction = { onAction(NodeDetailAction.HandleNodeMenuAction(it)) },
|
||||
onConfirmFavorite = { onAction(NodeDetailAction.HandleNodeMenuAction(NodeMenuAction.Favorite(it))) },
|
||||
onConfirmIgnore = { onAction(NodeDetailAction.HandleNodeMenuAction(NodeMenuAction.Ignore(it))) },
|
||||
onConfirmRemove = { onAction(NodeDetailAction.HandleNodeMenuAction(NodeMenuAction.Remove(it))) },
|
||||
)
|
||||
TitledCard(title = stringResource(R.string.actions)) {
|
||||
SettingsItem(
|
||||
|
|
|
|||
|
|
@ -45,8 +45,6 @@ constructor(
|
|||
|
||||
val ourNodeInfo: StateFlow<Node?> = nodeRepository.ourNodeInfo
|
||||
|
||||
val connectionState = serviceRepository.connectionState
|
||||
|
||||
private val _lastTraceRouteTime = MutableStateFlow<Long?>(null)
|
||||
val lastTraceRouteTime: StateFlow<Long?> = _lastTraceRouteTime.asStateFlow()
|
||||
|
||||
|
|
|
|||
|
|
@ -29,9 +29,20 @@ import androidx.compose.foundation.layout.padding
|
|||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.DoDisturbOn
|
||||
import androidx.compose.material.icons.outlined.DoDisturbOn
|
||||
import androidx.compose.material.icons.rounded.DeleteOutline
|
||||
import androidx.compose.material.icons.rounded.Star
|
||||
import androidx.compose.material.icons.rounded.StarBorder
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.animateFloatingActionButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
|
|
@ -41,34 +52,31 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.DpOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.AdminProtos
|
||||
import com.geeksville.mesh.service.ConnectionState
|
||||
import com.geeksville.mesh.ui.common.components.MainAppBar
|
||||
import com.geeksville.mesh.ui.node.components.NodeActionDialogs
|
||||
import com.geeksville.mesh.ui.node.components.NodeFilterTextField
|
||||
import com.geeksville.mesh.ui.node.components.NodeItem
|
||||
import com.geeksville.mesh.ui.node.components.NodeMenuAction
|
||||
import com.geeksville.mesh.ui.sharing.AddContactFAB
|
||||
import com.geeksville.mesh.ui.sharing.SharedContactDialog
|
||||
import com.geeksville.mesh.ui.sharing.supportsQrCodeSharing
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.DeviceVersion
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.rememberTimeTickWithLifecycle
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusRed
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
@Composable
|
||||
fun NodeScreen(
|
||||
nodesViewModel: NodesViewModel = hiltViewModel(),
|
||||
navigateToMessages: (String) -> Unit,
|
||||
navigateToNodeDetails: (Int) -> Unit,
|
||||
) {
|
||||
fun NodeScreen(nodesViewModel: NodesViewModel = hiltViewModel(), navigateToNodeDetails: (Int) -> Unit) {
|
||||
val state by nodesViewModel.nodesUiState.collectAsStateWithLifecycle()
|
||||
|
||||
val nodes by nodesViewModel.nodeList.collectAsStateWithLifecycle()
|
||||
|
|
@ -83,11 +91,6 @@ fun NodeScreen(
|
|||
val currentTimeMillis = rememberTimeTickWithLifecycle()
|
||||
val connectionState by nodesViewModel.connectionState.collectAsStateWithLifecycle()
|
||||
|
||||
var showSharedContact: Node? by remember { mutableStateOf(null) }
|
||||
if (showSharedContact != null) {
|
||||
SharedContactDialog(contact = showSharedContact, onDismiss = { showSharedContact = null })
|
||||
}
|
||||
|
||||
val isScrollInProgress by remember { derivedStateOf { listState.isScrollInProgress } }
|
||||
Scaffold(
|
||||
topBar = {
|
||||
|
|
@ -95,12 +98,11 @@ fun NodeScreen(
|
|||
title = stringResource(R.string.nodes),
|
||||
subtitle = stringResource(R.string.node_count_template, onlineNodeCount, totalNodeCount),
|
||||
ourNode = ourNode,
|
||||
isConnected = connectionState.isConnected(),
|
||||
showNodeChip = false,
|
||||
canNavigateUp = false,
|
||||
onNavigateUp = {},
|
||||
actions = {},
|
||||
onAction = {},
|
||||
onClickChip = {},
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
|
|
@ -151,37 +153,124 @@ fun NodeScreen(
|
|||
}
|
||||
|
||||
items(nodes, key = { it.num }) { node ->
|
||||
NodeItem(
|
||||
modifier = Modifier.animateItem(),
|
||||
thisNode = ourNode,
|
||||
thatNode = node,
|
||||
distanceUnits = state.distanceUnits,
|
||||
tempInFahrenheit = state.tempInFahrenheit,
|
||||
onAction = { menuItem ->
|
||||
when (menuItem) {
|
||||
is NodeMenuAction.Remove -> nodesViewModel.removeNode(node.num)
|
||||
is NodeMenuAction.Ignore -> nodesViewModel.ignoreNode(node)
|
||||
is NodeMenuAction.Favorite -> nodesViewModel.favoriteNode(node)
|
||||
is NodeMenuAction.DirectMessage -> {
|
||||
val hasPKC = nodesViewModel.ourNodeInfo.value?.hasPKC == true && node.hasPKC
|
||||
val channel = if (hasPKC) DataPacket.PKC_CHANNEL_INDEX else node.channel
|
||||
navigateToMessages("$channel${node.user.id}")
|
||||
}
|
||||
var displayFavoriteDialog by remember { mutableStateOf(false) }
|
||||
var displayIgnoreDialog by remember { mutableStateOf(false) }
|
||||
var displayRemoveDialog by remember { mutableStateOf(false) }
|
||||
|
||||
is NodeMenuAction.RequestUserInfo -> nodesViewModel.requestUserInfo(node.num)
|
||||
is NodeMenuAction.RequestPosition -> nodesViewModel.requestPosition(node.num)
|
||||
is NodeMenuAction.TraceRoute -> nodesViewModel.requestTraceroute(node.num)
|
||||
is NodeMenuAction.MoreDetails -> navigateToNodeDetails(node.num)
|
||||
is NodeMenuAction.Share -> showSharedContact = node
|
||||
}
|
||||
NodeActionDialogs(
|
||||
node = node,
|
||||
displayFavoriteDialog = displayFavoriteDialog,
|
||||
displayIgnoreDialog = displayIgnoreDialog,
|
||||
displayRemoveDialog = displayRemoveDialog,
|
||||
onDismissMenuRequest = {
|
||||
displayFavoriteDialog = false
|
||||
displayIgnoreDialog = false
|
||||
displayRemoveDialog = false
|
||||
},
|
||||
expanded = state.showDetails,
|
||||
currentTimeMillis = currentTimeMillis,
|
||||
isConnected = connectionState.isConnected(),
|
||||
onConfirmFavorite = nodesViewModel::favoriteNode,
|
||||
onConfirmIgnore = nodesViewModel::ignoreNode,
|
||||
onConfirmRemove = { nodesViewModel.removeNode(it.num) },
|
||||
)
|
||||
|
||||
Box {
|
||||
var showContextMenu by remember { mutableStateOf(false) }
|
||||
|
||||
NodeItem(
|
||||
modifier = Modifier.animateItem(),
|
||||
thisNode = ourNode,
|
||||
thatNode = node,
|
||||
distanceUnits = state.distanceUnits,
|
||||
tempInFahrenheit = state.tempInFahrenheit,
|
||||
onClickChip = { navigateToNodeDetails(it.num) },
|
||||
onLongClick = { showContextMenu = true },
|
||||
expanded = state.showDetails,
|
||||
currentTimeMillis = currentTimeMillis,
|
||||
isConnected = connectionState.isConnected(),
|
||||
)
|
||||
val isThisNode = remember(node) { ourNode?.num == node.num }
|
||||
ContextMenu(
|
||||
expanded = !isThisNode && showContextMenu,
|
||||
node = node,
|
||||
onClickFavorite = { displayFavoriteDialog = true },
|
||||
onClickIgnore = { displayIgnoreDialog = true },
|
||||
onClickRemove = { displayRemoveDialog = true },
|
||||
onDismiss = { showContextMenu = false },
|
||||
)
|
||||
}
|
||||
}
|
||||
item { Spacer(modifier = Modifier.height(88.dp)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ContextMenu(
|
||||
expanded: Boolean,
|
||||
node: Node,
|
||||
onClickFavorite: (Node) -> Unit,
|
||||
onClickIgnore: (Node) -> Unit,
|
||||
onClickRemove: (Node) -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
DropdownMenu(expanded = expanded, onDismissRequest = onDismiss, offset = DpOffset(16.dp, 0.dp)) {
|
||||
val isFavorite = node.isFavorite
|
||||
val isIgnored = node.isIgnored
|
||||
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
onClickFavorite(node)
|
||||
onDismiss()
|
||||
},
|
||||
enabled = !isIgnored,
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = if (isFavorite) Icons.Rounded.Star else Icons.Rounded.StarBorder,
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
text = { Text(stringResource(if (isFavorite) R.string.remove_favorite else R.string.add_favorite)) },
|
||||
)
|
||||
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
onClickIgnore(node)
|
||||
onDismiss()
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = if (isIgnored) Icons.Filled.DoDisturbOn else Icons.Outlined.DoDisturbOn,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.StatusRed,
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Text(
|
||||
text = stringResource(if (isIgnored) R.string.remove_ignored else R.string.ignore),
|
||||
color = MaterialTheme.colorScheme.StatusRed,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
onClickRemove(node)
|
||||
onDismiss()
|
||||
},
|
||||
enabled = !isIgnored,
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.DeleteOutline,
|
||||
contentDescription = null,
|
||||
tint = if (isIgnored) LocalContentColor.current else MaterialTheme.colorScheme.StatusRed,
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Text(
|
||||
text = stringResource(R.string.remove),
|
||||
color = if (isIgnored) Color.Unspecified else MaterialTheme.colorScheme.StatusRed,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@ import org.meshtastic.core.data.repository.NodeRepository
|
|||
import org.meshtastic.core.data.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.database.model.NodeSortOption
|
||||
import org.meshtastic.core.model.Position
|
||||
import org.meshtastic.core.prefs.ui.UiPrefs
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
|
@ -170,22 +169,14 @@ constructor(
|
|||
fun addSharedContact(sharedContact: AdminProtos.SharedContact) =
|
||||
viewModelScope.launch { serviceRepository.onServiceAction(ServiceAction.AddSharedContact(sharedContact)) }
|
||||
|
||||
fun removeNode(nodeNum: Int) = viewModelScope.launch(Dispatchers.IO) {
|
||||
Timber.i("Removing node '$nodeNum'")
|
||||
try {
|
||||
val packetId = serviceRepository.meshService?.packetId ?: return@launch
|
||||
serviceRepository.meshService?.removeByNodenum(packetId, nodeNum)
|
||||
nodeRepository.deleteNode(nodeNum)
|
||||
} catch (ex: RemoteException) {
|
||||
Timber.e("Remove node error: ${ex.message}")
|
||||
}
|
||||
fun setSharedContactRequested(sharedContact: AdminProtos.SharedContact?) {
|
||||
_sharedContactRequested.value = sharedContact
|
||||
}
|
||||
|
||||
fun ignoreNode(node: Node) = viewModelScope.launch {
|
||||
try {
|
||||
serviceRepository.onServiceAction(ServiceAction.Ignore(node))
|
||||
} catch (ex: RemoteException) {
|
||||
Timber.e(ex, "Ignore node error")
|
||||
private fun toggle(state: MutableStateFlow<Boolean>, onChanged: (newValue: Boolean) -> Unit) {
|
||||
(!state.value).let { toggled ->
|
||||
state.update { toggled }
|
||||
onChanged(toggled)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -197,42 +188,22 @@ constructor(
|
|||
}
|
||||
}
|
||||
|
||||
fun requestUserInfo(destNum: Int) {
|
||||
Timber.i("Requesting UserInfo for '$destNum'")
|
||||
fun ignoreNode(node: Node) = viewModelScope.launch {
|
||||
try {
|
||||
serviceRepository.meshService?.requestUserInfo(destNum)
|
||||
serviceRepository.onServiceAction(ServiceAction.Ignore(node))
|
||||
} catch (ex: RemoteException) {
|
||||
Timber.e("Request NodeInfo error: ${ex.message}")
|
||||
Timber.e(ex, "Ignore node error")
|
||||
}
|
||||
}
|
||||
|
||||
fun requestPosition(destNum: Int, position: Position = Position(0.0, 0.0, 0)) {
|
||||
Timber.i("Requesting position for '$destNum'")
|
||||
fun removeNode(nodeNum: Int) = viewModelScope.launch(Dispatchers.IO) {
|
||||
Timber.i("Removing node '$nodeNum'")
|
||||
try {
|
||||
serviceRepository.meshService?.requestPosition(destNum, position)
|
||||
val packetId = serviceRepository.meshService?.packetId ?: return@launch
|
||||
serviceRepository.meshService?.removeByNodenum(packetId, nodeNum)
|
||||
nodeRepository.deleteNode(nodeNum)
|
||||
} catch (ex: RemoteException) {
|
||||
Timber.e("Request position error: ${ex.message}")
|
||||
}
|
||||
}
|
||||
|
||||
fun requestTraceroute(destNum: Int) {
|
||||
Timber.i("Requesting traceroute for '$destNum'")
|
||||
try {
|
||||
val packetId = serviceRepository.meshService?.packetId ?: return
|
||||
serviceRepository.meshService?.requestTraceroute(packetId, destNum)
|
||||
} catch (ex: RemoteException) {
|
||||
Timber.e("Request traceroute error: ${ex.message}")
|
||||
}
|
||||
}
|
||||
|
||||
fun setSharedContactRequested(sharedContact: AdminProtos.SharedContact?) {
|
||||
_sharedContactRequested.value = sharedContact
|
||||
}
|
||||
|
||||
private fun toggle(state: MutableStateFlow<Boolean>, onChanged: (newValue: Boolean) -> Unit) {
|
||||
(!state.value).let { toggled ->
|
||||
state.update { toggled }
|
||||
onChanged(toggled)
|
||||
Timber.e("Remove node error: ${ex.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,9 +17,7 @@
|
|||
|
||||
package com.geeksville.mesh.ui.node.components
|
||||
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
|
|
@ -29,10 +27,7 @@ import androidx.compose.material3.ElevatedAssistChip
|
|||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
|
|
@ -47,65 +42,33 @@ import com.geeksville.mesh.TelemetryProtos
|
|||
import org.meshtastic.core.database.model.Node
|
||||
|
||||
@Composable
|
||||
fun NodeChip(
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
node: Node,
|
||||
isThisNode: Boolean,
|
||||
isConnected: Boolean,
|
||||
onAction: (NodeMenuAction) -> Unit,
|
||||
) {
|
||||
fun NodeChip(modifier: Modifier = Modifier, node: Node, onClick: (Node) -> Unit = {}) {
|
||||
val isIgnored = node.isIgnored
|
||||
val (textColor, nodeColor) = node.colors
|
||||
var menuExpanded by remember { mutableStateOf(false) }
|
||||
val inputChipInteractionSource = remember { MutableInteractionSource() }
|
||||
Box {
|
||||
ElevatedAssistChip(
|
||||
modifier =
|
||||
modifier.width(IntrinsicSize.Min).defaultMinSize(minWidth = 72.dp).semantics {
|
||||
contentDescription = node.user.shortName.ifEmpty { "Node" }
|
||||
},
|
||||
elevation = AssistChipDefaults.elevatedAssistChipElevation(),
|
||||
colors =
|
||||
AssistChipDefaults.elevatedAssistChipColors(
|
||||
containerColor = Color(nodeColor),
|
||||
labelColor = Color(textColor),
|
||||
),
|
||||
label = {
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = node.user.shortName.ifEmpty { "???" },
|
||||
fontSize = MaterialTheme.typography.labelLarge.fontSize,
|
||||
textDecoration = TextDecoration.LineThrough.takeIf { isIgnored },
|
||||
textAlign = TextAlign.Center,
|
||||
maxLines = 1,
|
||||
)
|
||||
},
|
||||
onClick = {},
|
||||
interactionSource = inputChipInteractionSource,
|
||||
)
|
||||
Box(
|
||||
modifier =
|
||||
Modifier.matchParentSize()
|
||||
.combinedClickable(
|
||||
enabled = enabled,
|
||||
onClick = { onAction(NodeMenuAction.MoreDetails(node)) },
|
||||
onLongClick = { menuExpanded = true },
|
||||
interactionSource = inputChipInteractionSource,
|
||||
indication = null,
|
||||
)
|
||||
.semantics { contentDescription = node.user.shortName.ifEmpty { "Node" } },
|
||||
)
|
||||
}
|
||||
NodeMenu(
|
||||
expanded = menuExpanded,
|
||||
node = node,
|
||||
showFullMenu = !isThisNode && isConnected,
|
||||
onDismissMenuRequest = { menuExpanded = false },
|
||||
onAction = {
|
||||
menuExpanded = false
|
||||
onAction(it)
|
||||
ElevatedAssistChip(
|
||||
modifier =
|
||||
modifier.width(IntrinsicSize.Min).defaultMinSize(minWidth = 72.dp).semantics {
|
||||
contentDescription = node.user.shortName.ifEmpty { "Node" }
|
||||
},
|
||||
elevation = AssistChipDefaults.elevatedAssistChipElevation(),
|
||||
colors =
|
||||
AssistChipDefaults.elevatedAssistChipColors(
|
||||
containerColor = Color(nodeColor),
|
||||
labelColor = Color(textColor),
|
||||
),
|
||||
label = {
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = node.user.shortName.ifEmpty { "???" },
|
||||
fontSize = MaterialTheme.typography.labelLarge.fontSize,
|
||||
textDecoration = TextDecoration.LineThrough.takeIf { isIgnored },
|
||||
textAlign = TextAlign.Center,
|
||||
maxLines = 1,
|
||||
)
|
||||
},
|
||||
onClick = { onClick(node) },
|
||||
interactionSource = inputChipInteractionSource,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -123,5 +86,5 @@ fun NodeChipPreview() {
|
|||
environmentMetrics =
|
||||
TelemetryProtos.EnvironmentMetrics.newBuilder().setTemperature(25f).setRelativeHumidity(60f).build(),
|
||||
)
|
||||
NodeChip(node = node, isThisNode = false, isConnected = true, onAction = {})
|
||||
NodeChip(node = node)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
package com.geeksville.mesh.ui.node.components
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
|
|
@ -68,7 +69,8 @@ fun NodeItem(
|
|||
distanceUnits: Int,
|
||||
tempInFahrenheit: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
onAction: (NodeMenuAction) -> Unit = {},
|
||||
onClickChip: (Node) -> Unit = {},
|
||||
onLongClick: () -> Unit = {},
|
||||
expanded: Boolean = false,
|
||||
currentTimeMillis: Long,
|
||||
isConnected: Boolean = false,
|
||||
|
|
@ -123,13 +125,16 @@ fun NodeItem(
|
|||
|
||||
Card(
|
||||
modifier =
|
||||
modifier.fillMaxWidth().padding(horizontal = 8.dp, vertical = 4.dp).defaultMinSize(minHeight = 80.dp),
|
||||
onClick = { showDetails(!detailsShown) },
|
||||
modifier
|
||||
.combinedClickable(onClick = { showDetails(!detailsShown) }, onLongClick = onLongClick)
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||
.defaultMinSize(minHeight = 80.dp),
|
||||
colors = cardColors,
|
||||
) {
|
||||
Column(modifier = Modifier.fillMaxWidth().padding(8.dp)) {
|
||||
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
|
||||
NodeChip(node = thatNode, isThisNode = isThisNode, isConnected = isConnected, onAction = onAction)
|
||||
NodeChip(node = thatNode, onClick = onClickChip)
|
||||
|
||||
NodeKeyStatusIcon(
|
||||
hasPKC = thatNode.hasPKC,
|
||||
|
|
|
|||
|
|
@ -17,166 +17,12 @@
|
|||
|
||||
package com.geeksville.mesh.ui.node.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Star
|
||||
import androidx.compose.material.icons.twotone.StarBorder
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.database.model.isUnmessageableRole
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.SimpleAlertDialog
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun NodeMenu(
|
||||
expanded: Boolean,
|
||||
node: Node,
|
||||
showFullMenu: Boolean = false,
|
||||
onDismissMenuRequest: () -> Unit,
|
||||
onAction: (NodeMenuAction) -> Unit,
|
||||
) {
|
||||
val isUnmessageable =
|
||||
if (node.user.hasIsUnmessagable()) {
|
||||
node.user.isUnmessagable
|
||||
} else {
|
||||
// for older firmwares
|
||||
node.user.role?.isUnmessageableRole() == true
|
||||
}
|
||||
|
||||
var displayFavoriteDialog by remember { mutableStateOf(false) }
|
||||
var displayIgnoreDialog by remember { mutableStateOf(false) }
|
||||
var displayRemoveDialog by remember { mutableStateOf(false) }
|
||||
val dialogDismissRequest = {
|
||||
displayFavoriteDialog = false
|
||||
displayIgnoreDialog = false
|
||||
displayRemoveDialog = false
|
||||
onDismissMenuRequest()
|
||||
}
|
||||
val onMenuAction: (NodeMenuAction) -> Unit = {
|
||||
dialogDismissRequest()
|
||||
onDismissMenuRequest()
|
||||
onAction(it)
|
||||
}
|
||||
NodeActionDialogs(
|
||||
node = node,
|
||||
displayFavoriteDialog = displayFavoriteDialog,
|
||||
displayIgnoreDialog = displayIgnoreDialog,
|
||||
displayRemoveDialog = displayRemoveDialog,
|
||||
onDismissMenuRequest = dialogDismissRequest,
|
||||
onAction = onMenuAction,
|
||||
)
|
||||
DropdownMenu(
|
||||
modifier = Modifier.background(MaterialTheme.colorScheme.background.copy(alpha = 1f)),
|
||||
expanded = expanded,
|
||||
onDismissRequest = onDismissMenuRequest,
|
||||
) {
|
||||
if (showFullMenu) {
|
||||
if (!isUnmessageable) {
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
dialogDismissRequest()
|
||||
onMenuAction(NodeMenuAction.DirectMessage(node))
|
||||
},
|
||||
text = { Text(stringResource(R.string.direct_message)) },
|
||||
)
|
||||
}
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
dialogDismissRequest()
|
||||
onMenuAction(NodeMenuAction.RequestUserInfo(node))
|
||||
},
|
||||
text = { Text(stringResource(R.string.exchange_userinfo)) },
|
||||
)
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
dialogDismissRequest()
|
||||
onMenuAction(NodeMenuAction.RequestPosition(node))
|
||||
},
|
||||
text = { Text(stringResource(R.string.exchange_position)) },
|
||||
)
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
dialogDismissRequest()
|
||||
onMenuAction(NodeMenuAction.TraceRoute(node))
|
||||
},
|
||||
text = { Text(stringResource(R.string.traceroute)) },
|
||||
)
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
dialogDismissRequest()
|
||||
displayFavoriteDialog = true
|
||||
},
|
||||
enabled = !node.isIgnored,
|
||||
text = { Text(stringResource(R.string.favorite)) },
|
||||
trailingIcon = {
|
||||
Icon(
|
||||
imageVector = if (node.isFavorite) Icons.Filled.Star else Icons.TwoTone.StarBorder,
|
||||
contentDescription = stringResource(R.string.favorite),
|
||||
)
|
||||
},
|
||||
)
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
dialogDismissRequest()
|
||||
displayIgnoreDialog = true
|
||||
},
|
||||
text = { Text(stringResource(R.string.ignore)) },
|
||||
trailingIcon = {
|
||||
Checkbox(
|
||||
checked = node.isIgnored,
|
||||
onCheckedChange = {
|
||||
dialogDismissRequest()
|
||||
displayIgnoreDialog = true
|
||||
},
|
||||
modifier = Modifier.size(24.dp),
|
||||
)
|
||||
},
|
||||
)
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
dialogDismissRequest()
|
||||
displayRemoveDialog = true
|
||||
},
|
||||
enabled = !node.isIgnored,
|
||||
text = { Text(stringResource(R.string.remove)) },
|
||||
)
|
||||
HorizontalDivider(Modifier.padding(vertical = 8.dp))
|
||||
}
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
dialogDismissRequest()
|
||||
onMenuAction(NodeMenuAction.Share(node))
|
||||
},
|
||||
text = { Text(stringResource(R.string.share_contact)) },
|
||||
)
|
||||
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
dialogDismissRequest()
|
||||
onMenuAction(NodeMenuAction.MoreDetails(node))
|
||||
},
|
||||
text = { Text(stringResource(R.string.more_details)) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NodeActionDialogs(
|
||||
node: Node,
|
||||
|
|
@ -184,7 +30,9 @@ fun NodeActionDialogs(
|
|||
displayIgnoreDialog: Boolean,
|
||||
displayRemoveDialog: Boolean,
|
||||
onDismissMenuRequest: () -> Unit,
|
||||
onAction: (NodeMenuAction) -> Unit,
|
||||
onConfirmFavorite: (Node) -> Unit,
|
||||
onConfirmIgnore: (Node) -> Unit,
|
||||
onConfirmRemove: (Node) -> Unit,
|
||||
) {
|
||||
if (displayFavoriteDialog) {
|
||||
SimpleAlertDialog(
|
||||
|
|
@ -196,7 +44,7 @@ fun NodeActionDialogs(
|
|||
),
|
||||
onConfirm = {
|
||||
onDismissMenuRequest()
|
||||
onAction(NodeMenuAction.Favorite(node))
|
||||
onConfirmFavorite(node)
|
||||
},
|
||||
onDismiss = onDismissMenuRequest,
|
||||
)
|
||||
|
|
@ -211,7 +59,7 @@ fun NodeActionDialogs(
|
|||
),
|
||||
onConfirm = {
|
||||
onDismissMenuRequest()
|
||||
onAction(NodeMenuAction.Ignore(node))
|
||||
onConfirmIgnore(node)
|
||||
},
|
||||
onDismiss = onDismissMenuRequest,
|
||||
)
|
||||
|
|
@ -222,7 +70,7 @@ fun NodeActionDialogs(
|
|||
text = R.string.remove_node_text,
|
||||
onConfirm = {
|
||||
onDismissMenuRequest()
|
||||
onAction(NodeMenuAction.Remove(node))
|
||||
onConfirmRemove(node)
|
||||
},
|
||||
onDismiss = onDismissMenuRequest,
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue