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
|
|
@ -45,8 +45,6 @@
|
|||
<ID>ComposableParamOrder:NodeFilterTextField.kt$NodeFilterTextField</ID>
|
||||
<ID>ComposableParamOrder:NodeItem.kt$NodeItem</ID>
|
||||
<ID>ComposableParamOrder:NodeKeyStatusIcon.kt$NodeKeyStatusIcon</ID>
|
||||
<ID>ComposableParamOrder:NodeMenu.kt$NodeMenu</ID>
|
||||
<ID>ComposableParamOrder:NodeScreen.kt$NodeScreen</ID>
|
||||
<ID>ComposableParamOrder:PaxMetrics.kt$PaxMetricsChart</ID>
|
||||
<ID>ComposableParamOrder:PermissionScreenLayout.kt$PermissionScreenLayout</ID>
|
||||
<ID>ComposableParamOrder:PowerMetrics.kt$PowerMetricsChart</ID>
|
||||
|
|
@ -91,11 +89,10 @@
|
|||
<ID>FinalNewline:UsbRepositoryModule.kt$com.geeksville.mesh.repository.usb.UsbRepositoryModule.kt</ID>
|
||||
<ID>ForbiddenComment:SafeBluetooth.kt$SafeBluetooth$// TODO: display some kind of UI about restarting BLE</ID>
|
||||
<ID>LambdaParameterEventTrailing:Channel.kt$onConfirm</ID>
|
||||
<ID>LambdaParameterEventTrailing:MainAppBar.kt$onAction</ID>
|
||||
<ID>LambdaParameterEventTrailing:MainAppBar.kt$onClickChip</ID>
|
||||
<ID>LambdaParameterEventTrailing:Message.kt$onClick</ID>
|
||||
<ID>LambdaParameterEventTrailing:Message.kt$onSendMessage</ID>
|
||||
<ID>LambdaParameterEventTrailing:MessageList.kt$onReply</ID>
|
||||
<ID>LambdaParameterEventTrailing:NodeChip.kt$onAction</ID>
|
||||
<ID>LambdaParameterEventTrailing:NodeDetail.kt$onClick</ID>
|
||||
<ID>LambdaParameterEventTrailing:NodeDetail.kt$onSaveNotes</ID>
|
||||
<ID>LambdaParameterInRestartableEffect:Channel.kt$onConfirm</ID>
|
||||
|
|
@ -119,7 +116,6 @@
|
|||
<ID>LongMethod:StoreForwardConfigItemList.kt$@Composable fun StoreForwardConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel())</ID>
|
||||
<ID>LongMethod:TelemetryConfigItemList.kt$@Composable fun TelemetryConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel())</ID>
|
||||
<ID>LongMethod:UserConfigItemList.kt$@Composable fun UserConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel())</ID>
|
||||
<ID>LongParameterList:MessageList.kt$( nodes: List<Node>, ourNode: Node?, isConnected: Boolean, modifier: Modifier = Modifier, listState: LazyListState = rememberLazyListState(), messages: List<Message>, selectedIds: MutableState<Set<Long>>, onUnreadChanged: (Long) -> Unit, onSendReaction: (String, Int) -> Unit, onNodeMenuAction: (NodeMenuAction) -> Unit, onDeleteMessages: (List<Long>) -> Unit, onSendMessage: (messageText: String, contactKey: String) -> Unit, contactKey: String, onReply: (Message?) -> Unit, )</ID>
|
||||
<ID>LongParameterList:MessageViewModel.kt$MessageViewModel$( private val nodeRepository: NodeRepository, radioConfigRepository: RadioConfigRepository, quickChatActionRepository: QuickChatActionRepository, private val serviceRepository: ServiceRepository, private val packetRepository: PacketRepository, private val uiPrefs: UiPrefs, private val meshServiceNotifications: MeshServiceNotifications, )</ID>
|
||||
<ID>MagicNumber:BluetoothInterface.kt$BluetoothInterface$1000</ID>
|
||||
<ID>MagicNumber:BluetoothInterface.kt$BluetoothInterface$500</ID>
|
||||
|
|
@ -202,7 +198,6 @@
|
|||
<ID>ModifierMissing:MessageActions.kt$ReplyButton</ID>
|
||||
<ID>ModifierMissing:NetworkConfigItemList.kt$NetworkConfigScreen</ID>
|
||||
<ID>ModifierMissing:NetworkDevices.kt$NetworkDevices</ID>
|
||||
<ID>ModifierMissing:NodeMenu.kt$NodeMenu</ID>
|
||||
<ID>ModifierMissing:NodeScreen.kt$NodeScreen</ID>
|
||||
<ID>ModifierMissing:NodeStatusIcons.kt$NodeStatusIcons</ID>
|
||||
<ID>ModifierMissing:PaxMetrics.kt$PaxMetricsItem</ID>
|
||||
|
|
@ -231,7 +226,6 @@
|
|||
<ID>ModifierNotUsedAtRoot:EditDeviceProfileDialog.kt$modifier = modifier.weight(1f)</ID>
|
||||
<ID>ModifierNotUsedAtRoot:EnvironmentCharts.kt$modifier = modifier.width(dp)</ID>
|
||||
<ID>ModifierNotUsedAtRoot:EnvironmentCharts.kt$modifier.width(dp)</ID>
|
||||
<ID>ModifierNotUsedAtRoot:NodeChip.kt$modifier = modifier.width(IntrinsicSize.Min).defaultMinSize(minWidth = 72.dp).semantics { contentDescription = node.user.shortName.ifEmpty { "Node" } }</ID>
|
||||
<ID>ModifierNotUsedAtRoot:PaxMetrics.kt$modifier.width(dp)</ID>
|
||||
<ID>ModifierNotUsedAtRoot:PowerMetrics.kt$modifier = modifier.weight(weight = Y_AXIS_WEIGHT)</ID>
|
||||
<ID>ModifierNotUsedAtRoot:PowerMetrics.kt$modifier = modifier.width(dp)</ID>
|
||||
|
|
@ -401,7 +395,7 @@
|
|||
<ID>ViewModelForwarding:Main.kt$VersionChecks(uIViewModel)</ID>
|
||||
<ID>ViewModelInjection:DebugSearch.kt$viewModel</ID>
|
||||
<ID>WildcardImport:UsbRepository.kt$import kotlinx.coroutines.flow.*</ID>
|
||||
<ID>Wrapping:Message.kt${ event -> when (event) { is MessageScreenEvent.SendMessage -> { viewModel.sendMessage(event.text, contactKey, event.replyingToPacketId) if (event.replyingToPacketId != null) replyingToPacketId = null messageInputState.clearText() } is MessageScreenEvent.SendReaction -> viewModel.sendReaction(event.emoji, event.messageId, contactKey) is MessageScreenEvent.DeleteMessages -> { viewModel.deleteMessages(event.ids) selectedMessageIds.value = emptySet() showDeleteDialog = false } is MessageScreenEvent.ClearUnreadCount -> viewModel.clearUnreadCount(contactKey, event.lastReadMessageId) is MessageScreenEvent.HandleNodeMenuAction -> { when (val action = event.action) { is NodeMenuAction.DirectMessage -> { val hasPKC = ourNode?.hasPKC == true && action.node.hasPKC val targetChannel = if (hasPKC) { DataPacket.PKC_CHANNEL_INDEX } else { action.node.channel } navigateToMessages("$targetChannel${action.node.user.id}") } is NodeMenuAction.MoreDetails -> navigateToNodeDetails(action.node.num) is NodeMenuAction.Share -> sharedContact = action.node else -> viewModel.handleNodeMenuAction(action) } } is MessageScreenEvent.SetTitle -> viewModel.setTitle(event.title) is MessageScreenEvent.NavigateToMessages -> navigateToMessages(event.contactKey) is MessageScreenEvent.NavigateToNodeDetails -> navigateToNodeDetails(event.nodeNum) MessageScreenEvent.NavigateBack -> onNavigateBack() is MessageScreenEvent.CopyToClipboard -> { clipboardManager.nativeClipboard.setPrimaryClip(ClipData.newPlainText(event.text, event.text)) selectedMessageIds.value = emptySet() } } }</ID>
|
||||
<ID>Wrapping:Message.kt${ event -> when (event) { is MessageScreenEvent.SendMessage -> { viewModel.sendMessage(event.text, contactKey, event.replyingToPacketId) if (event.replyingToPacketId != null) replyingToPacketId = null messageInputState.clearText() } is MessageScreenEvent.SendReaction -> viewModel.sendReaction(event.emoji, event.messageId, contactKey) is MessageScreenEvent.DeleteMessages -> { viewModel.deleteMessages(event.ids) selectedMessageIds.value = emptySet() showDeleteDialog = false } is MessageScreenEvent.ClearUnreadCount -> viewModel.clearUnreadCount(contactKey, event.lastReadMessageId) is MessageScreenEvent.NodeDetails -> navigateToNodeDetails(event.node.num) is MessageScreenEvent.SetTitle -> viewModel.setTitle(event.title) is MessageScreenEvent.NavigateToMessages -> navigateToMessages(event.contactKey) is MessageScreenEvent.NavigateToNodeDetails -> navigateToNodeDetails(event.nodeNum) MessageScreenEvent.NavigateBack -> onNavigateBack() is MessageScreenEvent.CopyToClipboard -> { clipboardManager.nativeClipboard.setPrimaryClip(ClipData.newPlainText(event.text, event.text)) selectedMessageIds.value = emptySet() } } }</ID>
|
||||
<ID>Wrapping:SerialConnectionImpl.kt$SerialConnectionImpl$(</ID>
|
||||
<ID>Wrapping:SerialConnectionImpl.kt$SerialConnectionImpl$(port, object : SerialInputOutputManager.Listener { override fun onNewData(data: ByteArray) { listener.onDataReceived(data) } override fun onRunError(e: Exception?) { closed.set(true) ignoreException { port.dtr = false port.rts = false port.close() } closedLatch.countDown() listener.onDisconnected(e) } })</ID>
|
||||
<ID>Wrapping:SerialInterface.kt$SerialInterface$(</ID>
|
||||
|
|
|
|||
|
|
@ -65,7 +65,6 @@ class MessageItemTest {
|
|||
onClick = {},
|
||||
onLongClick = {},
|
||||
onStatusClick = {},
|
||||
isConnected = true,
|
||||
ourNode = testNode,
|
||||
)
|
||||
}
|
||||
|
|
@ -105,7 +104,6 @@ class MessageItemTest {
|
|||
onClick = {},
|
||||
onLongClick = {},
|
||||
onStatusClick = {},
|
||||
isConnected = true,
|
||||
ourNode = testNode,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -406,14 +406,7 @@ fun MapView(
|
|||
val alpha = (index.toFloat() / (sortedPositions.size.toFloat() - 1))
|
||||
val color = Color(focusedNode!!.colors.second).copy(alpha = alpha)
|
||||
if (index == sortedPositions.lastIndex) {
|
||||
MarkerComposable(state = markerState, zIndex = 1f) {
|
||||
NodeChip(
|
||||
node = focusedNode,
|
||||
isThisNode = false,
|
||||
isConnected = false,
|
||||
onAction = {},
|
||||
)
|
||||
}
|
||||
MarkerComposable(state = markerState, zIndex = 1f) { NodeChip(node = focusedNode) }
|
||||
} else {
|
||||
MarkerInfoWindowComposable(
|
||||
state = markerState,
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ fun ClusterItemsListDialog(
|
|||
@Composable
|
||||
private fun ClusterDialogListItem(item: NodeClusterItem, onClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
ListItem(
|
||||
leadingContent = { NodeChip(node = item.node, enabled = false, isThisNode = false, isConnected = false) {} },
|
||||
leadingContent = { NodeChip(node = item.node) },
|
||||
headlineContent = { Text(item.nodeTitle) },
|
||||
supportingContent = {
|
||||
if (item.nodeSnippet.isNotBlank()) {
|
||||
|
|
|
|||
|
|
@ -63,8 +63,6 @@ fun NodeClusterMarkers(
|
|||
navigateToNodeDetails(item.node.num)
|
||||
false
|
||||
},
|
||||
clusterItemContent = { clusterItem ->
|
||||
NodeChip(node = clusterItem.node, enabled = false, isThisNode = false, isConnected = false) {}
|
||||
},
|
||||
clusterItemContent = { clusterItem -> NodeChip(node = clusterItem.node) },
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,19 +44,17 @@ fun NodeMapScreen(
|
|||
val positions = state.positionLogs
|
||||
val destNum = state.node?.num
|
||||
val ourNodeInfo by uiViewModel.ourNodeInfo.collectAsStateWithLifecycle()
|
||||
val isConnected by uiViewModel.isConnectedStateFlow.collectAsStateWithLifecycle()
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
MainAppBar(
|
||||
title = state.node?.user?.longName ?: "",
|
||||
ourNode = ourNodeInfo,
|
||||
isConnected = isConnected,
|
||||
showNodeChip = false,
|
||||
canNavigateUp = true,
|
||||
onNavigateUp = navController::navigateUp,
|
||||
actions = {},
|
||||
onAction = {},
|
||||
onClickChip = {},
|
||||
)
|
||||
},
|
||||
) { paddingValues ->
|
||||
|
|
|
|||
|
|
@ -64,10 +64,7 @@ fun NavGraphBuilder.nodesGraph(navController: NavHostController, uiViewModel: UI
|
|||
composable<NodesRoutes.Nodes>(
|
||||
deepLinks = listOf(navDeepLink<NodesRoutes.Nodes>(basePath = "$DEEP_LINK_BASE_URI/nodes")),
|
||||
) {
|
||||
NodeScreen(
|
||||
navigateToMessages = { navController.navigate(ContactsRoutes.Messages(it)) },
|
||||
navigateToNodeDetails = { navController.navigate(NodesRoutes.NodeDetailGraph(it)) },
|
||||
)
|
||||
NodeScreen(navigateToNodeDetails = { navController.navigate(NodesRoutes.NodeDetailGraph(it)) })
|
||||
}
|
||||
nodeDetailGraph(navController, uiViewModel)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,14 +95,11 @@ import com.geeksville.mesh.ui.common.components.ScannedQrCodeDialog
|
|||
import com.geeksville.mesh.ui.connections.DeviceType
|
||||
import com.geeksville.mesh.ui.connections.components.TopLevelNavIcon
|
||||
import com.geeksville.mesh.ui.metrics.annotateTraceroute
|
||||
import com.geeksville.mesh.ui.node.components.NodeMenuAction
|
||||
import com.geeksville.mesh.ui.sharing.SharedContactDialog
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.google.accompanist.permissions.isGranted
|
||||
import com.google.accompanist.permissions.rememberPermissionState
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.DeviceVersion
|
||||
import org.meshtastic.core.navigation.ConnectionsRoutes
|
||||
import org.meshtastic.core.navigation.ContactsRoutes
|
||||
|
|
@ -337,11 +334,6 @@ fun MainScreen(uIViewModel: UIViewModel = hiltViewModel(), scanModel: BTScanMode
|
|||
) {
|
||||
Scaffold(snackbarHost = { SnackbarHost(uIViewModel.snackBarHostState) }) { _ ->
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
var sharedContact: Node? by remember { mutableStateOf(null) }
|
||||
if (sharedContact != null) {
|
||||
SharedContactDialog(contact = sharedContact, onDismiss = { sharedContact = null })
|
||||
}
|
||||
|
||||
fun NavDestination.hasGlobalAppBar(): Boolean =
|
||||
// List of screens to exclude from having the global app bar
|
||||
listOf(
|
||||
|
|
@ -384,22 +376,14 @@ fun MainScreen(uIViewModel: UIViewModel = hiltViewModel(), scanModel: BTScanMode
|
|||
MainAppBar(
|
||||
navController = navController,
|
||||
ourNode = ourNodeInfo,
|
||||
isConnected = connectionState.isConnected(),
|
||||
onAction = { action ->
|
||||
when (action) {
|
||||
is NodeMenuAction.MoreDetails -> {
|
||||
navController.navigate(
|
||||
NodesRoutes.NodeDetailGraph(action.node.num),
|
||||
{
|
||||
launchSingleTop = true
|
||||
restoreState = true
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
is NodeMenuAction.Share -> sharedContact = action.node
|
||||
else -> {}
|
||||
}
|
||||
onClickChip = {
|
||||
navController.navigate(
|
||||
NodesRoutes.NodeDetailGraph(it.num),
|
||||
{
|
||||
launchSingleTop = true
|
||||
restoreState = true
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,6 @@ import androidx.navigation.NavHostController
|
|||
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
import com.geeksville.mesh.ui.debug.DebugMenuActions
|
||||
import com.geeksville.mesh.ui.node.components.NodeChip
|
||||
import com.geeksville.mesh.ui.node.components.NodeMenuAction
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.navigation.ContactsRoutes
|
||||
import org.meshtastic.core.navigation.SettingsRoutes
|
||||
|
|
@ -58,8 +57,7 @@ fun MainAppBar(
|
|||
modifier: Modifier = Modifier,
|
||||
navController: NavHostController,
|
||||
ourNode: Node?,
|
||||
isConnected: Boolean,
|
||||
onAction: (NodeMenuAction) -> Unit,
|
||||
onClickChip: (Node) -> Unit,
|
||||
) {
|
||||
val backStackEntry by navController.currentBackStackEntryAsState()
|
||||
val currentDestination = backStackEntry?.destination
|
||||
|
|
@ -86,7 +84,6 @@ fun MainAppBar(
|
|||
subtitle = null,
|
||||
canNavigateUp = navController.previousBackStackEntry != null,
|
||||
ourNode = ourNode,
|
||||
isConnected = isConnected,
|
||||
showNodeChip = false,
|
||||
onNavigateUp = navController::navigateUp,
|
||||
actions = {
|
||||
|
|
@ -97,7 +94,7 @@ fun MainAppBar(
|
|||
}
|
||||
}
|
||||
},
|
||||
onAction = onAction,
|
||||
onClickChip = onClickChip,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -108,12 +105,11 @@ fun MainAppBar(
|
|||
title: String,
|
||||
subtitle: String? = null,
|
||||
ourNode: Node?,
|
||||
isConnected: Boolean,
|
||||
showNodeChip: Boolean,
|
||||
canNavigateUp: Boolean,
|
||||
onNavigateUp: () -> Unit,
|
||||
actions: @Composable () -> Unit,
|
||||
onAction: (NodeMenuAction) -> Unit,
|
||||
onClickChip: (Node) -> Unit,
|
||||
) {
|
||||
TopAppBar(
|
||||
title = {
|
||||
|
|
@ -147,13 +143,7 @@ fun MainAppBar(
|
|||
}
|
||||
},
|
||||
actions = {
|
||||
TopBarActions(
|
||||
ourNode = ourNode,
|
||||
isConnected = isConnected,
|
||||
showNodeChip = showNodeChip,
|
||||
actions = actions,
|
||||
onAction = onAction,
|
||||
)
|
||||
TopBarActions(ourNode = ourNode, showNodeChip = showNodeChip, actions = actions, onClickChip = onClickChip)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -161,20 +151,13 @@ fun MainAppBar(
|
|||
@Composable
|
||||
private fun TopBarActions(
|
||||
ourNode: Node?,
|
||||
isConnected: Boolean,
|
||||
showNodeChip: Boolean,
|
||||
actions: @Composable () -> Unit,
|
||||
onAction: (NodeMenuAction) -> Unit,
|
||||
onClickChip: (Node) -> Unit,
|
||||
) {
|
||||
AnimatedVisibility(visible = showNodeChip, enter = fadeIn(), exit = fadeOut()) {
|
||||
ourNode?.let {
|
||||
NodeChip(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
node = it,
|
||||
isThisNode = true,
|
||||
isConnected = isConnected,
|
||||
onAction = onAction,
|
||||
)
|
||||
ourNode?.let { node ->
|
||||
NodeChip(modifier = Modifier.padding(horizontal = 16.dp), node = node, onClick = onClickChip)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -189,7 +172,6 @@ private fun MainAppBarPreview(@PreviewParameter(BooleanProvider::class) canNavig
|
|||
title = "Title",
|
||||
subtitle = "Subtitle",
|
||||
ourNode = previewNode,
|
||||
isConnected = false,
|
||||
showNodeChip = true,
|
||||
canNavigateUp = canNavigateUp,
|
||||
onNavigateUp = {},
|
||||
|
|
|
|||
|
|
@ -71,14 +71,11 @@ import com.geeksville.mesh.ui.connections.components.ConnectionsSegmentedBar
|
|||
import com.geeksville.mesh.ui.connections.components.CurrentlyConnectedInfo
|
||||
import com.geeksville.mesh.ui.connections.components.NetworkDevices
|
||||
import com.geeksville.mesh.ui.connections.components.UsbDevices
|
||||
import com.geeksville.mesh.ui.node.components.NodeMenuAction
|
||||
import com.geeksville.mesh.ui.settings.components.SettingsItem
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import com.geeksville.mesh.ui.settings.radio.components.PacketResponseStateDialog
|
||||
import com.geeksville.mesh.ui.sharing.SharedContactDialog
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import kotlinx.coroutines.delay
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.navigation.Route
|
||||
import org.meshtastic.core.navigation.SettingsRoutes
|
||||
import org.meshtastic.core.strings.R
|
||||
|
|
@ -177,27 +174,17 @@ fun ConnectionsScreen(
|
|||
scanModel.setErrorText(context.getString(it, firmwareString))
|
||||
}
|
||||
}
|
||||
var showSharedContact by remember { mutableStateOf<Node?>(null) }
|
||||
if (showSharedContact != null) {
|
||||
SharedContactDialog(contact = showSharedContact, onDismiss = { showSharedContact = null })
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
MainAppBar(
|
||||
title = stringResource(R.string.connections),
|
||||
ourNode = ourNode,
|
||||
isConnected = connectionState.isConnected(),
|
||||
showNodeChip = ourNode != null && connectionState.isConnected(),
|
||||
canNavigateUp = false,
|
||||
onNavigateUp = {},
|
||||
actions = {},
|
||||
onAction = { action ->
|
||||
when (action) {
|
||||
is NodeMenuAction.MoreDetails -> onClickNodeChip(action.node.num)
|
||||
else -> {}
|
||||
}
|
||||
},
|
||||
onClickChip = { onClickNodeChip(it.num) },
|
||||
)
|
||||
},
|
||||
) { paddingValues ->
|
||||
|
|
@ -221,7 +208,6 @@ fun ConnectionsScreen(
|
|||
CurrentlyConnectedInfo(
|
||||
node = node,
|
||||
onNavigateToNodeDetails = onNavigateToNodeDetails,
|
||||
onSetShowSharedContact = { showSharedContact = it },
|
||||
onClickDisconnect = { scanModel.disconnect() },
|
||||
bluetoothRssi = bluetoothRssi,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ import androidx.compose.foundation.layout.padding
|
|||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
|
|
@ -41,7 +40,6 @@ import com.geeksville.mesh.MeshProtos
|
|||
import com.geeksville.mesh.PaxcountProtos
|
||||
import com.geeksville.mesh.TelemetryProtos
|
||||
import com.geeksville.mesh.ui.node.components.NodeChip
|
||||
import com.geeksville.mesh.ui.node.components.NodeMenuAction
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.MaterialBatteryInfo
|
||||
|
|
@ -54,7 +52,6 @@ import org.meshtastic.core.ui.theme.StatusColors.StatusRed
|
|||
fun CurrentlyConnectedInfo(
|
||||
node: Node,
|
||||
onNavigateToNodeDetails: (Int) -> Unit,
|
||||
onSetShowSharedContact: (Node) -> Unit,
|
||||
onClickDisconnect: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
bluetoothRssi: Int? = null,
|
||||
|
|
@ -75,19 +72,7 @@ fun CurrentlyConnectedInfo(
|
|||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
NodeChip(
|
||||
node = node,
|
||||
isThisNode = true,
|
||||
isConnected = true,
|
||||
onAction = { action ->
|
||||
when (action) {
|
||||
is NodeMenuAction.MoreDetails -> onNavigateToNodeDetails(node.num)
|
||||
|
||||
is NodeMenuAction.Share -> onSetShowSharedContact(node)
|
||||
else -> {}
|
||||
}
|
||||
},
|
||||
)
|
||||
NodeChip(node = node, onClick = { onNavigateToNodeDetails(it.num) })
|
||||
}
|
||||
|
||||
Column(modifier = Modifier.weight(1f, fill = true)) {
|
||||
|
|
@ -124,26 +109,22 @@ fun CurrentlyConnectedInfo(
|
|||
@Composable
|
||||
private fun CurrentlyConnectedInfoPreview() {
|
||||
AppTheme {
|
||||
Surface {
|
||||
CurrentlyConnectedInfo(
|
||||
node =
|
||||
Node(
|
||||
num = 13444,
|
||||
user =
|
||||
MeshProtos.User.newBuilder().setShortName("\uD83E\uDEE0").setLongName("John Doe").build(),
|
||||
isIgnored = false,
|
||||
paxcounter = PaxcountProtos.Paxcount.newBuilder().setBle(10).setWifi(5).build(),
|
||||
environmentMetrics =
|
||||
TelemetryProtos.EnvironmentMetrics.newBuilder()
|
||||
.setTemperature(25f)
|
||||
.setRelativeHumidity(60f)
|
||||
.build(),
|
||||
),
|
||||
bluetoothRssi = -75, // Example RSSI for signal preview
|
||||
onNavigateToNodeDetails = {},
|
||||
onSetShowSharedContact = {},
|
||||
onClickDisconnect = {},
|
||||
)
|
||||
}
|
||||
CurrentlyConnectedInfo(
|
||||
node =
|
||||
Node(
|
||||
num = 13444,
|
||||
user = MeshProtos.User.newBuilder().setShortName("\uD83E\uDEE0").setLongName("John Doe").build(),
|
||||
isIgnored = false,
|
||||
paxcounter = PaxcountProtos.Paxcount.newBuilder().setBle(10).setWifi(5).build(),
|
||||
environmentMetrics =
|
||||
TelemetryProtos.EnvironmentMetrics.newBuilder()
|
||||
.setTemperature(25f)
|
||||
.setRelativeHumidity(60f)
|
||||
.build(),
|
||||
),
|
||||
bluetoothRssi = -75, // Example RSSI for signal preview
|
||||
onNavigateToNodeDetails = {},
|
||||
onClickDisconnect = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||
import com.geeksville.mesh.AppOnlyProtos
|
||||
import com.geeksville.mesh.model.Contact
|
||||
import com.geeksville.mesh.ui.common.components.MainAppBar
|
||||
import com.geeksville.mesh.ui.node.components.NodeMenuAction
|
||||
import org.meshtastic.core.strings.R
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
|
@ -144,17 +143,11 @@ fun ContactsScreen(
|
|||
MainAppBar(
|
||||
title = stringResource(R.string.conversations),
|
||||
ourNode = ourNode,
|
||||
isConnected = connectionState.isConnected(),
|
||||
showNodeChip = ourNode != null && connectionState.isConnected(),
|
||||
canNavigateUp = false,
|
||||
onNavigateUp = {},
|
||||
actions = {},
|
||||
onAction = { action ->
|
||||
when (action) {
|
||||
is NodeMenuAction.MoreDetails -> onClickNodeChip(action.node.num)
|
||||
else -> {}
|
||||
}
|
||||
},
|
||||
onClickChip = { onClickNodeChip(it.num) },
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.ui.common.components.MainAppBar
|
||||
import com.geeksville.mesh.ui.node.components.NodeMenuAction
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
|
|
@ -47,17 +46,11 @@ fun MapScreen(
|
|||
MainAppBar(
|
||||
title = stringResource(R.string.map),
|
||||
ourNode = ourNodeInfo,
|
||||
isConnected = isConnected,
|
||||
showNodeChip = ourNodeInfo != null && isConnected,
|
||||
canNavigateUp = false,
|
||||
onNavigateUp = {},
|
||||
actions = {},
|
||||
onAction = { action ->
|
||||
when (action) {
|
||||
is NodeMenuAction.MoreDetails -> onClickNodeChip(action.node.num)
|
||||
else -> {}
|
||||
}
|
||||
},
|
||||
onClickChip = { onClickNodeChip(it.num) },
|
||||
)
|
||||
},
|
||||
) { paddingValues ->
|
||||
|
|
|
|||
|
|
@ -97,7 +97,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||
import com.geeksville.mesh.AppOnlyProtos
|
||||
import com.geeksville.mesh.ui.common.components.SecurityIcon
|
||||
import com.geeksville.mesh.ui.node.components.NodeKeyStatusIcon
|
||||
import com.geeksville.mesh.ui.node.components.NodeMenuAction
|
||||
import com.geeksville.mesh.ui.sharing.SharedContactDialog
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -207,24 +206,7 @@ internal fun MessageScreen(
|
|||
is MessageScreenEvent.ClearUnreadCount ->
|
||||
viewModel.clearUnreadCount(contactKey, event.lastReadMessageId)
|
||||
|
||||
is MessageScreenEvent.HandleNodeMenuAction -> {
|
||||
when (val action = event.action) {
|
||||
is NodeMenuAction.DirectMessage -> {
|
||||
val hasPKC = ourNode?.hasPKC == true && action.node.hasPKC
|
||||
val targetChannel =
|
||||
if (hasPKC) {
|
||||
DataPacket.PKC_CHANNEL_INDEX
|
||||
} else {
|
||||
action.node.channel
|
||||
}
|
||||
navigateToMessages("$targetChannel${action.node.user.id}")
|
||||
}
|
||||
|
||||
is NodeMenuAction.MoreDetails -> navigateToNodeDetails(action.node.num)
|
||||
is NodeMenuAction.Share -> sharedContact = action.node
|
||||
else -> viewModel.handleNodeMenuAction(action)
|
||||
}
|
||||
}
|
||||
is MessageScreenEvent.NodeDetails -> navigateToNodeDetails(event.node.num)
|
||||
|
||||
is MessageScreenEvent.SetTitle -> viewModel.setTitle(event.title)
|
||||
is MessageScreenEvent.NavigateToMessages -> navigateToMessages(event.contactKey)
|
||||
|
|
@ -297,7 +279,6 @@ internal fun MessageScreen(
|
|||
MessageList(
|
||||
nodes = nodes,
|
||||
ourNode = ourNode,
|
||||
isConnected = connectionState.isConnected(),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
listState = listState,
|
||||
messages = messages,
|
||||
|
|
@ -308,7 +289,7 @@ internal fun MessageScreen(
|
|||
onSendMessage = { text, contactKey -> viewModel.sendMessage(text, contactKey) },
|
||||
contactKey = contactKey,
|
||||
onReply = { message -> replyingToPacketId = message?.packetId },
|
||||
onNodeMenuAction = { action -> onEvent(MessageScreenEvent.HandleNodeMenuAction(action)) },
|
||||
onClickChip = { onEvent(MessageScreenEvent.NodeDetails(it)) },
|
||||
)
|
||||
// Show FAB if we can scroll towards the newest messages (index 0).
|
||||
if (listState.canScrollBackward) {
|
||||
|
|
|
|||
|
|
@ -48,7 +48,6 @@ import androidx.compose.ui.text.style.TextAlign
|
|||
import androidx.compose.ui.unit.dp
|
||||
import com.geeksville.mesh.ui.message.components.MessageItem
|
||||
import com.geeksville.mesh.ui.message.components.ReactionDialog
|
||||
import com.geeksville.mesh.ui.node.components.NodeMenuAction
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
|
|
@ -107,14 +106,13 @@ fun DeliveryInfo(
|
|||
internal fun MessageList(
|
||||
nodes: List<Node>,
|
||||
ourNode: Node?,
|
||||
isConnected: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
listState: LazyListState = rememberLazyListState(),
|
||||
messages: List<Message>,
|
||||
selectedIds: MutableState<Set<Long>>,
|
||||
onUnreadChanged: (Long) -> Unit,
|
||||
onSendReaction: (String, Int) -> Unit,
|
||||
onNodeMenuAction: (NodeMenuAction) -> Unit,
|
||||
onClickChip: (Node) -> Unit,
|
||||
onDeleteMessages: (List<Long>) -> Unit,
|
||||
onSendMessage: (messageText: String, contactKey: String) -> Unit,
|
||||
contactKey: String,
|
||||
|
|
@ -173,13 +171,12 @@ internal fun MessageList(
|
|||
selectedIds.toggle(msg.uuid)
|
||||
haptics.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
},
|
||||
onAction = onNodeMenuAction,
|
||||
onClickChip = onClickChip,
|
||||
onStatusClick = { showStatusDialog = msg },
|
||||
onReply = { onReply(msg) },
|
||||
emojis = msg.emojis,
|
||||
sendReaction = { onSendReaction(it, msg.packetId) },
|
||||
onShowReactions = { showReactionDialog = msg.emojis },
|
||||
isConnected = isConnected,
|
||||
onNavigateToOriginalMessage = {
|
||||
coroutineScope.launch {
|
||||
val targetIndex = messages.indexOfFirst { it.packetId == msg.replyId }
|
||||
|
|
|
|||
|
|
@ -17,16 +17,15 @@
|
|||
|
||||
package com.geeksville.mesh.ui.message
|
||||
|
||||
import com.geeksville.mesh.ui.node.components.NodeMenuAction
|
||||
import org.meshtastic.core.database.model.Node
|
||||
|
||||
/**
|
||||
* Defines the various user interactions that can occur on the [MessageScreen].
|
||||
* These events are typically handled by the [com.geeksville.mesh.model.UIViewModel].
|
||||
* Defines the various user interactions that can occur on the [MessageScreen]. These events are typically handled by
|
||||
* the [com.geeksville.mesh.model.UIViewModel].
|
||||
*/
|
||||
internal sealed interface MessageScreenEvent {
|
||||
/** Send a new text message. */
|
||||
data class SendMessage(val text: String, val replyingToPacketId: Int? = null) :
|
||||
MessageScreenEvent
|
||||
data class SendMessage(val text: String, val replyingToPacketId: Int? = null) : MessageScreenEvent
|
||||
|
||||
/** Send an emoji reaction to a specific message. */
|
||||
data class SendReaction(val emoji: String, val messageId: Int) : MessageScreenEvent
|
||||
|
|
@ -38,7 +37,7 @@ internal sealed interface MessageScreenEvent {
|
|||
data class ClearUnreadCount(val lastReadMessageId: Long) : MessageScreenEvent
|
||||
|
||||
/** Handle an action from a node's context menu. */
|
||||
data class HandleNodeMenuAction(val action: NodeMenuAction) : MessageScreenEvent
|
||||
data class NodeDetails(val node: Node) : MessageScreenEvent
|
||||
|
||||
/** Set the title of the screen (typically the contact or channel name). */
|
||||
data class SetTitle(val title: String) : MessageScreenEvent
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import com.geeksville.mesh.channelSet
|
|||
import com.geeksville.mesh.service.MeshServiceNotifications
|
||||
import com.geeksville.mesh.service.ServiceAction
|
||||
import com.geeksville.mesh.service.ServiceRepository
|
||||
import com.geeksville.mesh.ui.node.components.NodeMenuAction
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
|
@ -43,7 +42,6 @@ import org.meshtastic.core.data.repository.RadioConfigRepository
|
|||
import org.meshtastic.core.database.model.Message
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.Position
|
||||
import org.meshtastic.core.prefs.ui.UiPrefs
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
|
@ -102,10 +100,6 @@ constructor(
|
|||
initialValue = emptyList(),
|
||||
)
|
||||
|
||||
// TODO this should be moved to a repository class
|
||||
private val _lastTraceRouteTime = MutableStateFlow<Long?>(null)
|
||||
val lastTraceRouteTime: StateFlow<Long?> = _lastTraceRouteTime.asStateFlow()
|
||||
|
||||
fun setTitle(title: String) {
|
||||
viewModelScope.launch { _title.value = title }
|
||||
}
|
||||
|
|
@ -157,22 +151,6 @@ constructor(
|
|||
if (unreadCount == 0) meshServiceNotifications.cancelMessageNotification(contact)
|
||||
}
|
||||
|
||||
fun handleNodeMenuAction(action: NodeMenuAction) {
|
||||
when (action) {
|
||||
is NodeMenuAction.Remove -> removeNode(action.node.num)
|
||||
is NodeMenuAction.Ignore -> ignoreNode(action.node)
|
||||
is NodeMenuAction.Favorite -> favoriteNode(action.node)
|
||||
is NodeMenuAction.RequestUserInfo -> requestUserInfo(action.node.num)
|
||||
is NodeMenuAction.RequestPosition -> requestPosition(action.node.num)
|
||||
is NodeMenuAction.TraceRoute -> {
|
||||
requestTraceroute(action.node.num)
|
||||
_lastTraceRouteTime.value = System.currentTimeMillis()
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun favoriteNode(node: Node) = viewModelScope.launch {
|
||||
try {
|
||||
serviceRepository.onServiceAction(ServiceAction.Favorite(node))
|
||||
|
|
@ -188,51 +166,4 @@ constructor(
|
|||
Timber.e("Send DataPacket error: ${ex.message}")
|
||||
}
|
||||
}
|
||||
|
||||
private 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}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun ignoreNode(node: Node) = viewModelScope.launch {
|
||||
try {
|
||||
serviceRepository.onServiceAction(ServiceAction.Ignore(node))
|
||||
} catch (ex: RemoteException) {
|
||||
Timber.e(ex, "Ignore node error:")
|
||||
}
|
||||
}
|
||||
|
||||
private fun requestUserInfo(destNum: Int) {
|
||||
Timber.i("Requesting UserInfo for '$destNum'")
|
||||
try {
|
||||
serviceRepository.meshService?.requestUserInfo(destNum)
|
||||
} catch (ex: RemoteException) {
|
||||
Timber.e("Request NodeInfo error: ${ex.message}")
|
||||
}
|
||||
}
|
||||
|
||||
fun requestPosition(destNum: Int, position: Position = Position(0.0, 0.0, 0)) {
|
||||
Timber.i("Requesting position for '$destNum'")
|
||||
try {
|
||||
serviceRepository.meshService?.requestPosition(destNum, position)
|
||||
} 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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,6 @@ import androidx.compose.ui.tooling.preview.PreviewLightDark
|
|||
import androidx.compose.ui.unit.dp
|
||||
import com.geeksville.mesh.ui.common.preview.NodePreviewParameterProvider
|
||||
import com.geeksville.mesh.ui.node.components.NodeChip
|
||||
import com.geeksville.mesh.ui.node.components.NodeMenuAction
|
||||
import org.meshtastic.core.database.entity.Reaction
|
||||
import org.meshtastic.core.database.model.Message
|
||||
import org.meshtastic.core.database.model.Node
|
||||
|
|
@ -77,9 +76,8 @@ internal fun MessageItem(
|
|||
emojis: List<Reaction> = emptyList(),
|
||||
onClick: () -> Unit = {},
|
||||
onLongClick: () -> Unit = {},
|
||||
onAction: (NodeMenuAction) -> Unit = {},
|
||||
onClickChip: (Node) -> Unit = {},
|
||||
onStatusClick: () -> Unit = {},
|
||||
isConnected: Boolean,
|
||||
onNavigateToOriginalMessage: (Int) -> Unit = {},
|
||||
) = Column(
|
||||
modifier =
|
||||
|
|
@ -134,12 +132,8 @@ internal fun MessageItem(
|
|||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
NodeChip(
|
||||
node = if (message.fromLocal) ourNode else node,
|
||||
onAction = onAction,
|
||||
isConnected = isConnected,
|
||||
isThisNode = message.fromLocal,
|
||||
)
|
||||
val chipNode = if (message.fromLocal) ourNode else node
|
||||
NodeChip(node = chipNode, onClick = onClickChip)
|
||||
Text(
|
||||
text = with(if (message.fromLocal) ourNode.user else node.user) { "$longName ($id)" },
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
|
|
@ -325,7 +319,6 @@ private fun MessageItemPreview() {
|
|||
onClick = {},
|
||||
onLongClick = {},
|
||||
onStatusClick = {},
|
||||
isConnected = true,
|
||||
ourNode = sent.node,
|
||||
)
|
||||
|
||||
|
|
@ -336,7 +329,6 @@ private fun MessageItemPreview() {
|
|||
onClick = {},
|
||||
onLongClick = {},
|
||||
onStatusClick = {},
|
||||
isConnected = true,
|
||||
ourNode = sent.node,
|
||||
)
|
||||
|
||||
|
|
@ -347,7 +339,6 @@ private fun MessageItemPreview() {
|
|||
onClick = {},
|
||||
onLongClick = {},
|
||||
onStatusClick = {},
|
||||
isConnected = true,
|
||||
ourNode = sent.node,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -60,7 +60,6 @@ import com.geeksville.mesh.ClientOnlyProtos.DeviceProfile
|
|||
import com.geeksville.mesh.android.gpsDisabled
|
||||
import com.geeksville.mesh.navigation.getNavRouteFrom
|
||||
import com.geeksville.mesh.ui.common.components.MainAppBar
|
||||
import com.geeksville.mesh.ui.node.components.NodeMenuAction
|
||||
import com.geeksville.mesh.ui.settings.components.SettingsItem
|
||||
import com.geeksville.mesh.ui.settings.components.SettingsItemDetail
|
||||
import com.geeksville.mesh.ui.settings.components.SettingsItemSwitch
|
||||
|
|
@ -194,17 +193,11 @@ fun SettingsScreen(
|
|||
stringResource(R.string.remotely_administrating, remoteName)
|
||||
},
|
||||
ourNode = ourNode,
|
||||
isConnected = isConnected,
|
||||
showNodeChip = ourNode != null && isConnected && state.isLocal,
|
||||
canNavigateUp = false,
|
||||
onNavigateUp = {},
|
||||
actions = {},
|
||||
onAction = { action ->
|
||||
when (action) {
|
||||
is NodeMenuAction.MoreDetails -> onClickNodeChip(action.node.num)
|
||||
else -> {}
|
||||
}
|
||||
},
|
||||
onClickChip = { onClickNodeChip(it.num) },
|
||||
)
|
||||
},
|
||||
) { paddingValues ->
|
||||
|
|
|
|||
|
|
@ -173,12 +173,7 @@ private fun NodesDeletionPreview(nodesToDelete: List<NodeEntity>) {
|
|||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
nodesToDelete.forEach { node ->
|
||||
NodeChip(
|
||||
node = node.toModel(),
|
||||
modifier = Modifier.padding(end = 8.dp, bottom = 8.dp),
|
||||
isThisNode = false,
|
||||
isConnected = false,
|
||||
) {}
|
||||
NodeChip(node = node.toModel(), modifier = Modifier.padding(end = 8.dp, bottom = 8.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,10 +59,9 @@ fun <T : MessageLite> RadioConfigScreenList(
|
|||
canNavigateUp = true,
|
||||
onNavigateUp = onBack,
|
||||
ourNode = null,
|
||||
isConnected = false,
|
||||
showNodeChip = false,
|
||||
actions = {},
|
||||
onAction = {},
|
||||
onClickChip = {},
|
||||
)
|
||||
},
|
||||
) { innerPadding ->
|
||||
|
|
|
|||
|
|
@ -297,6 +297,7 @@
|
|||
<string name="delivery_confirmed">Delivery confirmed</string>
|
||||
<string name="error">Error</string>
|
||||
<string name="ignore">Ignore</string>
|
||||
<string name="remove_ignored">Remove from ignored</string>
|
||||
<string name="ignore_add">Add \'%s\' to ignore list?</string>
|
||||
<string name="ignore_remove">Remove \'%s\' from ignore list?</string>
|
||||
<string name="map_select_download_region">Select download region</string>
|
||||
|
|
@ -397,6 +398,8 @@
|
|||
<string name="alert_bell_text">Alert Bell Character!</string>
|
||||
<string name="critical_alert">Critical Alert!</string>
|
||||
<string name="favorite">Favorite</string>
|
||||
<string name="add_favorite">Add to favorites</string>
|
||||
<string name="remove_favorite">Remove from favorites</string>
|
||||
<string name="favorite_add">Add \'%s\' as a favorite node?</string>
|
||||
<string name="favorite_remove">Remove \'%s\' as a favorite node?</string>
|
||||
<string name="power_metrics_log">Power Metrics Log</string>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue