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