diff --git a/app/src/main/java/com/geeksville/mesh/model/UIState.kt b/app/src/main/java/com/geeksville/mesh/model/UIState.kt index a9598e24..b5767d36 100644 --- a/app/src/main/java/com/geeksville/mesh/model/UIState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt @@ -485,6 +485,14 @@ class UIViewModel @Inject constructor( updateLoraConfig { it.copy { region = value } } } + fun favoriteNode(node: Node) = viewModelScope.launch { + try { + radioConfigRepository.onServiceAction(ServiceAction.Favorite(node)) + } catch (ex: RemoteException) { + errormsg("Favorite node error:", ex) + } + } + fun ignoreNode(node: Node) = viewModelScope.launch { try { radioConfigRepository.onServiceAction(ServiceAction.Ignore(node)) diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt index 6aaac59f..14f7a96a 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -106,6 +106,7 @@ import kotlin.math.absoluteValue sealed class ServiceAction { data class GetDeviceMetadata(val destNum: Int) : ServiceAction() + data class Favorite(val node: Node) : ServiceAction() data class Ignore(val node: Node) : ServiceAction() data class Reaction(val emoji: String, val replyId: Int, val contactKey: String) : ServiceAction() } @@ -1791,6 +1792,7 @@ class MeshService : Service(), Logging { private fun onServiceAction(action: ServiceAction) { when (action) { is ServiceAction.GetDeviceMetadata -> getDeviceMetadata(action.destNum) + is ServiceAction.Favorite -> favoriteNode(action.node) is ServiceAction.Ignore -> ignoreNode(action.node) is ServiceAction.Reaction -> sendReaction(action) } @@ -1802,6 +1804,21 @@ class MeshService : Service(), Logging { }) } + private fun favoriteNode(node: Node) = toRemoteExceptions { + sendToRadio(newMeshPacketTo(myNodeNum).buildAdminPacket { + if (node.isFavorite) { + debug("removing node ${node.num} from favorite list") + removeFavoriteNode = node.num + } else { + debug("adding node ${node.num} to favorite list") + setFavoriteNode = node.num + } + }) + updateNodeInfo(node.num) { + it.isFavorite = !node.isFavorite + } + } + private fun ignoreNode(node: Node) = toRemoteExceptions { sendToRadio(newMeshPacketTo(myNodeNum).buildAdminPacket { if (node.isIgnored) { diff --git a/app/src/main/java/com/geeksville/mesh/ui/NodeItem.kt b/app/src/main/java/com/geeksville/mesh/ui/NodeItem.kt index 74807325..f5e5c9a5 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/NodeItem.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/NodeItem.kt @@ -84,6 +84,7 @@ fun NodeItem( currentTimeMillis: Long, isConnected: Boolean = false, ) { + val isFavorite = thatNode.isFavorite val isIgnored = thatNode.isIgnored val longName = thatNode.user.longName.ifEmpty { stringResource(id = R.string.unknown_username) } @@ -150,7 +151,7 @@ fun NodeItem( Text( modifier = Modifier.fillMaxWidth(), text = thatNode.user.shortName.ifEmpty { "???" }, - fontWeight = FontWeight.Normal, + fontWeight = if (isFavorite) FontWeight.Bold else FontWeight.Normal, fontSize = MaterialTheme.typography.button.fontSize, textDecoration = TextDecoration.LineThrough.takeIf { isIgnored }, textAlign = TextAlign.Center, @@ -173,6 +174,7 @@ fun NodeItem( Text( modifier = Modifier.weight(1f), text = longName, + fontWeight = if (isFavorite) FontWeight.Bold else FontWeight.Normal, style = style, textDecoration = TextDecoration.LineThrough.takeIf { isIgnored }, softWrap = true, diff --git a/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt index c9adfe5f..a8d34e88 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt @@ -136,6 +136,7 @@ fun NodesScreen( when (menuItem) { is NodeMenuAction.Remove -> model.removeNode(node.num) is NodeMenuAction.Ignore -> model.ignoreNode(node) + is NodeMenuAction.Favorite -> model.favoriteNode(node) is NodeMenuAction.DirectMessage -> navigateToMessages(node) is NodeMenuAction.RequestUserInfo -> model.requestUserInfo(node.num) is NodeMenuAction.RequestPosition -> model.requestPosition(node.num) diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/NodeMenu.kt b/app/src/main/java/com/geeksville/mesh/ui/components/NodeMenu.kt index 50d0f7bf..ae029ac2 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/components/NodeMenu.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/components/NodeMenu.kt @@ -47,8 +47,25 @@ fun NodeMenu( expanded: Boolean = false, onAction: (NodeMenuAction) -> Unit ) { + var displayFavoriteDialog by remember { mutableStateOf(false) } var displayIgnoreDialog by remember { mutableStateOf(false) } var displayRemoveDialog by remember { mutableStateOf(false) } + if (displayFavoriteDialog) { + SimpleAlertDialog( + title = R.string.favorite, + text = stringResource( + id = if (node.isFavorite) R.string.favorite_remove else R.string.favorite_add, + node.user.longName + ), + onConfirm = { + displayFavoriteDialog = false + onAction(NodeMenuAction.Favorite(node)) + }, + onDismiss = { + displayFavoriteDialog = false + } + ) + } if (displayIgnoreDialog) { SimpleAlertDialog( title = R.string.ignore, @@ -113,6 +130,25 @@ fun NodeMenu( }, content = { Text(stringResource(R.string.traceroute)) } ) + DropdownMenuItem( + onClick = { + onDismissRequest() + displayFavoriteDialog = true + }, + enabled = !node.isIgnored, + ) { + Text(stringResource(R.string.favorite)) + Spacer(Modifier.weight(1f)) + Checkbox( + checked = node.isFavorite, + onCheckedChange = { + onDismissRequest() + displayFavoriteDialog = true + }, + modifier = Modifier.size(24.dp), + enabled = !node.isIgnored, + ) + } DropdownMenuItem( onClick = { onDismissRequest() @@ -152,6 +188,7 @@ fun NodeMenu( sealed class NodeMenuAction { data class Remove(val node: Node) : NodeMenuAction() data class Ignore(val node: Node) : NodeMenuAction() + data class Favorite(val node: Node) : NodeMenuAction() data class DirectMessage(val node: Node) : NodeMenuAction() data class RequestUserInfo(val node: Node) : NodeMenuAction() data class RequestPosition(val node: Node) : NodeMenuAction() diff --git a/app/src/main/java/com/geeksville/mesh/ui/message/Message.kt b/app/src/main/java/com/geeksville/mesh/ui/message/Message.kt index 9070b0b6..95a89806 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/message/Message.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/message/Message.kt @@ -283,6 +283,7 @@ internal fun MessageScreen( when (action) { is NodeMenuAction.Remove -> viewModel.removeNode(action.node.num) is NodeMenuAction.Ignore -> viewModel.ignoreNode(action.node) + is NodeMenuAction.Favorite -> viewModel.favoriteNode(action.node) is NodeMenuAction.DirectMessage -> navigateToMessages(action.node) is NodeMenuAction.RequestUserInfo -> viewModel.requestUserInfo(action.node.num) is NodeMenuAction.RequestPosition -> viewModel.requestPosition(action.node.num) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bb6b7fd2..0f910772 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -313,6 +313,9 @@ Unknown Age Copy Alert Bell Character! + Favorite + Add \'%s\' as a favorite node? + Remove \'%s\' as a favorite node? Power Metrics Log Channel 1 Channel 2