mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
feat: add dialog for message status information
This commit is contained in:
parent
056f6b28cf
commit
a075dfbd3a
16 changed files with 803 additions and 103 deletions
|
|
@ -0,0 +1,656 @@
|
|||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 11,
|
||||
"identityHash": "d5d55af54cacbb3f4f42f8e96e91acda",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "MyNodeInfo",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`myNodeNum` INTEGER NOT NULL, `hasGPS` 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, `channelUtilization` REAL NOT NULL, `airUtilTx` REAL NOT NULL, PRIMARY KEY(`myNodeNum`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "myNodeNum",
|
||||
"columnName": "myNodeNum",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasGPS",
|
||||
"columnName": "hasGPS",
|
||||
"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
|
||||
},
|
||||
{
|
||||
"fieldPath": "channelUtilization",
|
||||
"columnName": "channelUtilization",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "airUtilTx",
|
||||
"columnName": "airUtilTx",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"myNodeNum"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "NodeInfo",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`num` INTEGER NOT NULL, `snr` REAL NOT NULL, `rssi` INTEGER NOT NULL, `lastHeard` INTEGER NOT NULL, `channel` INTEGER NOT NULL, `hopsAway` INTEGER NOT NULL DEFAULT 0, `user_id` TEXT, `user_longName` TEXT, `user_shortName` TEXT, `user_hwModel` TEXT, `user_isLicensed` INTEGER, `user_role` INTEGER DEFAULT 0, `position_latitude` REAL, `position_longitude` REAL, `position_altitude` INTEGER, `position_time` INTEGER, `position_satellitesInView` INTEGER, `position_groundSpeed` INTEGER, `position_groundTrack` INTEGER, `position_precisionBits` INTEGER, `devMetrics_time` INTEGER, `devMetrics_batteryLevel` INTEGER, `devMetrics_voltage` REAL, `devMetrics_channelUtilization` REAL, `devMetrics_airUtilTx` REAL, `devMetrics_uptimeSeconds` INTEGER, `envMetrics_time` INTEGER, `envMetrics_temperature` REAL, `envMetrics_relativeHumidity` REAL, `envMetrics_barometricPressure` REAL, `envMetrics_gasResistance` REAL, `envMetrics_voltage` REAL, `envMetrics_current` REAL, `envMetrics_iaq` INTEGER, PRIMARY KEY(`num`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "num",
|
||||
"columnName": "num",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "snr",
|
||||
"columnName": "snr",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "rssi",
|
||||
"columnName": "rssi",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastHeard",
|
||||
"columnName": "lastHeard",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "channel",
|
||||
"columnName": "channel",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hopsAway",
|
||||
"columnName": "hopsAway",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "user.id",
|
||||
"columnName": "user_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "user.longName",
|
||||
"columnName": "user_longName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "user.shortName",
|
||||
"columnName": "user_shortName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "user.hwModel",
|
||||
"columnName": "user_hwModel",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "user.isLicensed",
|
||||
"columnName": "user_isLicensed",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "user.role",
|
||||
"columnName": "user_role",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "position.latitude",
|
||||
"columnName": "position_latitude",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "position.longitude",
|
||||
"columnName": "position_longitude",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "position.altitude",
|
||||
"columnName": "position_altitude",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "position.time",
|
||||
"columnName": "position_time",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "position.satellitesInView",
|
||||
"columnName": "position_satellitesInView",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "position.groundSpeed",
|
||||
"columnName": "position_groundSpeed",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "position.groundTrack",
|
||||
"columnName": "position_groundTrack",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "position.precisionBits",
|
||||
"columnName": "position_precisionBits",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "deviceMetrics.time",
|
||||
"columnName": "devMetrics_time",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "deviceMetrics.batteryLevel",
|
||||
"columnName": "devMetrics_batteryLevel",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "deviceMetrics.voltage",
|
||||
"columnName": "devMetrics_voltage",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "deviceMetrics.channelUtilization",
|
||||
"columnName": "devMetrics_channelUtilization",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "deviceMetrics.airUtilTx",
|
||||
"columnName": "devMetrics_airUtilTx",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "deviceMetrics.uptimeSeconds",
|
||||
"columnName": "devMetrics_uptimeSeconds",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "environmentMetrics.time",
|
||||
"columnName": "envMetrics_time",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "environmentMetrics.temperature",
|
||||
"columnName": "envMetrics_temperature",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "environmentMetrics.relativeHumidity",
|
||||
"columnName": "envMetrics_relativeHumidity",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "environmentMetrics.barometricPressure",
|
||||
"columnName": "envMetrics_barometricPressure",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "environmentMetrics.gasResistance",
|
||||
"columnName": "envMetrics_gasResistance",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "environmentMetrics.voltage",
|
||||
"columnName": "envMetrics_voltage",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "environmentMetrics.current",
|
||||
"columnName": "envMetrics_current",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "environmentMetrics.iaq",
|
||||
"columnName": "envMetrics_iaq",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"num"
|
||||
]
|
||||
},
|
||||
"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, `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": "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)",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"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, 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
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"uuid"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"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": []
|
||||
}
|
||||
],
|
||||
"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, 'd5d55af54cacbb3f4f42f8e96e91acda')"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -36,8 +36,9 @@ import com.geeksville.mesh.database.entity.QuickChatAction
|
|||
AutoMigration (from = 7, to = 8),
|
||||
AutoMigration (from = 8, to = 9),
|
||||
AutoMigration (from = 9, to = 10),
|
||||
AutoMigration (from = 10, to = 11),
|
||||
],
|
||||
version = 10,
|
||||
version = 11,
|
||||
exportSchema = true,
|
||||
)
|
||||
@TypeConverters(Converters::class)
|
||||
|
|
|
|||
|
|
@ -50,8 +50,8 @@ class PacketRepository @Inject constructor(private val packetDaoLazy: dagger.Laz
|
|||
packetDao.updateMessageId(d, id)
|
||||
}
|
||||
|
||||
suspend fun getDataPacketById(requestId: Int) = withContext(Dispatchers.IO) {
|
||||
packetDao.getDataPacketById(requestId)
|
||||
suspend fun getPacketById(requestId: Int) = withContext(Dispatchers.IO) {
|
||||
packetDao.getPacketById(requestId)
|
||||
}
|
||||
|
||||
suspend fun deleteMessages(uuidList: List<Long>) = withContext(Dispatchers.IO) {
|
||||
|
|
|
|||
|
|
@ -130,10 +130,15 @@ interface PacketDao {
|
|||
)
|
||||
fun getDataPackets(): List<DataPacket>
|
||||
|
||||
@Transaction
|
||||
fun getDataPacketById(requestId: Int): DataPacket? {
|
||||
return getDataPackets().lastOrNull { it.id == requestId }
|
||||
}
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM packet
|
||||
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM MyNodeInfo))
|
||||
AND packet_id = :requestId
|
||||
ORDER BY received_time DESC
|
||||
"""
|
||||
)
|
||||
fun getPacketById(requestId: Int): Packet?
|
||||
|
||||
@Transaction
|
||||
fun getQueuedPackets(): List<DataPacket>? =
|
||||
|
|
|
|||
|
|
@ -22,7 +22,9 @@ data class Packet(
|
|||
@ColumnInfo(name = "contact_key") val contact_key: String,
|
||||
@ColumnInfo(name = "received_time") val received_time: Long,
|
||||
@ColumnInfo(name = "read", defaultValue = "1") val read: Boolean,
|
||||
@ColumnInfo(name = "data") val data: DataPacket
|
||||
@ColumnInfo(name = "data") val data: DataPacket,
|
||||
@ColumnInfo(name = "packet_id", defaultValue = "0") val packetId: Int = 0,
|
||||
@ColumnInfo(name = "routing_error", defaultValue = "-1") var routingError: Int = -1,
|
||||
)
|
||||
|
||||
@Entity(tableName = "contact_settings")
|
||||
|
|
|
|||
52
app/src/main/java/com/geeksville/mesh/model/Message.kt
Normal file
52
app/src/main/java/com/geeksville/mesh/model/Message.kt
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
package com.geeksville.mesh.model
|
||||
|
||||
import com.geeksville.mesh.MeshProtos.Routing
|
||||
import com.geeksville.mesh.MeshUser
|
||||
import com.geeksville.mesh.MessageStatus
|
||||
import com.geeksville.mesh.R
|
||||
|
||||
val Routing.Error.stringRes: Int
|
||||
get() = when (this) {
|
||||
Routing.Error.NONE -> R.string.routing_error_none
|
||||
Routing.Error.NO_ROUTE -> R.string.routing_error_no_route
|
||||
Routing.Error.GOT_NAK -> R.string.routing_error_got_nak
|
||||
Routing.Error.TIMEOUT -> R.string.routing_error_timeout
|
||||
Routing.Error.NO_INTERFACE -> R.string.routing_error_no_interface
|
||||
Routing.Error.MAX_RETRANSMIT -> R.string.routing_error_max_retransmit
|
||||
Routing.Error.NO_CHANNEL -> R.string.routing_error_no_channel
|
||||
Routing.Error.TOO_LARGE -> R.string.routing_error_too_large
|
||||
Routing.Error.NO_RESPONSE -> R.string.routing_error_no_response
|
||||
Routing.Error.DUTY_CYCLE_LIMIT -> R.string.routing_error_duty_cycle_limit
|
||||
Routing.Error.BAD_REQUEST -> R.string.routing_error_bad_request
|
||||
Routing.Error.NOT_AUTHORIZED -> R.string.routing_error_not_authorized
|
||||
Routing.Error.PKI_FAILED -> R.string.routing_error_pki_failed
|
||||
Routing.Error.PKI_UNKNOWN_PUBKEY -> R.string.routing_error_pki_unknown_pubkey
|
||||
else -> R.string.unrecognized
|
||||
}
|
||||
|
||||
data class Message(
|
||||
val uuid: Long,
|
||||
val receivedTime: Long,
|
||||
val user: MeshUser,
|
||||
val text: String,
|
||||
val time: Long,
|
||||
val read: Boolean,
|
||||
val status: MessageStatus?,
|
||||
val routingError: Int,
|
||||
) {
|
||||
private fun getStatusStringRes(value: Int): Int {
|
||||
val error = Routing.Error.forNumber(value) ?: Routing.Error.UNRECOGNIZED
|
||||
return error.stringRes
|
||||
}
|
||||
|
||||
fun getStatusStringRes(): Pair<Int, Int> {
|
||||
val title = if (routingError > 0) R.string.error else R.string.message_delivery_status
|
||||
val text = when (status) {
|
||||
MessageStatus.RECEIVED -> R.string.delivery_confirmed
|
||||
MessageStatus.QUEUED -> R.string.message_status_queued
|
||||
MessageStatus.ENROUTE -> R.string.message_status_enroute
|
||||
else -> getStatusStringRes(routingError)
|
||||
}
|
||||
return title to text
|
||||
}
|
||||
}
|
||||
|
|
@ -463,7 +463,7 @@ class RadioConfigViewModel @Inject constructor(
|
|||
val parsed = MeshProtos.Routing.parseFrom(data.payload)
|
||||
debug(debugMsg.format(parsed.errorReason.name))
|
||||
if (parsed.errorReason != MeshProtos.Routing.Error.NONE) {
|
||||
setResponseStateError(parsed.errorReason.name)
|
||||
setResponseStateError(app.getString(parsed.errorReason.stringRes))
|
||||
} else if (packet.from == destNum && route.isEmpty()) {
|
||||
requestIds.update { it.apply { remove(data.requestId) } }
|
||||
if (requestIds.value.isEmpty()) setResponseStateSuccess()
|
||||
|
|
|
|||
|
|
@ -130,16 +130,6 @@ data class Contact(
|
|||
val isMuted: Boolean,
|
||||
)
|
||||
|
||||
data class Message(
|
||||
val uuid: Long,
|
||||
val receivedTime: Long,
|
||||
val user: MeshUser,
|
||||
val text: String,
|
||||
val time: Long,
|
||||
val read: Boolean,
|
||||
val status: MessageStatus?,
|
||||
)
|
||||
|
||||
// return time if within 24 hours, otherwise date
|
||||
internal fun getShortDateTime(time: Long): String? {
|
||||
val date = if (time != 0L) Date(time) else return null
|
||||
|
|
@ -181,7 +171,6 @@ class UIViewModel @Inject constructor(
|
|||
|
||||
private val _channels = MutableStateFlow(channelSet {})
|
||||
val channels: StateFlow<AppOnlyProtos.ChannelSet> get() = _channels
|
||||
val channelSet get() = channels.value
|
||||
|
||||
private val _quickChatActions = MutableStateFlow<List<QuickChatAction>>(emptyList())
|
||||
val quickChatActions: StateFlow<List<QuickChatAction>> = _quickChatActions
|
||||
|
|
@ -339,6 +328,7 @@ class UIViewModel @Inject constructor(
|
|||
time = it.data.time,
|
||||
read = it.read,
|
||||
status = it.data.status,
|
||||
routingError = it.routingError,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -601,13 +601,14 @@ class MeshService : Service(), Logging {
|
|||
val contactKey = "${dataPacket.channel}$contactId"
|
||||
|
||||
val packetToSave = Packet(
|
||||
0L, // autoGenerated
|
||||
myNodeNum,
|
||||
dataPacket.dataType,
|
||||
contactKey,
|
||||
System.currentTimeMillis(),
|
||||
fromLocal,
|
||||
dataPacket
|
||||
uuid = 0L, // autoGenerated
|
||||
myNodeNum = myNodeNum,
|
||||
packetId = dataPacket.id,
|
||||
port_num = dataPacket.dataType,
|
||||
contact_key = contactKey,
|
||||
received_time = System.currentTimeMillis(),
|
||||
read = fromLocal,
|
||||
data = dataPacket
|
||||
)
|
||||
serviceScope.handledLaunch {
|
||||
packetRepository.get().apply {
|
||||
|
|
@ -686,13 +687,12 @@ class MeshService : Service(), Logging {
|
|||
// We always send ACKs to other apps, because they might care about the messages they sent
|
||||
shouldBroadcast = true
|
||||
val u = MeshProtos.Routing.parseFrom(data.payload)
|
||||
val isAck = u.errorReasonValue == MeshProtos.Routing.Error.NONE_VALUE
|
||||
|
||||
if (u.errorReason == MeshProtos.Routing.Error.DUTY_CYCLE_LIMIT) {
|
||||
radioConfigRepository.setErrorMessage(getString(R.string.error_duty_cycle))
|
||||
}
|
||||
|
||||
handleAckNak(isAck, fromId, data.requestId)
|
||||
handleAckNak(data.requestId, fromId, u.errorReasonValue)
|
||||
queueResponse.remove(data.requestId)?.complete(true)
|
||||
}
|
||||
|
||||
|
|
@ -1011,7 +1011,7 @@ class MeshService : Service(), Logging {
|
|||
private suspend fun getDataPacketById(packetId: Int): DataPacket? = withTimeoutOrNull(1000) {
|
||||
var dataPacket: DataPacket? = null
|
||||
while (dataPacket == null) {
|
||||
dataPacket = packetRepository.get().getDataPacketById(packetId)
|
||||
dataPacket = packetRepository.get().getPacketById(packetId)?.data
|
||||
if (dataPacket == null) delay(100)
|
||||
}
|
||||
dataPacket
|
||||
|
|
@ -1031,14 +1031,21 @@ class MeshService : Service(), Logging {
|
|||
/**
|
||||
* Handle an ack/nak packet by updating sent message status
|
||||
*/
|
||||
private fun handleAckNak(isAck: Boolean, fromId: String, requestId: Int) {
|
||||
private fun handleAckNak(requestId: Int, fromId: String, routingError: Int) {
|
||||
serviceScope.handledLaunch {
|
||||
val p = getDataPacketById(requestId)
|
||||
val isAck = routingError == MeshProtos.Routing.Error.NONE_VALUE
|
||||
val p = packetRepository.get().getPacketById(requestId)
|
||||
// distinguish real ACKs coming from the intended receiver
|
||||
val m = if (isAck && fromId == p?.to) MessageStatus.RECEIVED
|
||||
else if (isAck) MessageStatus.DELIVERED else MessageStatus.ERROR
|
||||
if (p != null && p.status != MessageStatus.RECEIVED)
|
||||
packetRepository.get().updateMessageStatus(p, m)
|
||||
val m = when {
|
||||
isAck && fromId == p?.data?.to -> MessageStatus.RECEIVED
|
||||
isAck -> MessageStatus.DELIVERED
|
||||
else -> MessageStatus.ERROR
|
||||
}
|
||||
if (p != null && p.data.status != MessageStatus.RECEIVED) {
|
||||
p.data.status = m
|
||||
p.routingError = routingError
|
||||
packetRepository.get().update(p)
|
||||
}
|
||||
serviceBroadcasts.broadcastMessageStatus(requestId, m)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import androidx.compose.material.TopAppBar
|
|||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.automirrored.twotone.KeyboardArrowRight
|
||||
import androidx.compose.material.icons.twotone.Warning
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
|
|
@ -42,7 +43,6 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
@ -686,15 +686,15 @@ private fun NavButton(@StringRes title: Int, enabled: Boolean, onClick: () -> Un
|
|||
horizontalArrangement = Arrangement.Center,
|
||||
) {
|
||||
Icon(
|
||||
painterResource(R.drawable.ic_twotone_warning_24),
|
||||
"warning",
|
||||
imageVector = Icons.TwoTone.Warning,
|
||||
contentDescription = "warning",
|
||||
modifier = Modifier.padding(end = 8.dp)
|
||||
)
|
||||
Text(
|
||||
text = "${stringResource(title)}?\n")
|
||||
Icon(
|
||||
painterResource(R.drawable.ic_twotone_warning_24),
|
||||
"warning",
|
||||
imageVector = Icons.TwoTone.Warning,
|
||||
contentDescription = "warning",
|
||||
modifier = Modifier.padding(start = 8.dp)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.geeksville.mesh.ui
|
|||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
|
@ -20,14 +21,19 @@ import androidx.compose.material.LocalTextStyle
|
|||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.twotone.Cloud
|
||||
import androidx.compose.material.icons.twotone.CloudDone
|
||||
import androidx.compose.material.icons.twotone.CloudOff
|
||||
import androidx.compose.material.icons.twotone.CloudUpload
|
||||
import androidx.compose.material.icons.twotone.HowToReg
|
||||
import androidx.compose.material.icons.twotone.Warning
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
|
|
@ -54,6 +60,7 @@ internal fun MessageItem(
|
|||
onClick: () -> Unit = {},
|
||||
onLongClick: () -> Unit = {},
|
||||
onChipClick: () -> Unit = {},
|
||||
onStatusClick: () -> Unit = {},
|
||||
) {
|
||||
val fromLocal = shortName == null
|
||||
val messageColor = if (fromLocal) R.color.colorMyMsg else R.color.colorMsg
|
||||
|
|
@ -134,18 +141,19 @@ internal fun MessageItem(
|
|||
fontSize = MaterialTheme.typography.caption.fontSize,
|
||||
)
|
||||
AnimatedVisibility(visible = fromLocal) {
|
||||
val icon = when (messageStatus) {
|
||||
MessageStatus.RECEIVED -> R.drawable.ic_twotone_how_to_reg_24
|
||||
MessageStatus.QUEUED -> R.drawable.ic_twotone_cloud_upload_24
|
||||
MessageStatus.DELIVERED -> R.drawable.cloud_on
|
||||
MessageStatus.ENROUTE -> R.drawable.ic_twotone_cloud_24
|
||||
MessageStatus.ERROR -> R.drawable.cloud_off
|
||||
else -> R.drawable.ic_twotone_warning_24
|
||||
}
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(id = icon),
|
||||
imageVector = when (messageStatus) {
|
||||
MessageStatus.RECEIVED -> Icons.TwoTone.HowToReg
|
||||
MessageStatus.QUEUED -> Icons.TwoTone.CloudUpload
|
||||
MessageStatus.DELIVERED -> Icons.TwoTone.CloudDone
|
||||
MessageStatus.ENROUTE -> Icons.TwoTone.Cloud
|
||||
MessageStatus.ERROR -> Icons.TwoTone.CloudOff
|
||||
else -> Icons.TwoTone.Warning
|
||||
},
|
||||
contentDescription = stringResource(R.string.message_delivery_status),
|
||||
modifier = Modifier.padding(start = 8.dp),
|
||||
modifier = Modifier
|
||||
.padding(start = 8.dp)
|
||||
.clickable { onStatusClick() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,11 +9,14 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.geeksville.mesh.DataPacket
|
||||
import com.geeksville.mesh.model.Message
|
||||
import com.geeksville.mesh.ui.components.SimpleAlertDialog
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
|
|
@ -34,6 +37,13 @@ internal fun MessageListView(
|
|||
AutoScrollToBottom(listState, messages)
|
||||
UpdateUnreadCount(listState, messages, onUnreadChanged)
|
||||
|
||||
var showStatusDialog by remember { mutableStateOf<Message?>(null) }
|
||||
if (showStatusDialog != null) {
|
||||
val msg = showStatusDialog ?: return
|
||||
val (title, text) = msg.getStatusStringRes()
|
||||
SimpleAlertDialog(title = title, text = text) { showStatusDialog = null }
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
state = listState,
|
||||
|
|
@ -52,6 +62,7 @@ internal fun MessageListView(
|
|||
onClick = { onClick(msg) },
|
||||
onLongClick = { onLongClick(msg) },
|
||||
onChipClick = { onChipClick(msg) },
|
||||
onStatusClick = { showStatusDialog = msg }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M19.21,12.04l-1.53,-0.11 -0.3,-1.5C16.88,7.86 14.62,6 12,6 9.94,6 8.08,7.14 7.12,8.96l-0.5,0.95 -1.07,0.11C3.53,10.24 2,11.95 2,14c0,2.21 1.79,4 4,4h13c1.65,0 3,-1.35 3,-3 0,-1.55 -1.22,-2.86 -2.79,-2.96z"
|
||||
android:strokeAlpha="0.3"
|
||||
android:fillAlpha="0.3"/>
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM19,18H6c-2.21,0 -4,-1.79 -4,-4 0,-2.05 1.53,-3.76 3.56,-3.97l1.07,-0.11 0.5,-0.95C8.08,7.14 9.94,6 12,6c2.62,0 4.88,1.86 5.39,4.43l0.3,1.5 1.53,0.11c1.56,0.1 2.78,1.41 2.78,2.96 0,1.65 -1.35,3 -3,3z"/>
|
||||
</vector>
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillAlpha="0.3"
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M11,8m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"
|
||||
android:strokeAlpha="0.3" />
|
||||
<path
|
||||
android:fillAlpha="0.3"
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M5,18h4.99L9,17l0.93,-0.94C7.55,16.33 5.2,17.37 5,18z"
|
||||
android:strokeAlpha="0.3" />
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M11,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM11,6c1.1,0 2,0.9 2,2s-0.9,2 -2,2 -2,-0.9 -2,-2 0.9,-2 2,-2zM10,18L5,18c0.2,-0.63 2.55,-1.67 4.93,-1.94h0.03l0.46,-0.45L12,14.06c-0.39,-0.04 -0.68,-0.06 -1,-0.06 -2.67,0 -8,1.34 -8,4v2h9l-2,-2zM20.6,12.5l-5.13,5.17 -2.07,-2.08L12,17l3.47,3.5L22,13.91z" />
|
||||
</vector>
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillAlpha="0.3"
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M4.47,19h15.06L12,5.99 4.47,19zM13,18h-2v-2h2v2zM13,14h-2v-4h2v4z"
|
||||
android:strokeAlpha="0.3" />
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M1,21h22L12,2 1,21zM4.47,19L12,5.99 19.53,19L4.47,19zM11,16h2v2h-2zM11,10h2v4h-2z" />
|
||||
</vector>
|
||||
|
|
@ -25,6 +25,24 @@
|
|||
<string name="node_sort_last_heard">Last heard</string>
|
||||
<string name="node_sort_via_mqtt">via MQTT</string>
|
||||
|
||||
<string name="unrecognized">Unrecognized</string>
|
||||
<string name="message_status_enroute">Waiting to be acknowledged</string>
|
||||
<string name="message_status_queued">Queued for sending</string>
|
||||
<string name="routing_error_none">Acknowledged</string>
|
||||
<string name="routing_error_no_route">No route</string>
|
||||
<string name="routing_error_got_nak">Received a negative acknowledgment</string>
|
||||
<string name="routing_error_timeout">Timeout</string>
|
||||
<string name="routing_error_no_interface">No Interface</string>
|
||||
<string name="routing_error_max_retransmit">Max Retransmission Reached</string>
|
||||
<string name="routing_error_no_channel">No Channel</string>
|
||||
<string name="routing_error_too_large">Packet too large</string>
|
||||
<string name="routing_error_no_response">No response</string>
|
||||
<string name="routing_error_bad_request">Bad Request</string>
|
||||
<string name="routing_error_duty_cycle_limit">Regional Duty Cycle Limit Reached</string>
|
||||
<string name="routing_error_not_authorized">Not Authorized</string>
|
||||
<string name="routing_error_pki_failed">Encrypted Send Failed</string>
|
||||
<string name="routing_error_pki_unknown_pubkey">Unknown Public Key</string>
|
||||
|
||||
<string name="elevation_suffix" translatable="false">MSL</string>
|
||||
<string name="channel_air_util" translatable="false">ChUtil %.1f%% AirUtilTX %.1f%%</string>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue