refactor: move ignore node to `is_ignored` field in `NodeInfo`

pull/1449/head
andrekir 2024-12-07 08:09:41 -03:00 zatwierdzone przez Andre K
rodzic 4a1319a645
commit 1ea55b2209
8 zmienionych plików z 585 dodań i 59 usunięć

Wyświetl plik

@ -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')"
]
}
}

Wyświetl plik

@ -59,8 +59,9 @@ import com.geeksville.mesh.database.entity.ReactionEntity
AutoMigration(from = 11, to = 12), AutoMigration(from = 11, to = 12),
AutoMigration(from = 12, to = 13, spec = AutoMigration12to13::class), AutoMigration(from = 12, to = 13, spec = AutoMigration12to13::class),
AutoMigration(from = 13, to = 14), AutoMigration(from = 13, to = 14),
AutoMigration(from = 14, to = 15),
], ],
version = 14, version = 15,
exportSchema = true, exportSchema = true,
) )
@TypeConverters(Converters::class) @TypeConverters(Converters::class)

Wyświetl plik

@ -74,6 +74,9 @@ data class NodeEntity(
@ColumnInfo(name = "is_favorite") @ColumnInfo(name = "is_favorite")
var isFavorite: Boolean = false, var isFavorite: Boolean = false,
@ColumnInfo(name = "is_ignored", defaultValue = "0")
var isIgnored: Boolean = false,
@ColumnInfo(name = "environment_metrics", typeAffinity = ColumnInfo.BLOB) @ColumnInfo(name = "environment_metrics", typeAffinity = ColumnInfo.BLOB)
var environmentTelemetry: TelemetryProtos.Telemetry = TelemetryProtos.Telemetry.getDefaultInstance(), var environmentTelemetry: TelemetryProtos.Telemetry = TelemetryProtos.Telemetry.getDefaultInstance(),

Wyświetl plik

@ -142,7 +142,6 @@ data class NodesUiState(
val gpsFormat: Int = 0, val gpsFormat: Int = 0,
val distanceUnits: Int = 0, val distanceUnits: Int = 0,
val tempInFahrenheit: Boolean = false, val tempInFahrenheit: Boolean = false,
val ignoreIncomingList: List<Int> = emptyList(),
val showDetails: Boolean = false, val showDetails: Boolean = false,
) { ) {
companion object { companion object {
@ -227,7 +226,6 @@ class UIViewModel @Inject constructor(
gpsFormat = profile.config.display.gpsFormat.number, gpsFormat = profile.config.display.gpsFormat.number,
distanceUnits = profile.config.display.units.number, distanceUnits = profile.config.display.units.number,
tempInFahrenheit = profile.moduleConfig.telemetry.environmentDisplayFahrenheit, tempInFahrenheit = profile.moduleConfig.telemetry.environmentDisplayFahrenheit,
ignoreIncomingList = profile.config.lora.ignoreIncomingList,
showDetails = showDetails, showDetails = showDetails,
) )
}.stateIn( }.stateIn(
@ -486,19 +484,11 @@ class UIViewModel @Inject constructor(
updateLoraConfig { it.copy { region = value } } updateLoraConfig { it.copy { region = value } }
} }
fun ignoreNode(nodeNum: Int) = updateLoraConfig { fun ignoreNode(node: NodeEntity) = viewModelScope.launch {
it.copy { try {
val list = ignoreIncoming.toMutableList().apply { radioConfigRepository.onServiceAction(ServiceAction.Ignore(node))
if (contains(nodeNum)) { } catch (ex: RemoteException) {
debug("removing node $nodeNum from ignore list") errormsg("Ignore node error:", ex)
remove(nodeNum)
} else {
debug("adding node $nodeNum to ignore list")
add(nodeNum)
}
}
ignoreIncoming.clear()
ignoreIncoming.addAll(list)
} }
} }

Wyświetl plik

@ -77,6 +77,7 @@ import javax.inject.Inject
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
sealed class ServiceAction { sealed class ServiceAction {
data class Ignore(val node: NodeEntity) : ServiceAction()
data class Reaction(val emoji: String, val replyId: Int, val contactKey: String) : ServiceAction() data class Reaction(val emoji: String, val replyId: Int, val contactKey: String) : ServiceAction()
} }
@ -303,6 +304,7 @@ class MeshService : Service(), Logging {
.launchIn(serviceScope) .launchIn(serviceScope)
radioConfigRepository.serviceAction.onEach { action -> radioConfigRepository.serviceAction.onEach { action ->
when (action) { when (action) {
is ServiceAction.Ignore -> ignoreNode(action.node)
is ServiceAction.Reaction -> sendReaction(action) is ServiceAction.Reaction -> sendReaction(action)
} }
}.launchIn(serviceScope) }.launchIn(serviceScope)
@ -1453,6 +1455,7 @@ class MeshService : Service(), Logging {
-1 -1
} }
it.isFavorite = info.isFavorite 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 { private fun sendReaction(reaction: ServiceAction.Reaction) = toRemoteExceptions {
// contactKey: unique contact key filter (channel)+(nodeId) // contactKey: unique contact key filter (channel)+(nodeId)
val channel = reaction.contactKey[0].digitToInt() val channel = reaction.contactKey[0].digitToInt()

Wyświetl plik

@ -60,7 +60,7 @@ import com.geeksville.mesh.ConfigProtos.Config.DisplayConfig
import com.geeksville.mesh.MeshProtos import com.geeksville.mesh.MeshProtos
import com.geeksville.mesh.R import com.geeksville.mesh.R
import com.geeksville.mesh.database.entity.NodeEntity 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.NodeKeyStatusIcon
import com.geeksville.mesh.ui.components.NodeMenu import com.geeksville.mesh.ui.components.NodeMenu
import com.geeksville.mesh.ui.components.SignalInfo import com.geeksville.mesh.ui.components.SignalInfo
@ -79,13 +79,12 @@ fun NodeItem(
gpsFormat: Int, gpsFormat: Int,
distanceUnits: Int, distanceUnits: Int,
tempInFahrenheit: Boolean, tempInFahrenheit: Boolean,
ignoreIncomingList: List<Int> = emptyList(), onAction: (NodeMenuAction) -> Unit = {},
menuItemActionClicked: (MenuItemAction) -> Unit = {},
expanded: Boolean = false, expanded: Boolean = false,
currentTimeMillis: Long, currentTimeMillis: Long,
isConnected: Boolean = false, 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 longName = thatNode.user.longName.ifEmpty { stringResource(id = R.string.unknown_username) }
val isThisNode = thisNode?.num == thatNode.num val isThisNode = thisNode?.num == thatNode.num
@ -159,12 +158,10 @@ fun NodeItem(
} }
NodeMenu( NodeMenu(
node = thatNode, node = thatNode,
ignoreIncomingList = ignoreIncomingList, showFullMenu = !isThisNode && isConnected,
isThisNode = isThisNode, onAction = onAction,
onMenuItemAction = menuItemActionClicked,
expanded = menuExpanded, expanded = menuExpanded,
onDismissRequest = { menuExpanded = false }, onDismissRequest = { menuExpanded = false },
isConnected = isConnected,
) )
} }
NodeKeyStatusIcon( NodeKeyStatusIcon(

Wyświetl plik

@ -41,7 +41,7 @@ import com.geeksville.mesh.DataPacket
import com.geeksville.mesh.android.Logging import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.database.entity.NodeEntity import com.geeksville.mesh.database.entity.NodeEntity
import com.geeksville.mesh.model.UIViewModel 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.NodeFilterTextField
import com.geeksville.mesh.ui.components.rememberTimeTickWithLifecycle import com.geeksville.mesh.ui.components.rememberTimeTickWithLifecycle
import com.geeksville.mesh.ui.message.navigateToMessages import com.geeksville.mesh.ui.message.navigateToMessages
@ -131,16 +131,15 @@ fun NodesScreen(
gpsFormat = state.gpsFormat, gpsFormat = state.gpsFormat,
distanceUnits = state.distanceUnits, distanceUnits = state.distanceUnits,
tempInFahrenheit = state.tempInFahrenheit, tempInFahrenheit = state.tempInFahrenheit,
ignoreIncomingList = state.ignoreIncomingList, onAction = { menuItem ->
menuItemActionClicked = { menuItem ->
when (menuItem) { when (menuItem) {
MenuItemAction.Remove -> model.removeNode(node.num) is NodeMenuAction.Remove -> model.removeNode(node.num)
MenuItemAction.Ignore -> model.ignoreNode(node.num) is NodeMenuAction.Ignore -> model.ignoreNode(node)
MenuItemAction.DirectMessage -> navigateToMessages(node) is NodeMenuAction.DirectMessage -> navigateToMessages(node)
MenuItemAction.RequestUserInfo -> model.requestUserInfo(node.num) is NodeMenuAction.RequestUserInfo -> model.requestUserInfo(node.num)
MenuItemAction.RequestPosition -> model.requestPosition(node.num) is NodeMenuAction.RequestPosition -> model.requestPosition(node.num)
MenuItemAction.TraceRoute -> model.requestTraceroute(node.num) is NodeMenuAction.TraceRoute -> model.requestTraceroute(node.num)
MenuItemAction.MoreDetails -> navigateToNodeDetails(node.num) is NodeMenuAction.MoreDetails -> navigateToNodeDetails(node.num)
} }
}, },
expanded = state.showDetails, expanded = state.showDetails,

Wyświetl plik

@ -42,26 +42,23 @@ import com.geeksville.mesh.database.entity.NodeEntity
@Composable @Composable
fun NodeMenu( fun NodeMenu(
node: NodeEntity, node: NodeEntity,
ignoreIncomingList: List<Int>, showFullMenu: Boolean = false,
isThisNode: Boolean = false,
onMenuItemAction: (MenuItemAction) -> Unit,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
expanded: Boolean = false, expanded: Boolean = false,
isConnected: Boolean = false, onAction: (NodeMenuAction) -> Unit
) { ) {
val isIgnored = ignoreIncomingList.contains(node.num)
var displayIgnoreDialog by remember { mutableStateOf(false) } var displayIgnoreDialog by remember { mutableStateOf(false) }
var displayRemoveDialog by remember { mutableStateOf(false) } var displayRemoveDialog by remember { mutableStateOf(false) }
if (displayIgnoreDialog) { if (displayIgnoreDialog) {
SimpleAlertDialog( SimpleAlertDialog(
title = R.string.ignore, title = R.string.ignore,
text = stringResource( 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 node.user.longName
), ),
onConfirm = { onConfirm = {
displayIgnoreDialog = false displayIgnoreDialog = false
onMenuItemAction(MenuItemAction.Ignore) onAction(NodeMenuAction.Ignore(node))
}, },
onDismiss = { onDismiss = {
displayIgnoreDialog = false displayIgnoreDialog = false
@ -74,7 +71,7 @@ fun NodeMenu(
text = R.string.remove_node_text, text = R.string.remove_node_text,
onConfirm = { onConfirm = {
displayRemoveDialog = false displayRemoveDialog = false
onMenuItemAction(MenuItemAction.Remove) onAction(NodeMenuAction.Remove(node))
}, },
onDismiss = { onDismiss = {
displayRemoveDialog = false displayRemoveDialog = false
@ -87,32 +84,32 @@ fun NodeMenu(
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
) { ) {
if (!isThisNode && isConnected) { if (showFullMenu) {
DropdownMenuItem( DropdownMenuItem(
onClick = { onClick = {
onDismissRequest() onDismissRequest()
onMenuItemAction(MenuItemAction.DirectMessage) onAction(NodeMenuAction.DirectMessage(node))
}, },
content = { Text(stringResource(R.string.direct_message)) } content = { Text(stringResource(R.string.direct_message)) }
) )
DropdownMenuItem( DropdownMenuItem(
onClick = { onClick = {
onDismissRequest() onDismissRequest()
onMenuItemAction(MenuItemAction.RequestUserInfo) onAction(NodeMenuAction.RequestUserInfo(node))
}, },
content = { Text(stringResource(R.string.request_userinfo)) } content = { Text(stringResource(R.string.request_userinfo)) }
) )
DropdownMenuItem( DropdownMenuItem(
onClick = { onClick = {
onDismissRequest() onDismissRequest()
onMenuItemAction(MenuItemAction.RequestPosition) onAction(NodeMenuAction.RequestPosition(node))
}, },
content = { Text(stringResource(R.string.request_position)) } content = { Text(stringResource(R.string.request_position)) }
) )
DropdownMenuItem( DropdownMenuItem(
onClick = { onClick = {
onDismissRequest() onDismissRequest()
onMenuItemAction(MenuItemAction.TraceRoute) onAction(NodeMenuAction.TraceRoute(node))
}, },
content = { Text(stringResource(R.string.traceroute)) } content = { Text(stringResource(R.string.traceroute)) }
) )
@ -121,17 +118,15 @@ fun NodeMenu(
onDismissRequest() onDismissRequest()
displayIgnoreDialog = true displayIgnoreDialog = true
}, },
enabled = ignoreIncomingList.size < 3 || isIgnored
) { ) {
Text(stringResource(R.string.ignore)) Text(stringResource(R.string.ignore))
Spacer(Modifier.weight(1f)) Spacer(Modifier.weight(1f))
Checkbox( Checkbox(
checked = isIgnored, checked = node.isIgnored,
onCheckedChange = { onCheckedChange = {
onDismissRequest() onDismissRequest()
displayIgnoreDialog = true displayIgnoreDialog = true
}, },
enabled = isIgnored || ignoreIncomingList.size < 3,
modifier = Modifier.size(24.dp), modifier = Modifier.size(24.dp),
) )
} }
@ -140,25 +135,26 @@ fun NodeMenu(
onDismissRequest() onDismissRequest()
displayRemoveDialog = true displayRemoveDialog = true
}, },
enabled = !node.isIgnored,
) { Text(stringResource(R.string.remove)) } ) { Text(stringResource(R.string.remove)) }
Divider(Modifier.padding(vertical = 8.dp)) Divider(Modifier.padding(vertical = 8.dp))
} }
DropdownMenuItem( DropdownMenuItem(
onClick = { onClick = {
onDismissRequest() onDismissRequest()
onMenuItemAction(MenuItemAction.MoreDetails) onAction(NodeMenuAction.MoreDetails(node))
}, },
content = { Text(stringResource(R.string.more_details)) } content = { Text(stringResource(R.string.more_details)) }
) )
} }
} }
enum class MenuItemAction { sealed class NodeMenuAction {
Remove, data class Remove(val node: NodeEntity) : NodeMenuAction()
Ignore, data class Ignore(val node: NodeEntity) : NodeMenuAction()
DirectMessage, data class DirectMessage(val node: NodeEntity) : NodeMenuAction()
RequestUserInfo, data class RequestUserInfo(val node: NodeEntity) : NodeMenuAction()
RequestPosition, data class RequestPosition(val node: NodeEntity) : NodeMenuAction()
TraceRoute, data class TraceRoute(val node: NodeEntity) : NodeMenuAction()
MoreDetails data class MoreDetails(val node: NodeEntity) : NodeMenuAction()
} }