diff --git a/app/schemas/com.geeksville.mesh.database.MeshtasticDatabase/15.json b/app/schemas/com.geeksville.mesh.database.MeshtasticDatabase/15.json new file mode 100644 index 000000000..e9557798e --- /dev/null +++ b/app/schemas/com.geeksville.mesh.database.MeshtasticDatabase/15.json @@ -0,0 +1,522 @@ +{ + "formatVersion": 1, + "database": { + "version": 15, + "identityHash": "2435abd7894404b70957f327189b0de7", + "entities": [ + { + "tableName": "my_node", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`myNodeNum` INTEGER NOT NULL, `model` TEXT, `firmwareVersion` TEXT, `couldUpdate` INTEGER NOT NULL, `shouldUpdate` INTEGER NOT NULL, `currentPacketId` INTEGER NOT NULL, `messageTimeoutMsec` INTEGER NOT NULL, `minAppVersion` INTEGER NOT NULL, `maxChannels` INTEGER NOT NULL, `hasWifi` INTEGER NOT NULL, PRIMARY KEY(`myNodeNum`))", + "fields": [ + { + "fieldPath": "myNodeNum", + "columnName": "myNodeNum", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "model", + "columnName": "model", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firmwareVersion", + "columnName": "firmwareVersion", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "couldUpdate", + "columnName": "couldUpdate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shouldUpdate", + "columnName": "shouldUpdate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "currentPacketId", + "columnName": "currentPacketId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageTimeoutMsec", + "columnName": "messageTimeoutMsec", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "minAppVersion", + "columnName": "minAppVersion", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "maxChannels", + "columnName": "maxChannels", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasWifi", + "columnName": "hasWifi", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "myNodeNum" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "nodes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`num` INTEGER NOT NULL, `user` BLOB NOT NULL, `long_name` TEXT, `short_name` TEXT, `position` BLOB NOT NULL, `latitude` REAL NOT NULL, `longitude` REAL NOT NULL, `snr` REAL NOT NULL, `rssi` INTEGER NOT NULL, `last_heard` INTEGER NOT NULL, `device_metrics` BLOB NOT NULL, `channel` INTEGER NOT NULL, `via_mqtt` INTEGER NOT NULL, `hops_away` INTEGER NOT NULL, `is_favorite` INTEGER NOT NULL, `is_ignored` INTEGER NOT NULL DEFAULT 0, `environment_metrics` BLOB NOT NULL, `power_metrics` BLOB NOT NULL, `paxcounter` BLOB NOT NULL, PRIMARY KEY(`num`))", + "fields": [ + { + "fieldPath": "num", + "columnName": "num", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "user", + "columnName": "user", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "longName", + "columnName": "long_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "shortName", + "columnName": "short_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "latitude", + "columnName": "latitude", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "longitude", + "columnName": "longitude", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "snr", + "columnName": "snr", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "rssi", + "columnName": "rssi", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastHeard", + "columnName": "last_heard", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "deviceTelemetry", + "columnName": "device_metrics", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "channel", + "columnName": "channel", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "viaMqtt", + "columnName": "via_mqtt", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hopsAway", + "columnName": "hops_away", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isFavorite", + "columnName": "is_favorite", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isIgnored", + "columnName": "is_ignored", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "environmentTelemetry", + "columnName": "environment_metrics", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "powerTelemetry", + "columnName": "power_metrics", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "paxcounter", + "columnName": "paxcounter", + "affinity": "BLOB", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "num" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "packet", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `myNodeNum` INTEGER NOT NULL DEFAULT 0, `port_num` INTEGER NOT NULL, `contact_key` TEXT NOT NULL, `received_time` INTEGER NOT NULL, `read` INTEGER NOT NULL DEFAULT 1, `data` TEXT NOT NULL, `packet_id` INTEGER NOT NULL DEFAULT 0, `routing_error` INTEGER NOT NULL DEFAULT -1, `reply_id` INTEGER NOT NULL DEFAULT 0)", + "fields": [ + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "myNodeNum", + "columnName": "myNodeNum", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "port_num", + "columnName": "port_num", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "contact_key", + "columnName": "contact_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "received_time", + "columnName": "received_time", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "read", + "columnName": "read", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "1" + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "packetId", + "columnName": "packet_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "routingError", + "columnName": "routing_error", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "-1" + }, + { + "fieldPath": "replyId", + "columnName": "reply_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "uuid" + ] + }, + "indices": [ + { + "name": "index_packet_myNodeNum", + "unique": false, + "columnNames": [ + "myNodeNum" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_packet_myNodeNum` ON `${TABLE_NAME}` (`myNodeNum`)" + }, + { + "name": "index_packet_port_num", + "unique": false, + "columnNames": [ + "port_num" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_packet_port_num` ON `${TABLE_NAME}` (`port_num`)" + }, + { + "name": "index_packet_contact_key", + "unique": false, + "columnNames": [ + "contact_key" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_packet_contact_key` ON `${TABLE_NAME}` (`contact_key`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "contact_settings", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`contact_key` TEXT NOT NULL, `muteUntil` INTEGER NOT NULL, PRIMARY KEY(`contact_key`))", + "fields": [ + { + "fieldPath": "contact_key", + "columnName": "contact_key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "muteUntil", + "columnName": "muteUntil", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "contact_key" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "log", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `type` TEXT NOT NULL, `received_date` INTEGER NOT NULL, `message` TEXT NOT NULL, `from_num` INTEGER NOT NULL DEFAULT 0, `port_num` INTEGER NOT NULL DEFAULT 0, `from_radio` BLOB NOT NULL DEFAULT x'', PRIMARY KEY(`uuid`))", + "fields": [ + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "message_type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "received_date", + "columnName": "received_date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "raw_message", + "columnName": "message", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "fromNum", + "columnName": "from_num", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "portNum", + "columnName": "port_num", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "fromRadio", + "columnName": "from_radio", + "affinity": "BLOB", + "notNull": true, + "defaultValue": "x''" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "uuid" + ] + }, + "indices": [ + { + "name": "index_log_from_num", + "unique": false, + "columnNames": [ + "from_num" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_log_from_num` ON `${TABLE_NAME}` (`from_num`)" + }, + { + "name": "index_log_port_num", + "unique": false, + "columnNames": [ + "port_num" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_log_port_num` ON `${TABLE_NAME}` (`port_num`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "quick_chat", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `message` TEXT NOT NULL, `mode` TEXT NOT NULL, `position` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "message", + "columnName": "message", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mode", + "columnName": "mode", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "uuid" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "reactions", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`reply_id` INTEGER NOT NULL, `user_id` TEXT NOT NULL, `emoji` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, PRIMARY KEY(`reply_id`, `user_id`, `emoji`))", + "fields": [ + { + "fieldPath": "replyId", + "columnName": "reply_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userId", + "columnName": "user_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "emoji", + "columnName": "emoji", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "reply_id", + "user_id", + "emoji" + ] + }, + "indices": [ + { + "name": "index_reactions_reply_id", + "unique": false, + "columnNames": [ + "reply_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_reactions_reply_id` ON `${TABLE_NAME}` (`reply_id`)" + } + ], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '2435abd7894404b70957f327189b0de7')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/database/MeshtasticDatabase.kt b/app/src/main/java/com/geeksville/mesh/database/MeshtasticDatabase.kt index 03da56143..01f84d489 100644 --- a/app/src/main/java/com/geeksville/mesh/database/MeshtasticDatabase.kt +++ b/app/src/main/java/com/geeksville/mesh/database/MeshtasticDatabase.kt @@ -59,8 +59,9 @@ import com.geeksville.mesh.database.entity.ReactionEntity AutoMigration(from = 11, to = 12), AutoMigration(from = 12, to = 13, spec = AutoMigration12to13::class), AutoMigration(from = 13, to = 14), + AutoMigration(from = 14, to = 15), ], - version = 14, + version = 15, exportSchema = true, ) @TypeConverters(Converters::class) diff --git a/app/src/main/java/com/geeksville/mesh/database/entity/NodeEntity.kt b/app/src/main/java/com/geeksville/mesh/database/entity/NodeEntity.kt index 8cc47c742..e0d27fceb 100644 --- a/app/src/main/java/com/geeksville/mesh/database/entity/NodeEntity.kt +++ b/app/src/main/java/com/geeksville/mesh/database/entity/NodeEntity.kt @@ -74,6 +74,9 @@ data class NodeEntity( @ColumnInfo(name = "is_favorite") var isFavorite: Boolean = false, + @ColumnInfo(name = "is_ignored", defaultValue = "0") + var isIgnored: Boolean = false, + @ColumnInfo(name = "environment_metrics", typeAffinity = ColumnInfo.BLOB) var environmentTelemetry: TelemetryProtos.Telemetry = TelemetryProtos.Telemetry.getDefaultInstance(), 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 2008e253f..e07304dc1 100644 --- a/app/src/main/java/com/geeksville/mesh/model/UIState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt @@ -142,7 +142,6 @@ data class NodesUiState( val gpsFormat: Int = 0, val distanceUnits: Int = 0, val tempInFahrenheit: Boolean = false, - val ignoreIncomingList: List = emptyList(), val showDetails: Boolean = false, ) { companion object { @@ -227,7 +226,6 @@ class UIViewModel @Inject constructor( gpsFormat = profile.config.display.gpsFormat.number, distanceUnits = profile.config.display.units.number, tempInFahrenheit = profile.moduleConfig.telemetry.environmentDisplayFahrenheit, - ignoreIncomingList = profile.config.lora.ignoreIncomingList, showDetails = showDetails, ) }.stateIn( @@ -486,19 +484,11 @@ class UIViewModel @Inject constructor( updateLoraConfig { it.copy { region = value } } } - fun ignoreNode(nodeNum: Int) = updateLoraConfig { - it.copy { - val list = ignoreIncoming.toMutableList().apply { - if (contains(nodeNum)) { - debug("removing node $nodeNum from ignore list") - remove(nodeNum) - } else { - debug("adding node $nodeNum to ignore list") - add(nodeNum) - } - } - ignoreIncoming.clear() - ignoreIncoming.addAll(list) + fun ignoreNode(node: NodeEntity) = viewModelScope.launch { + try { + radioConfigRepository.onServiceAction(ServiceAction.Ignore(node)) + } catch (ex: RemoteException) { + errormsg("Ignore node error:", ex) } } 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 2968785bb..f88f23d64 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -77,6 +77,7 @@ import javax.inject.Inject import kotlin.math.absoluteValue sealed class ServiceAction { + data class Ignore(val node: NodeEntity) : ServiceAction() data class Reaction(val emoji: String, val replyId: Int, val contactKey: String) : ServiceAction() } @@ -303,6 +304,7 @@ class MeshService : Service(), Logging { .launchIn(serviceScope) radioConfigRepository.serviceAction.onEach { action -> when (action) { + is ServiceAction.Ignore -> ignoreNode(action.node) is ServiceAction.Reaction -> sendReaction(action) } }.launchIn(serviceScope) @@ -1453,6 +1455,7 @@ class MeshService : Service(), Logging { -1 } it.isFavorite = info.isFavorite + it.isIgnored = info.isIgnored } } @@ -1757,6 +1760,21 @@ class MeshService : Service(), Logging { } } + private fun ignoreNode(node: NodeEntity) = toRemoteExceptions { + sendToRadio(newMeshPacketTo(myNodeNum).buildAdminPacket { + if (node.isIgnored) { + debug("removing node ${node.num} from ignore list") + removeIgnoredNode = node.num + } else { + debug("adding node ${node.num} to ignore list") + setIgnoredNode = node.num + } + }) + updateNodeInfo(node.num) { + it.isIgnored = !node.isIgnored + } + } + private fun sendReaction(reaction: ServiceAction.Reaction) = toRemoteExceptions { // contactKey: unique contact key filter (channel)+(nodeId) val channel = reaction.contactKey[0].digitToInt() 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 7d8fc1a14..6e9a0939b 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/NodeItem.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/NodeItem.kt @@ -60,7 +60,7 @@ import com.geeksville.mesh.ConfigProtos.Config.DisplayConfig import com.geeksville.mesh.MeshProtos import com.geeksville.mesh.R import com.geeksville.mesh.database.entity.NodeEntity -import com.geeksville.mesh.ui.components.MenuItemAction +import com.geeksville.mesh.ui.components.NodeMenuAction import com.geeksville.mesh.ui.components.NodeKeyStatusIcon import com.geeksville.mesh.ui.components.NodeMenu import com.geeksville.mesh.ui.components.SignalInfo @@ -79,13 +79,12 @@ fun NodeItem( gpsFormat: Int, distanceUnits: Int, tempInFahrenheit: Boolean, - ignoreIncomingList: List = emptyList(), - menuItemActionClicked: (MenuItemAction) -> Unit = {}, + onAction: (NodeMenuAction) -> Unit = {}, expanded: Boolean = false, currentTimeMillis: Long, isConnected: Boolean = false, ) { - val isIgnored = ignoreIncomingList.contains(thatNode.num) + val isIgnored = thatNode.isIgnored val longName = thatNode.user.longName.ifEmpty { stringResource(id = R.string.unknown_username) } val isThisNode = thisNode?.num == thatNode.num @@ -159,12 +158,10 @@ fun NodeItem( } NodeMenu( node = thatNode, - ignoreIncomingList = ignoreIncomingList, - isThisNode = isThisNode, - onMenuItemAction = menuItemActionClicked, + showFullMenu = !isThisNode && isConnected, + onAction = onAction, expanded = menuExpanded, onDismissRequest = { menuExpanded = false }, - isConnected = isConnected, ) } NodeKeyStatusIcon( 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 de2290a8c..a7885aa4a 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt @@ -41,7 +41,7 @@ import com.geeksville.mesh.DataPacket import com.geeksville.mesh.android.Logging import com.geeksville.mesh.database.entity.NodeEntity import com.geeksville.mesh.model.UIViewModel -import com.geeksville.mesh.ui.components.MenuItemAction +import com.geeksville.mesh.ui.components.NodeMenuAction import com.geeksville.mesh.ui.components.NodeFilterTextField import com.geeksville.mesh.ui.components.rememberTimeTickWithLifecycle import com.geeksville.mesh.ui.message.navigateToMessages @@ -131,16 +131,15 @@ fun NodesScreen( gpsFormat = state.gpsFormat, distanceUnits = state.distanceUnits, tempInFahrenheit = state.tempInFahrenheit, - ignoreIncomingList = state.ignoreIncomingList, - menuItemActionClicked = { menuItem -> + onAction = { menuItem -> when (menuItem) { - MenuItemAction.Remove -> model.removeNode(node.num) - MenuItemAction.Ignore -> model.ignoreNode(node.num) - MenuItemAction.DirectMessage -> navigateToMessages(node) - MenuItemAction.RequestUserInfo -> model.requestUserInfo(node.num) - MenuItemAction.RequestPosition -> model.requestPosition(node.num) - MenuItemAction.TraceRoute -> model.requestTraceroute(node.num) - MenuItemAction.MoreDetails -> navigateToNodeDetails(node.num) + is NodeMenuAction.Remove -> model.removeNode(node.num) + is NodeMenuAction.Ignore -> model.ignoreNode(node) + is NodeMenuAction.DirectMessage -> navigateToMessages(node) + is NodeMenuAction.RequestUserInfo -> model.requestUserInfo(node.num) + is NodeMenuAction.RequestPosition -> model.requestPosition(node.num) + is NodeMenuAction.TraceRoute -> model.requestTraceroute(node.num) + is NodeMenuAction.MoreDetails -> navigateToNodeDetails(node.num) } }, expanded = state.showDetails, 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 9abae0178..bb319e762 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 @@ -42,26 +42,23 @@ import com.geeksville.mesh.database.entity.NodeEntity @Composable fun NodeMenu( node: NodeEntity, - ignoreIncomingList: List, - isThisNode: Boolean = false, - onMenuItemAction: (MenuItemAction) -> Unit, + showFullMenu: Boolean = false, onDismissRequest: () -> Unit, expanded: Boolean = false, - isConnected: Boolean = false, + onAction: (NodeMenuAction) -> Unit ) { - val isIgnored = ignoreIncomingList.contains(node.num) var displayIgnoreDialog by remember { mutableStateOf(false) } var displayRemoveDialog by remember { mutableStateOf(false) } if (displayIgnoreDialog) { SimpleAlertDialog( title = R.string.ignore, text = stringResource( - id = if (isIgnored) R.string.ignore_remove else R.string.ignore_add, + id = if (node.isIgnored) R.string.ignore_remove else R.string.ignore_add, node.user.longName ), onConfirm = { displayIgnoreDialog = false - onMenuItemAction(MenuItemAction.Ignore) + onAction(NodeMenuAction.Ignore(node)) }, onDismiss = { displayIgnoreDialog = false @@ -74,7 +71,7 @@ fun NodeMenu( text = R.string.remove_node_text, onConfirm = { displayRemoveDialog = false - onMenuItemAction(MenuItemAction.Remove) + onAction(NodeMenuAction.Remove(node)) }, onDismiss = { displayRemoveDialog = false @@ -87,32 +84,32 @@ fun NodeMenu( onDismissRequest = onDismissRequest, ) { - if (!isThisNode && isConnected) { + if (showFullMenu) { DropdownMenuItem( onClick = { onDismissRequest() - onMenuItemAction(MenuItemAction.DirectMessage) + onAction(NodeMenuAction.DirectMessage(node)) }, content = { Text(stringResource(R.string.direct_message)) } ) DropdownMenuItem( onClick = { onDismissRequest() - onMenuItemAction(MenuItemAction.RequestUserInfo) + onAction(NodeMenuAction.RequestUserInfo(node)) }, content = { Text(stringResource(R.string.request_userinfo)) } ) DropdownMenuItem( onClick = { onDismissRequest() - onMenuItemAction(MenuItemAction.RequestPosition) + onAction(NodeMenuAction.RequestPosition(node)) }, content = { Text(stringResource(R.string.request_position)) } ) DropdownMenuItem( onClick = { onDismissRequest() - onMenuItemAction(MenuItemAction.TraceRoute) + onAction(NodeMenuAction.TraceRoute(node)) }, content = { Text(stringResource(R.string.traceroute)) } ) @@ -121,17 +118,15 @@ fun NodeMenu( onDismissRequest() displayIgnoreDialog = true }, - enabled = ignoreIncomingList.size < 3 || isIgnored ) { Text(stringResource(R.string.ignore)) Spacer(Modifier.weight(1f)) Checkbox( - checked = isIgnored, + checked = node.isIgnored, onCheckedChange = { onDismissRequest() displayIgnoreDialog = true }, - enabled = isIgnored || ignoreIncomingList.size < 3, modifier = Modifier.size(24.dp), ) } @@ -140,25 +135,26 @@ fun NodeMenu( onDismissRequest() displayRemoveDialog = true }, + enabled = !node.isIgnored, ) { Text(stringResource(R.string.remove)) } Divider(Modifier.padding(vertical = 8.dp)) } DropdownMenuItem( onClick = { onDismissRequest() - onMenuItemAction(MenuItemAction.MoreDetails) + onAction(NodeMenuAction.MoreDetails(node)) }, content = { Text(stringResource(R.string.more_details)) } ) } } -enum class MenuItemAction { - Remove, - Ignore, - DirectMessage, - RequestUserInfo, - RequestPosition, - TraceRoute, - MoreDetails +sealed class NodeMenuAction { + data class Remove(val node: NodeEntity) : NodeMenuAction() + data class Ignore(val node: NodeEntity) : NodeMenuAction() + data class DirectMessage(val node: NodeEntity) : NodeMenuAction() + data class RequestUserInfo(val node: NodeEntity) : NodeMenuAction() + data class RequestPosition(val node: NodeEntity) : NodeMenuAction() + data class TraceRoute(val node: NodeEntity) : NodeMenuAction() + data class MoreDetails(val node: NodeEntity) : NodeMenuAction() }