diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index f43cb4049..000000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 88e766d63..27092f63a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -184,7 +184,7 @@ dependencies { kspAndroidTest "com.google.dagger:hilt-compiler:$hilt_version" // Navigation - def nav_version = "2.8.2" + def nav_version = "2.8.4" implementation "androidx.navigation:navigation-compose:$nav_version" androidTestImplementation "androidx.navigation:navigation-testing:$nav_version" 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/assets/device_hardware.json b/app/src/main/assets/device_hardware.json new file mode 100644 index 000000000..853bec3b8 --- /dev/null +++ b/app/src/main/assets/device_hardware.json @@ -0,0 +1,764 @@ +[ + { + "hwModel": 1, + "hwModelSlug": "TLORA_V2", + "platformioTarget": "tlora-v2", + "architecture": "esp32", + "activelySupported": false, + "displayName": "LILYGO T-LoRa V2", + "tags": [ + "LilyGo" + ] + }, + { + "hwModel": 2, + "hwModelSlug": "TLORA_V1", + "platformioTarget": "tlora-v1", + "architecture": "esp32", + "activelySupported": false, + "displayName": "LILYGO T-LoRa V1", + "tags": [ + "LilyGo" + ] + }, + { + "hwModel": 3, + "hwModelSlug": "TLORA_V2_1_1P6", + "platformioTarget": "tlora-v2-1-1_6", + "architecture": "esp32", + "activelySupported": true, + "supportLevel": 1, + "displayName": "LILYGO T-LoRa V2.1-1.6", + "tags": [ + "LilyGo" + ], + "images": [ + "tlora-v2-1-1_6.svg" + ] + }, + { + "hwModel": 4, + "hwModelSlug": "TBEAM", + "platformioTarget": "tbeam", + "architecture": "esp32", + "activelySupported": true, + "supportLevel": 1, + "displayName": "LILYGO T-Beam", + "tags": [ + "LilyGo" + ], + "images": [ + "tbeam.svg" + ] + }, + { + "hwModel": 5, + "hwModelSlug": "HELTEC_V2_0", + "platformioTarget": "heltec-v2_0", + "architecture": "esp32", + "activelySupported": false, + "displayName": "Heltec V2.0", + "tags": [ + "Heltec" + ] + }, + { + "hwModel": 6, + "hwModelSlug": "TBEAM_V0P7", + "platformioTarget": "tbeam0_7", + "architecture": "esp32", + "activelySupported": false, + "displayName": "LILYGO T-Beam V0.7", + "tags": [ + "LilyGo" + ] + }, + { + "hwModel": 7, + "hwModelSlug": "T_ECHO", + "platformioTarget": "t-echo", + "architecture": "nrf52840", + "supportLevel": 1, + "activelySupported": true, + "displayName": "LILYGO T-Echo", + "tags": [ + "LilyGo" + ], + "images": [ + "t-echo.svg" + ], + "requiresDfu": true + }, + { + "hwModel": 8, + "hwModelSlug": "TLORA_V1_1P3", + "platformioTarget": "tlora-v1_3", + "architecture": "esp32", + "activelySupported": false, + "displayName": "LILYGO T-LoRa V1.1-1.3", + "tags": [ + "LilyGo" + ] + }, + { + "hwModel": 9, + "hwModelSlug": "RAK4631", + "platformioTarget": "rak4631", + "architecture": "nrf52840", + "activelySupported": true, + "supportLevel": 1, + "displayName": "RAK WisBlock 4631", + "tags": [ + "RAK" + ], + "images": [ + "rak4631.svg", + "rak4631_case.svg" + ], + "requiresDfu": true + }, + { + "hwModel": 10, + "hwModelSlug": "HELTEC_V2_1", + "platformioTarget": "heltec-v2_1", + "architecture": "esp32", + "activelySupported": false, + "displayName": "Heltec V2.1", + "tags": [ + "Heltec" + ] + }, + { + "hwModel": 11, + "hwModelSlug": "HELTEC_V1", + "platformioTarget": "heltec-v1", + "architecture": "esp32", + "activelySupported": false, + "displayName": "Heltec V1", + "tags": [ + "Heltec" + ] + }, + { + "hwModel": 12, + "hwModelSlug": "TBEAM_S3_CORE", + "platformioTarget": "tbeam-s3-core", + "architecture": "esp32-s3", + "activelySupported": true, + "supportLevel": 1, + "displayName": "LILYGO T-Beam Supreme", + "tags": [ + "LilyGo" + ], + "images": [ + "tbeam-s3-core.svg" + ], + "requiresDfu": true + }, + { + "hwModel": 13, + "hwModelSlug": "RAK11200", + "platformioTarget": "rak11200", + "architecture": "esp32", + "activelySupported": false, + "displayName": "RAK WisBlock 11200", + "tags": [ + "RAK" + ] + }, + { + "hwModel": 14, + "hwModelSlug": "NANO_G1", + "platformioTarget": "nano-g1", + "architecture": "esp32", + "activelySupported": true, + "supportLevel": 3, + "displayName": "Nano G1", + "tags": [ + "B&Q" + ] + }, + { + "hwModel": 15, + "hwModelSlug": "TLORA_V2_1_1P8", + "platformioTarget": "tlora-v2-1-1_8", + "architecture": "esp32", + "activelySupported": true, + "supportLevel": 2, + "displayName": "LILYGO T-LoRa V2.1-1.8", + "tags": [ + "LilyGo", + "2.4G LoRA" + ], + "images": [ + "tlora-v2-1-1_8.svg" + ] + }, + { + "hwModel": 16, + "hwModelSlug": "TLORA_T3_S3", + "platformioTarget": "tlora-t3s3-v1", + "architecture": "esp32-s3", + "activelySupported": true, + "displayName": "LILYGO T-LoRa T3-S3", + "supportLevel": 1, + "tags": [ + "LilyGo" + ], + "images": [ + "tlora-t3s3-v1.svg" + ], + "requiresDfu": true + }, + { + "hwModel": 16, + "hwModelSlug": "TLORA_T3_S3", + "platformioTarget": "tlora-t3s3-epaper", + "architecture": "esp32-s3", + "activelySupported": true, + "supportLevel": 1, + "displayName": "LILYGO T-LoRa T3-S3 E-Ink", + "tags": [ + "LilyGo" + ], + "images": [ + "tlora-t3s3-epaper.svg" + ], + "requiresDfu": true + }, + { + "hwModel": 17, + "hwModelSlug": "NANO_G1_EXPLORER", + "platformioTarget": "nano-g1-explorer", + "architecture": "esp32", + "activelySupported": true, + "supportLevel": 3, + "displayName": "Nano G1 Explorer", + "tags": [ + "B&Q" + ] + }, + { + "hwModel": 18, + "hwModelSlug": "NANO_G2_ULTRA", + "platformioTarget": "nano-g2-ultra", + "architecture": "nrf52840", + "activelySupported": true, + "supportLevel": 2, + "displayName": "Nano G2 Ultra", + "tags": [ + "B&Q" + ], + "requiresDfu": true, + "images": [ + "nano-g2-ultra.svg" + ] + }, + { + "hwModel": 21, + "hwModelSlug": "WIO_WM1110", + "platformioTarget": "wio-tracker-wm1110", + "architecture": "nrf52840", + "activelySupported": true, + "supportLevel": 1, + "displayName": "Seeed Wio WM1110 Tracker", + "tags": [ + "Seeed" + ], + "images": [ + "wio-tracker-wm1110.svg" + ], + "requiresDfu": true + }, + { + "hwModel": 25, + "hwModelSlug": "STATION_G1", + "platformioTarget": "station-g1", + "architecture": "esp32", + "activelySupported": true, + "supportLevel": 3, + "displayName": "Station G1", + "tags": [ + "B&Q" + ] + }, + { + "hwModel": 26, + "hwModelSlug": "RAK11310", + "platformioTarget": "rak11310", + "architecture": "rp2040", + "activelySupported": true, + "supportLevel": 2, + "displayName": "RAK WisBlock 11310", + "tags": [ + "RAK" + ], + "images": [ + "rak11310.svg" + ], + "requiresDfu": true + }, + { + "hwModel": 29, + "hwModelSlug": "CANARYONE", + "platformioTarget": "canaryone", + "architecture": "nrf52840", + "activelySupported": true, + "supportLevel": 3, + "displayName": "Canary One", + "tags": [ + "Canary" + ], + "requiresDfu": true + }, + { + "hwModel": 30, + "hwModelSlug": "RP2040_LORA", + "platformioTarget": "rp2040-lora", + "architecture": "rp2040", + "activelySupported": true, + "supportLevel": 2, + "displayName": "RP2040 LoRa", + "tags": [ + "Waveshare" + ], + "requiresDfu": true + }, + { + "hwModel": 31, + "hwModelSlug": "STATION_G2", + "platformioTarget": "station-g2", + "architecture": "esp32-s3", + "activelySupported": true, + "supportLevel": 2, + "displayName": "Station G2", + "tags": [ + "B&Q" + ], + "requiresDfu": true, + "images": [ + "station-g2.svg" + ] + }, + { + "hwModel": 39, + "hwModelSlug": "DIY_V1", + "platformioTarget": "meshtastic-diy-v1", + "architecture": "esp32", + "activelySupported": true, + "supportLevel": 3, + "displayName": "DIY V1", + "tags": [ + "DIY" + ], + "images": [ + "diy.svg" + ] + }, + { + "hwModel": 39, + "hwModelSlug": "HYDRA", + "platformioTarget": "hydra", + "architecture": "esp32", + "activelySupported": true, + "supportLevel": 3, + "displayName": "Hydra", + "tags": [ + "DIY" + ] + }, + { + "hwModel": 41, + "hwModelSlug": "DR_DEV", + "platformioTarget": "meshtastic-dr-dev", + "architecture": "esp32", + "activelySupported": false, + "displayName": "DR-DEV", + "tags": [ + "DIY" + ] + }, + { + "hwModel": 42, + "hwModelSlug": "M5STACK", + "platformioTarget": "m5stack-core", + "architecture": "esp32", + "activelySupported": true, + "supportLevel": 3, + "displayName": "M5 Stack", + "tags": [ + "M5Stack" + ] + }, + { + "hwModel": 43, + "hwModelSlug": "HELTEC_V3", + "platformioTarget": "heltec-v3", + "architecture": "esp32-s3", + "activelySupported": true, + "supportLevel": 1, + "displayName": "Heltec V3", + "tags": [ + "Heltec" + ], + "images": [ + "heltec-v3.svg", + "heltec-v3-case.svg" + ] + }, + { + "hwModel": 44, + "hwModelSlug": "HELTEC_WSL_V3", + "platformioTarget": "heltec-wsl-v3", + "architecture": "esp32-s3", + "activelySupported": true, + "supportLevel": 1, + "displayName": "Heltec Wireless Stick Lite V3", + "tags": [ + "Heltec" + ], + "images": [ + "heltec-wsl-v3.svg" + ] + }, + { + "hwModel": 47, + "hwModelSlug": "RPI_PICO", + "platformioTarget": "pico", + "architecture": "rp2040", + "activelySupported": true, + "supportLevel": 3, + "displayName": "Raspberry Pi Pico", + "tags": [ + "RPi", + "DIY" + ], + "requiresDfu": true, + "images": [ + "pico.svg" + ] + }, + { + "hwModel": 47, + "hwModelSlug": "RPI_PICO", + "platformioTarget": "picow", + "architecture": "rp2040", + "activelySupported": true, + "supportLevel": 3, + "displayName": "Raspberry Pi Pico W", + "tags": [ + "RPi", + "DIY" + ], + "requiresDfu": true, + "images": [ + "rpipicow.svg" + ] + }, + { + "hwModel": 48, + "hwModelSlug": "HELTEC_WIRELESS_TRACKER", + "platformioTarget": "heltec-wireless-tracker", + "architecture": "esp32-s3", + "activelySupported": true, + "supportLevel": 1, + "displayName": "Heltec Wireless Tracker V1.1", + "tags": [ + "Heltec" + ], + "images": [ + "heltec-wireless-tracker.svg" + ], + "requiresDfu": true + }, + { + "hwModel": 58, + "hwModelSlug": "HELTEC_WIRELESS_TRACKER_V1_0", + "platformioTarget": "heltec-wireless-tracker-V1-0", + "architecture": "esp32-s3", + "activelySupported": false, + "supportLevel": 3, + "displayName": "Heltec Wireless Tracker V1.0", + "images": [ + "heltec-wireless-tracker.svg" + ], + "requiresDfu": true + }, + { + "hwModel": 49, + "hwModelSlug": "HELTEC_WIRELESS_PAPER", + "platformioTarget": "heltec-wireless-paper", + "architecture": "esp32-s3", + "activelySupported": true, + "supportLevel": 1, + "displayName": "Heltec Wireless Paper", + "tags": [ + "Heltec" + ], + "images": [ + "heltec-wireless-paper.svg" + ] + }, + { + "hwModel": 50, + "hwModelSlug": "T_DECK", + "platformioTarget": "t-deck", + "architecture": "esp32-s3", + "activelySupported": true, + "supportLevel": 1, + "displayName": "LILYGO T-Deck", + "tags": [ + "LilyGo" + ], + "images": [ + "t-deck.svg" + ], + "requiresDfu": true + }, + { + "hwModel": 51, + "hwModelSlug": "T_WATCH_S3", + "platformioTarget": "t-watch-s3", + "architecture": "esp32-s3", + "activelySupported": true, + "supportLevel": 1, + "displayName": "LILYGO T-Watch S3", + "tags": [ + "LilyGo" + ], + "images": [ + "t-watch-s3.svg" + ] + }, + { + "hwModel": 52, + "hwModelSlug": "PICOMPUTER_S3", + "platformioTarget": "picomputer-s3", + "architecture": "esp32-s3", + "activelySupported": true, + "supportLevel": 3, + "displayName": "Pi Computer S3" + }, + { + "hwModel": 53, + "hwModelSlug": "HELTEC_HT62", + "platformioTarget": "heltec-ht62-esp32c3-sx1262", + "architecture": "esp32-c3", + "supportLevel": 1, + "activelySupported": true, + "displayName": "Heltec HT62", + "tags": [ + "Heltec" + ], + "images": [ + "heltec-ht62-esp32c3-sx1262.svg" + ] + }, + { + "hwModel": 57, + "hwModelSlug": "HELTEC_WIRELESS_PAPER_V1_0", + "platformioTarget": "heltec-wireless-paper-v1_0", + "architecture": "esp32-s3", + "activelySupported": false, + "supportLevel": 3, + "tags": [ + "Heltec" + ], + "displayName": "Heltec Wireless Paper V1.0", + "images": [ + "heltec-wireless-paper-v1_0.svg" + ] + }, + { + "hwModel": 59, + "hwModelSlug": "UNPHONE", + "platformioTarget": "unphone", + "architecture": "esp32-s3", + "activelySupported": true, + "supportLevel": 3, + "displayName": "unPhone", + "requiresDfu": true + }, + { + "hwModel": 48, + "hwModelSlug": "HELTEC_WIRELESS_TRACKER", + "platformioTarget": "tracksenger", + "architecture": "esp32-s3", + "activelySupported": true, + "supportLevel": 3, + "displayName": "TrackSenger (small TFT)", + "requiresDfu": true + }, + { + "hwModel": 48, + "hwModelSlug": "HELTEC_WIRELESS_TRACKER", + "platformioTarget": "tracksenger-lcd", + "architecture": "esp32-s3", + "activelySupported": true, + "supportLevel": 3, + "displayName": "TrackSenger (big TFT)", + "requiresDfu": true + }, + { + "hwModel": 48, + "hwModelSlug": "HELTEC_WIRELESS_TRACKER", + "platformioTarget": "tracksenger-oled", + "architecture": "esp32-s3", + "activelySupported": true, + "supportLevel": 3, + "displayName": "TrackSenger (big OLED)" + }, + { + "hwModel": 61, + "hwModelSlug": "CDEBYTE_EORA_S3", + "platformioTarget": "CDEBYTE_EoRa-S3", + "architecture": "esp32-s3", + "activelySupported": true, + "supportLevel": 3, + "displayName": "EBYTE EoRa-S3", + "tags": [ + "EByte" + ], + "requiresDfu": true + }, + { + "hwModel": 64, + "hwModelSlug": "RADIOMASTER_900_BANDIT_NANO", + "platformioTarget": "radiomaster_900_bandit_nano", + "architecture": "esp32", + "activelySupported": true, + "supportLevel": 2, + "displayName": "RadioMaster 900 Bandit Nano", + "tags": [ + "RadioMaster" + ] + }, + { + "hwModel": 66, + "hwModelSlug": "HELTEC_VISION_MASTER_T190", + "platformioTarget": "heltec-vision-master-t190", + "architecture": "esp32-s3", + "activelySupported": true, + "supportLevel": 1, + "displayName": "Heltec Vision Master T190", + "tags": [ + "Heltec" + ], + "images": [ + "heltec-vision-master-t190.svg" + ], + "requiresDfu": true + }, + { + "hwModel": 67, + "hwModelSlug": "HELTEC_VISION_MASTER_E213", + "platformioTarget": "heltec-vision-master-e213", + "architecture": "esp32-s3", + "activelySupported": true, + "supportLevel": 1, + "displayName": "Heltec Vision Master E213", + "tags": [ + "Heltec" + ], + "images": [ + "heltec-vision-master-e213.svg" + ], + "requiresDfu": true + }, + { + "hwModel": 68, + "hwModelSlug": "HELTEC_VISION_MASTER_E290", + "platformioTarget": "heltec-vision-master-e290", + "architecture": "esp32-s3", + "activelySupported": true, + "supportLevel": 1, + "displayName": "Heltec Vision Master E290", + "tags": [ + "Heltec" + ], + "images": [ + "heltec-vision-master-e290.svg" + ], + "requiresDfu": true + }, + { + "hwModel": 69, + "hwModelSlug": "HELTEC_MESH_NODE_T114", + "platformioTarget": "heltec-mesh-node-t114", + "architecture": "nrf52840", + "activelySupported": true, + "supportLevel": 1, + "displayName": "Heltec Mesh Node T114", + "tags": [ + "Heltec" + ], + "images": [ + "heltec-mesh-node-t114.svg", + "heltec-mesh-node-t114-case.svg" + ], + "requiresDfu": true + }, + { + "hwModel": 70, + "hwModelSlug": "SENSECAP_INDICATOR", + "platformioTarget": "seeed-sensecap-indicator", + "architecture": "esp32-s3", + "activelySupported": true, + "supportLevel": 1, + "displayName": "Seeed SenseCAP Indicator", + "tags": [ + "Seeed" + ], + "images": [ + "seeed-sensecap-indicator.svg" + ] + }, + { + "hwModel": 71, + "hwModelSlug": "TRACKER_T1000_E", + "platformioTarget": "tracker-t1000-e", + "architecture": "nrf52840", + "activelySupported": true, + "supportLevel": 1, + "displayName": "Seeed Card Tracker T1000-E", + "tags": [ + "Seeed" + ], + "images": [ + "tracker-t1000-e.svg" + ], + "requiresDfu": true + }, + { + "hwModel": 72, + "hwModelSlug": "Seeed_XIAO_S3", + "platformioTarget": "seeed-xiao-s3", + "architecture": "esp32-s3", + "activelySupported": true, + "supportLevel": 1, + "displayName": "Seeed Xiao ESP32-S3", + "tags": [ + "Seeed" + ], + "images": [ + "seeed-xiao-s3.svg" + ], + "requiresDfu": true + }, + { + "hwModel": 84, + "hwModelSlug": "WISMESH_TAP", + "platformioTarget": "rak_wismeshtap", + "architecture": "nrf52840", + "activelySupported": false, + "supportLevel": 1, + "displayName": "RAK WisMesh Tap", + "tags": [ + "RAK" + ], + "images": [ + "rak-wismeshtap.svg" + ], + "requiresDfu": true + } +] diff --git a/app/src/main/java/com/geeksville/mesh/DataPacket.kt b/app/src/main/java/com/geeksville/mesh/DataPacket.kt index 7d8658412..4753358be 100644 --- a/app/src/main/java/com/geeksville/mesh/DataPacket.kt +++ b/app/src/main/java/com/geeksville/mesh/DataPacket.kt @@ -190,7 +190,7 @@ data class DataPacket( const val PKC_CHANNEL_INDEX = 8 fun nodeNumToDefaultId(n: Int): String = "!%08x".format(n) - fun idToDefaultNodeNum(id: String?): Int? = id?.toLong(16)?.toInt() + fun idToDefaultNodeNum(id: String?): Int? = runCatching { id?.toLong(16)?.toInt() }.getOrNull() override fun createFromParcel(parcel: Parcel): DataPacket { return DataPacket(parcel) 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/NodeRepository.kt b/app/src/main/java/com/geeksville/mesh/database/NodeRepository.kt index 7da648fe2..007f881c7 100644 --- a/app/src/main/java/com/geeksville/mesh/database/NodeRepository.kt +++ b/app/src/main/java/com/geeksville/mesh/database/NodeRepository.kt @@ -67,6 +67,12 @@ class NodeRepository @Inject constructor( .conflate() .stateIn(processLifecycle.coroutineScope, SharingStarted.Eagerly, emptyMap()) + fun getNode(userId: String): NodeEntity = nodeDBbyNum.value.values.find { it.user.id == userId } + ?: NodeEntity( + num = DataPacket.idToDefaultNodeNum(userId) ?: 0, + user = getUser(userId), + ) + fun getUser(nodeNum: Int): MeshProtos.User = getUser(DataPacket.nodeNumToDefaultId(nodeNum)) fun getUser(userId: String): MeshProtos.User = 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/database/entity/Packet.kt b/app/src/main/java/com/geeksville/mesh/database/entity/Packet.kt index 3634826c0..c36b0c6cb 100644 --- a/app/src/main/java/com/geeksville/mesh/database/entity/Packet.kt +++ b/app/src/main/java/com/geeksville/mesh/database/entity/Packet.kt @@ -33,18 +33,18 @@ data class PacketEntity( @Relation(entity = ReactionEntity::class, parentColumn = "packet_id", entityColumn = "reply_id") val reactions: List = emptyList(), ) { - suspend fun toMessage(getUser: suspend (userId: String?) -> User) = with(packet) { + suspend fun toMessage(getNode: suspend (userId: String?) -> NodeEntity) = with(packet) { Message( uuid = uuid, receivedTime = received_time, - user = getUser(data.from), + node = getNode(data.from), text = data.text.orEmpty(), time = getShortDateTime(data.time), read = read, status = data.status, routingError = routingError, packetId = packetId, - emojis = reactions.toReaction(getUser), + emojis = reactions.toReaction(getNode), ) } } @@ -101,14 +101,14 @@ data class ReactionEntity( ) private suspend fun ReactionEntity.toReaction( - getUser: suspend (userId: String?) -> User + getNode: suspend (userId: String?) -> NodeEntity ) = Reaction( replyId = replyId, - user = getUser(userId), + user = getNode(userId).user, emoji = emoji, timestamp = timestamp, ) private suspend fun List.toReaction( - getUser: suspend (userId: String?) -> User -) = this.map { it.toReaction(getUser) } + getNode: suspend (userId: String?) -> NodeEntity +) = this.map { it.toReaction(getNode) } diff --git a/app/src/main/java/com/geeksville/mesh/model/DeviceHardware.kt b/app/src/main/java/com/geeksville/mesh/model/DeviceHardware.kt new file mode 100644 index 000000000..eb137599e --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/model/DeviceHardware.kt @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2024 Meshtastic LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.geeksville.mesh.model + +import com.geeksville.mesh.MeshProtos.HardwareModel +import com.geeksville.mesh.R +import kotlinx.serialization.Serializable + +data class DeviceHardware( + val hwModel: Int, + val hwModelSlug: String, + val architecture: String, + val activelySupported: Boolean, + val supportLevel: Int? = null, + val displayName: String, + val tags: List? = listOf(), + val image: Int, + val requiresDfu: Boolean? = null, +) + +@Serializable +data class DeviceHardwareDto( + val hwModel: Int, + val hwModelSlug: String, + val platformioTarget: String, + val architecture: String, + val activelySupported: Boolean, + val supportLevel: Int? = null, + val displayName: String, + val tags: List? = listOf(), + val images: List? = listOf(), + val requiresDfu: Boolean? = null, +) { + fun toDeviceHardware() = DeviceHardware( + hwModel = hwModel, + hwModelSlug = hwModelSlug, + architecture = architecture, + activelySupported = activelySupported, + supportLevel = supportLevel, + displayName = displayName, + tags = tags, + image = HardwareModel.forNumber(hwModel).getDeviceVectorImage(), + requiresDfu = requiresDfu + ) +} + +@Suppress("CyclomaticComplexMethod") +private fun HardwareModel.getDeviceVectorImage(): Int = when (this) { + HardwareModel.DIY_V1 -> R.drawable.hw_diy + HardwareModel.HELTEC_HT62 -> R.drawable.hw_heltec_ht62_esp32c3_sx1262 + HardwareModel.HELTEC_MESH_NODE_T114 -> R.drawable.hw_heltec_mesh_node_t114 + HardwareModel.HELTEC_V3 -> R.drawable.hw_heltec_v3 + HardwareModel.HELTEC_VISION_MASTER_E213 -> R.drawable.hw_heltec_vision_master_e213 + HardwareModel.HELTEC_VISION_MASTER_E290 -> R.drawable.hw_heltec_vision_master_e290 + HardwareModel.HELTEC_VISION_MASTER_T190 -> R.drawable.hw_heltec_vision_master_t190 + HardwareModel.HELTEC_WIRELESS_PAPER -> R.drawable.hw_heltec_wireless_paper + HardwareModel.HELTEC_WIRELESS_TRACKER -> R.drawable.hw_heltec_wireless_tracker + HardwareModel.HELTEC_WIRELESS_TRACKER_V1_0 -> R.drawable.hw_heltec_wireless_tracker_v1_0 + HardwareModel.HELTEC_WSL_V3 -> R.drawable.hw_heltec_wsl_v3 + HardwareModel.NANO_G2_ULTRA -> R.drawable.hw_nano_g2_ultra + HardwareModel.RPI_PICO -> R.drawable.hw_pico + HardwareModel.NRF52_PROMICRO_DIY -> R.drawable.hw_promicro + HardwareModel.RAK11310 -> R.drawable.hw_rak11310 + HardwareModel.RAK4631 -> R.drawable.hw_rak4631 + HardwareModel.RPI_PICO2 -> R.drawable.hw_rpipicow + HardwareModel.SENSECAP_INDICATOR -> R.drawable.hw_seeed_sensecap_indicator + HardwareModel.SEEED_XIAO_S3 -> R.drawable.hw_seeed_xiao_s3 + HardwareModel.STATION_G2 -> R.drawable.hw_station_g2 + HardwareModel.T_DECK -> R.drawable.hw_t_deck + HardwareModel.T_ECHO -> R.drawable.hw_t_echo + HardwareModel.T_WATCH_S3 -> R.drawable.hw_t_watch_s3 + HardwareModel.TBEAM -> R.drawable.hw_tbeam + HardwareModel.LILYGO_TBEAM_S3_CORE -> R.drawable.hw_tbeam_s3_core + HardwareModel.TLORA_C6 -> R.drawable.hw_tlora_c6 + HardwareModel.TLORA_T3_S3 -> R.drawable.hw_tlora_t3s3_v1 + HardwareModel.TLORA_V2_1_1P6 -> R.drawable.hw_tlora_v2_1_1_6 + HardwareModel.TLORA_V2_1_1P8 -> R.drawable.hw_tlora_v2_1_1_8 + HardwareModel.TRACKER_T1000_E -> R.drawable.hw_tracker_t1000_e + HardwareModel.WIO_WM1110 -> R.drawable.hw_wio_tracker_wm1110 + HardwareModel.WISMESH_TAP -> R.drawable.hw_rak_wismeshtap + else -> R.drawable.hw_unknown +} diff --git a/app/src/main/java/com/geeksville/mesh/model/Message.kt b/app/src/main/java/com/geeksville/mesh/model/Message.kt index 61ef926fc..6dc98b8be 100644 --- a/app/src/main/java/com/geeksville/mesh/model/Message.kt +++ b/app/src/main/java/com/geeksville/mesh/model/Message.kt @@ -18,9 +18,9 @@ package com.geeksville.mesh.model import com.geeksville.mesh.MeshProtos.Routing -import com.geeksville.mesh.MeshProtos.User import com.geeksville.mesh.MessageStatus import com.geeksville.mesh.R +import com.geeksville.mesh.database.entity.NodeEntity import com.geeksville.mesh.database.entity.Reaction val Routing.Error.stringRes: Int @@ -47,7 +47,7 @@ val Routing.Error.stringRes: Int data class Message( val uuid: Long, val receivedTime: Long, - val user: User, + val node: NodeEntity, val text: String, val time: String, val read: Boolean, diff --git a/app/src/main/java/com/geeksville/mesh/model/MetricsViewModel.kt b/app/src/main/java/com/geeksville/mesh/model/MetricsViewModel.kt index 1a4be6f9f..a27d8a83b 100644 --- a/app/src/main/java/com/geeksville/mesh/model/MetricsViewModel.kt +++ b/app/src/main/java/com/geeksville/mesh/model/MetricsViewModel.kt @@ -29,6 +29,7 @@ import androidx.lifecycle.viewModelScope import androidx.navigation.toRoute import com.geeksville.mesh.ConfigProtos.Config.DisplayConfig.DisplayUnits import com.geeksville.mesh.CoroutineDispatchers +import com.geeksville.mesh.MeshProtos.HardwareModel import com.geeksville.mesh.MeshProtos.MeshPacket import com.geeksville.mesh.MeshProtos.Position import com.geeksville.mesh.Portnums.PortNum @@ -56,9 +57,11 @@ import kotlinx.coroutines.flow.toList import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import kotlinx.serialization.json.Json import java.io.BufferedWriter import java.io.FileNotFoundException import java.io.FileWriter +import java.io.IOException import java.text.SimpleDateFormat import java.util.Locale import java.util.concurrent.TimeUnit @@ -75,6 +78,7 @@ data class MetricsState( val tracerouteRequests: List = emptyList(), val tracerouteResults: List = emptyList(), val positionLogs: List = emptyList(), + val deviceHardware: DeviceHardware? = null, ) { fun hasDeviceMetrics() = deviceMetrics.isNotEmpty() fun hasEnvironmentMetrics() = environmentMetrics.isNotEmpty() @@ -207,12 +211,21 @@ class MetricsViewModel @Inject constructor( private val _timeFrame = MutableStateFlow(TimeFrame.TWENTY_FOUR_HOURS) val timeFrame: StateFlow = _timeFrame + private var deviceHardwareList: List = listOf() + init { @OptIn(ExperimentalCoroutinesApi::class) radioConfigRepository.nodeDBbyNum .mapLatest { nodes -> nodes[destNum] } .distinctUntilChanged() - .onEach { node -> _state.update { state -> state.copy(node = node) } } + .onEach { node -> + _state.update { state -> state.copy(node = node) } + node?.user?.hwModel?.let { hwModel -> + _state.update { state -> + state.copy(deviceHardware = getDeviceHardwareFromHardwareModel(hwModel)) + } + } + } .launchIn(viewModelScope) radioConfigRepository.deviceProfileFlow.onEach { profile -> @@ -316,4 +329,20 @@ class MetricsViewModel @Inject constructor( errormsg("Can't write file error: ${ex.message}") } } + + private fun getDeviceHardwareFromHardwareModel( + hwModel: HardwareModel + ): DeviceHardware? { + if (deviceHardwareList.isEmpty()) { + try { + val json = + app.assets.open("device_hardware.json").bufferedReader().use { it.readText() } + deviceHardwareList = Json.decodeFromString>(json) + .map { it.toDeviceHardware() } + } catch (ex: IOException) { + errormsg("Can't read device_hardware.json error: ${ex.message}") + } + } + return deviceHardwareList.find { it.hwModel == hwModel.number } + } } 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 a8917719e..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( @@ -255,6 +253,7 @@ class UIViewModel @Inject constructor( get() = preferences.getInt(MAP_STYLE_ID, 0) set(value) = preferences.edit { putInt(MAP_STYLE_ID, value) } + fun getNode(userId: String?) = nodeDB.getNode(userId ?: DataPacket.ID_BROADCAST) fun getUser(userId: String?) = nodeDB.getUser(userId ?: DataPacket.ID_BROADCAST) private val _snackbarText = MutableLiveData(null) @@ -330,7 +329,7 @@ class UIViewModel @Inject constructor( @OptIn(ExperimentalCoroutinesApi::class) fun getMessagesFrom(contactKey: String) = packetRepository.getMessagesFrom(contactKey) - .mapLatest { list -> list.map { it.toMessage(::getUser) } } + .mapLatest { list -> list.map { it.toMessage(::getNode) } } @OptIn(ExperimentalCoroutinesApi::class) val waypoints = packetRepository.getWaypoints().mapLatest { list -> @@ -485,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/ChannelFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt index 7072d9d58..8af5bc710 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt @@ -49,6 +49,7 @@ import androidx.compose.material.icons.twotone.Check import androidx.compose.material.icons.twotone.Close import androidx.compose.material.icons.twotone.ContentCopy import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf @@ -321,6 +322,11 @@ fun ChannelScreen( channelSelections = channelSelections, onClick = { showChannelEditor = true } ) + EditChannelUrl( + enabled = enabled, + channelUrl = selectedChannelSet.getChannelUrl(), + onConfirm = viewModel::requestChannelUrl + ) } } else { dragDropItemsIndexed( @@ -354,14 +360,6 @@ fun ChannelScreen( } } - item { - EditChannelUrl( - enabled = enabled, - channelUrl = selectedChannelSet.getChannelUrl(), - onConfirm = viewModel::requestChannelUrl - ) - } - item { DropDownPreference(title = stringResource(id = R.string.channel_options), enabled = enabled, @@ -419,6 +417,13 @@ private fun EditChannelUrl( var valueState by remember(channelUrl) { mutableStateOf(channelUrl) } var isError by remember { mutableStateOf(false) } + // Trigger dialog automatically when users paste a new valid URL + LaunchedEffect(valueState, isError) { + if (!isError && valueState != channelUrl) { + onConfirm(valueState) + } + } + OutlinedTextField( value = valueState.toString(), onValueChange = { diff --git a/app/src/main/java/com/geeksville/mesh/ui/NodeDetail.kt b/app/src/main/java/com/geeksville/mesh/ui/NodeDetail.kt index fd179a690..ac7165273 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/NodeDetail.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/NodeDetail.kt @@ -15,10 +15,12 @@ * along with this program. If not, see . */ -@file:Suppress("TooManyFunctions") +@file:Suppress("TooManyFunctions", "LongMethod") package com.geeksville.mesh.ui +import androidx.compose.foundation.Image +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -36,9 +38,11 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Card import androidx.compose.material.CircularProgressIndicator +import androidx.compose.material.ContentAlpha import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.Text @@ -64,6 +68,7 @@ import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.SignalCellularAlt import androidx.compose.material.icons.filled.Speed import androidx.compose.material.icons.filled.Thermostat +import androidx.compose.material.icons.filled.Verified import androidx.compose.material.icons.filled.WaterDrop import androidx.compose.material.icons.filled.Work import androidx.compose.material.icons.outlined.Navigation @@ -71,11 +76,13 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.rotate import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter @@ -98,8 +105,8 @@ import kotlin.math.ln @Composable fun NodeDetailScreen( - viewModel: MetricsViewModel = hiltViewModel(), modifier: Modifier = Modifier, + viewModel: MetricsViewModel = hiltViewModel(), onNavigate: (Any) -> Unit, ) { val state by viewModel.state.collectAsStateWithLifecycle() @@ -124,15 +131,20 @@ fun NodeDetailScreen( @Composable private fun NodeDetailList( + modifier: Modifier = Modifier, node: NodeEntity, metricsState: MetricsState, - modifier: Modifier = Modifier, onNavigate: (Any) -> Unit = {}, ) { LazyColumn( modifier = modifier.fillMaxSize(), contentPadding = PaddingValues(horizontal = 16.dp), ) { + item { + PreferenceCategory("Device") { + DeviceDetailsContent(metricsState) + } + } item { PreferenceCategory("Details") { NodeDetailsContent(node) @@ -176,7 +188,12 @@ private fun NodeDetailList( } @Composable -private fun NodeDetailRow(label: String, icon: ImageVector, value: String) { +private fun NodeDetailRow( + label: String, + icon: ImageVector, + value: String, + iconTint: Color = MaterialTheme.colors.onSurface +) { Row( modifier = Modifier .fillMaxWidth() @@ -186,7 +203,8 @@ private fun NodeDetailRow(label: String, icon: ImageVector, value: String) { Icon( imageVector = icon, contentDescription = label, - modifier = Modifier.size(24.dp) + modifier = Modifier.size(24.dp), + tint = iconTint ) Spacer(modifier = Modifier.width(8.dp)) Text(label) @@ -196,7 +214,49 @@ private fun NodeDetailRow(label: String, icon: ImageVector, value: String) { } @Composable -private fun NodeDetailsContent(node: NodeEntity) { +private fun DeviceDetailsContent( + state: MetricsState, +) { + val node = state.node ?: return + val deviceHardware = state.deviceHardware ?: return + val hwModelName = deviceHardware.displayName + val isSupported = deviceHardware.activelySupported + Box( + modifier = Modifier + .size(100.dp) + .padding(4.dp) + .clip(CircleShape) + .background( + color = Color(node.colors.second).copy(alpha = .5f), + shape = CircleShape + ), + contentAlignment = Alignment.Center + ) { + Image( + modifier = Modifier.padding(16.dp), + imageVector = ImageVector.vectorResource(deviceHardware.image), + contentDescription = hwModelName, + ) + } + NodeDetailRow( + label = "Hardware", + icon = Icons.Default.Router, + value = hwModelName + ) + if (isSupported) { + NodeDetailRow( + label = "Supported", + icon = Icons.Default.Verified, + value = "", + iconTint = Color.Green + ) + } +} + +@Composable +private fun NodeDetailsContent( + node: NodeEntity, +) { if (node.mismatchKey) { Row(verticalAlignment = Alignment.CenterVertically) { Icon( @@ -204,18 +264,21 @@ private fun NodeDetailsContent(node: NodeEntity) { contentDescription = stringResource(id = R.string.encryption_error), tint = Color.Red, ) - Column(modifier = Modifier.padding(start = 8.dp)) { - Text( - text = stringResource(id = R.string.encryption_error), - style = MaterialTheme.typography.h6.copy(color = Color.Red) - ) - Text( - text = stringResource(id = R.string.encryption_error_text), - style = MaterialTheme.typography.body2, - color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f) - ) - } + Spacer(Modifier.width(12.dp)) + Text( + text = stringResource(id = R.string.encryption_error), + style = MaterialTheme.typography.h6.copy(color = Color.Red), + textAlign = TextAlign.Center, + ) } + Spacer(Modifier.height(16.dp)) + Text( + text = stringResource(id = R.string.encryption_error_text), + style = MaterialTheme.typography.body2, + color = MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.medium), + textAlign = TextAlign.Center, + ) + Spacer(Modifier.height(16.dp)) } NodeDetailRow( label = "Node Number", @@ -232,11 +295,6 @@ private fun NodeDetailsContent(node: NodeEntity) { icon = Icons.Default.Work, value = node.user.role.name ) - NodeDetailRow( - label = "Hardware", - icon = Icons.Default.Router, - value = node.user.hwModel.name - ) if (node.deviceMetrics.uptimeSeconds > 0) { NodeDetailRow( label = "Uptime", @@ -449,6 +507,13 @@ private fun EnvironmentMetrics( value = "%.2f kg".format(weight) ) } + if (radiation != 0f) { + InfoCard( + icon = ImageVector.vectorResource(R.drawable.ic_filled_radioactive_24), + text = "Radiation", + value = "%.1f µR".format(radiation) + ) + } } } @@ -534,6 +599,9 @@ private fun NodeDetailsPreview( node: NodeEntity ) { AppTheme { - NodeDetailList(node, MetricsState.Empty) + NodeDetailList( + node = node, + metricsState = MetricsState.Empty, + ) } } 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 4fdc64d7d..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,17 +158,16 @@ fun NodeItem( } NodeMenu( node = thatNode, - ignoreIncomingList = ignoreIncomingList, - isThisNode = isThisNode, - onMenuItemAction = menuItemActionClicked, + showFullMenu = !isThisNode && isConnected, + onAction = onAction, expanded = menuExpanded, onDismissRequest = { menuExpanded = false }, - isConnected = isConnected, ) } NodeKeyStatusIcon( hasPKC = thatNode.hasPKC, mismatchKey = thatNode.mismatchKey, + publicKey = thatNode.user.publicKey, modifier = Modifier.size(32.dp) ) Text( diff --git a/app/src/main/java/com/geeksville/mesh/ui/ShareFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/ShareFragment.kt index 999db49f4..a41fd7163 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/ShareFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/ShareFragment.kt @@ -1,3 +1,20 @@ +/* + * Copyright (c) 2024 Meshtastic LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package com.geeksville.mesh.ui import android.os.Bundle 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/BottomSheetDialog.kt b/app/src/main/java/com/geeksville/mesh/ui/components/BottomSheetDialog.kt index 86aded6c1..5833c9870 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/components/BottomSheetDialog.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/components/BottomSheetDialog.kt @@ -1,3 +1,20 @@ +/* + * Copyright (c) 2024 Meshtastic LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package com.geeksville.mesh.ui.components import androidx.compose.foundation.background diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/MetricsTimeSelector.kt b/app/src/main/java/com/geeksville/mesh/ui/components/MetricsTimeSelector.kt index 39ae19b07..117c2d6df 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/components/MetricsTimeSelector.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/components/MetricsTimeSelector.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 8874-9126 Meshtastic LLC + * Copyright (c) 2024 Meshtastic LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/NodeKeyStatusIcon.kt b/app/src/main/java/com/geeksville/mesh/ui/components/NodeKeyStatusIcon.kt index 233bbc777..10b814aa2 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/components/NodeKeyStatusIcon.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/components/NodeKeyStatusIcon.kt @@ -17,8 +17,25 @@ package com.geeksville.mesh.ui.components +import android.util.Base64 +import androidx.annotation.StringRes +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.ContentAlpha import androidx.compose.material.Icon import androidx.compose.material.IconButton +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.material.TextButton import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.KeyOff import androidx.compose.material.icons.filled.Lock @@ -27,17 +44,83 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.vector.rememberVectorPainter -import androidx.compose.ui.res.painterResource +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.PreviewLightDark +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog import com.geeksville.mesh.R +import com.geeksville.mesh.model.Channel +import com.geeksville.mesh.ui.theme.AppTheme +import com.google.protobuf.ByteString + +@Composable +private fun KeyStatusDialog( + @StringRes title: Int, + @StringRes text: Int, + key: ByteString?, + onDismiss: () -> Unit = {} +) = Dialog( + onDismissRequest = onDismiss, +) { + Surface( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(16.dp), + color = MaterialTheme.colors.background + ) { + LazyColumn( + contentPadding = PaddingValues(horizontal = 24.dp, vertical = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + item { + Text( + text = stringResource(id = title), + color = MaterialTheme.colors.onBackground.copy(alpha = ContentAlpha.high), + textAlign = TextAlign.Center, + ) + Spacer(Modifier.height(16.dp)) + Text( + text = stringResource(id = text), + color = MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.medium), + textAlign = TextAlign.Center, + ) + Spacer(Modifier.height(16.dp)) + if (key != null && title == R.string.encryption_pkc) { + val keyString = Base64.encodeToString(key.toByteArray(), Base64.NO_WRAP) + SelectionContainer { + Text( + text = "Public Key: $keyString", + textAlign = TextAlign.Center, + ) + } + Spacer(Modifier.height(16.dp)) + } + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End, + ) { + TextButton( + onClick = onDismiss, + colors = ButtonDefaults.textButtonColors( + contentColor = MaterialTheme.colors.onSurface, + ), + ) { Text(text = stringResource(id = R.string.close)) } + } + } + } + } +} @Composable fun NodeKeyStatusIcon( hasPKC: Boolean, mismatchKey: Boolean, + publicKey: ByteString? = null, modifier: Modifier = Modifier, ) { var showEncryptionDialog by remember { mutableStateOf(false) } @@ -47,13 +130,13 @@ fun NodeKeyStatusIcon( hasPKC -> R.string.encryption_pkc to R.string.encryption_pkc_text else -> R.string.encryption_psk to R.string.encryption_psk_text } - SimpleAlertDialog(title, text) { showEncryptionDialog = false } + KeyStatusDialog(title, text, publicKey) { showEncryptionDialog = false } } val (icon, tint) = when { - mismatchKey -> rememberVectorPainter(Icons.Default.KeyOff) to Color.Red - hasPKC -> rememberVectorPainter(Icons.Default.Lock) to Color(color = 0xFF30C047) - else -> painterResource(R.drawable.ic_lock_open_right_24) to Color(color = 0xFFFEC30A) + mismatchKey -> Icons.Default.KeyOff to Color.Red + hasPKC -> Icons.Default.Lock to Color(color = 0xFF30C047) + else -> ImageVector.vectorResource(R.drawable.ic_lock_open_right_24) to Color(color = 0xFFFEC30A) } IconButton( @@ -61,7 +144,7 @@ fun NodeKeyStatusIcon( modifier = modifier, ) { Icon( - painter = icon, + imageVector = icon, contentDescription = stringResource( id = when { mismatchKey -> R.string.encryption_error @@ -73,3 +156,39 @@ fun NodeKeyStatusIcon( ) } } + +@PreviewLightDark +@Composable +private fun KeyStatusDialogErrorPreview() { + AppTheme { + KeyStatusDialog( + title = R.string.encryption_error, + text = R.string.encryption_error_text, + key = null, + ) + } +} + +@PreviewLightDark +@Composable +private fun KeyStatusDialogPkcPreview() { + AppTheme { + KeyStatusDialog( + title = R.string.encryption_pkc, + text = R.string.encryption_pkc_text, + key = Channel.getRandomKey(), + ) + } +} + +@PreviewLightDark +@Composable +private fun KeyStatusDialogPskPreview() { + AppTheme { + KeyStatusDialog( + title = R.string.encryption_psk, + text = R.string.encryption_psk_text, + key = null, + ) + } +} 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() } 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 1a5103e55..9470803fb 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 @@ -67,7 +67,7 @@ import androidx.compose.ui.focus.onFocusEvent import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.LocalClipboardManager -import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.pluralStringResource @@ -395,7 +395,7 @@ private fun TextInput( maxSize: Int = 200, onClick: (String) -> Unit = {} ) = Column(modifier) { - val keyboardController = LocalSoftwareKeyboardController.current + val focusManager = LocalFocusManager.current var isFocused by remember { mutableStateOf(false) } Row( @@ -426,7 +426,7 @@ private fun TextInput( if (message.value.text.isNotEmpty()) { onClick(message.value.text) message.value = TextFieldValue("") - keyboardController?.hide() + focusManager.clearFocus() } }, modifier = Modifier.size(48.dp), diff --git a/app/src/main/java/com/geeksville/mesh/ui/message/components/MessageItem.kt b/app/src/main/java/com/geeksville/mesh/ui/message/components/MessageItem.kt index 32853f592..997af5a2d 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/message/components/MessageItem.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/message/components/MessageItem.kt @@ -31,6 +31,7 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Card import androidx.compose.material.Chip +import androidx.compose.material.ChipDefaults import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon import androidx.compose.material.LocalContentColor @@ -55,16 +56,19 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp +import com.geeksville.mesh.DataPacket import com.geeksville.mesh.MessageStatus import com.geeksville.mesh.R +import com.geeksville.mesh.database.entity.NodeEntity import com.geeksville.mesh.ui.components.AutoLinkText +import com.geeksville.mesh.ui.preview.NodeEntityPreviewParameterProvider import com.geeksville.mesh.ui.theme.AppTheme @Suppress("LongMethod") @OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class) @Composable internal fun MessageItem( - shortName: String?, + node: NodeEntity, messageText: String?, messageTime: String, messageStatus: MessageStatus?, @@ -81,7 +85,7 @@ internal fun MessageItem( .background(color = if (selected) Color.Gray else MaterialTheme.colors.background), verticalAlignment = Alignment.CenterVertically, ) { - val fromLocal = shortName == null + val fromLocal = node.user.id == DataPacket.ID_LOCAL val messageColor = if (fromLocal) R.color.colorMyMsg else R.color.colorMsg val (topStart, topEnd) = if (fromLocal) 12.dp to 4.dp else 4.dp to 12.dp val messageModifier = if (fromLocal) { @@ -110,15 +114,19 @@ internal fun MessageItem( .padding(horizontal = 8.dp), verticalAlignment = Alignment.CenterVertically, ) { - if (shortName != null) { + if (!fromLocal) { Chip( onClick = onChipClick, modifier = Modifier .padding(end = 8.dp) .width(72.dp), + colors = ChipDefaults.chipColors( + backgroundColor = Color(node.colors.second), + contentColor = Color(node.colors.first), + ), ) { Text( - text = shortName, + text = node.user.shortName, modifier = Modifier.fillMaxWidth(), fontSize = MaterialTheme.typography.button.fontSize, fontWeight = FontWeight.Normal, @@ -129,11 +137,14 @@ internal fun MessageItem( Column( modifier = Modifier.padding(top = 8.dp), ) { -// Text( -// text = longName ?: stringResource(id = R.string.unknown_username), -// color = MaterialTheme.colors.onSurface, -// fontSize = MaterialTheme.typography.button.fontSize, -// ) +// if (!fromLocal) { +// Text( +// text = with(node.user) { "$longName ($id)" }, +// modifier = Modifier.padding(bottom = 4.dp), +// color = MaterialTheme.colors.onSurface, +// fontSize = MaterialTheme.typography.caption.fontSize, +// ) +// } AutoLinkText( text = messageText.orEmpty(), style = LocalTextStyle.current.copy( @@ -181,8 +192,7 @@ internal fun MessageItem( private fun MessageItemPreview() { AppTheme { MessageItem( - shortName = stringResource(R.string.some_username), - // longName = stringResource(R.string.unknown_username), + node = NodeEntityPreviewParameterProvider().values.first(), messageText = stringResource(R.string.sample_message), messageTime = "10:00", messageStatus = MessageStatus.DELIVERED, diff --git a/app/src/main/java/com/geeksville/mesh/ui/message/components/MessageList.kt b/app/src/main/java/com/geeksville/mesh/ui/message/components/MessageList.kt index 42c30588d..3d8ae96a7 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/message/components/MessageList.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/message/components/MessageList.kt @@ -33,6 +33,8 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Modifier +import androidx.compose.ui.hapticfeedback.HapticFeedbackType +import androidx.compose.ui.platform.LocalHapticFeedback import com.geeksville.mesh.DataPacket import com.geeksville.mesh.database.entity.Reaction import com.geeksville.mesh.model.Message @@ -50,6 +52,7 @@ internal fun MessageList( onSendReaction: (String, Int) -> Unit, onClick: (Message) -> Unit = {} ) { + val haptics = LocalHapticFeedback.current val inSelectionMode by remember { derivedStateOf { selectedIds.value.isNotEmpty() } } val listState = rememberLazyListState( initialFirstVisibleItemIndex = messages.indexOfLast { !it.read }.coerceAtLeast(0) @@ -70,10 +73,10 @@ internal fun MessageList( ReactionDialog(reactions) { showReactionDialog = null } } - fun toggle(uuid: Long) = if (selectedIds.value.contains(uuid)) { - selectedIds.value -= uuid + fun MutableState>.toggle(uuid: Long) = if (value.contains(uuid)) { + value -= uuid } else { - selectedIds.value += uuid + value += uuid } LazyColumn( @@ -83,18 +86,21 @@ internal fun MessageList( contentPadding = contentPadding ) { items(messages, key = { it.uuid }) { msg -> - val fromLocal = msg.user.id == DataPacket.ID_LOCAL + val fromLocal = msg.node.user.id == DataPacket.ID_LOCAL val selected by remember { derivedStateOf { selectedIds.value.contains(msg.uuid) } } ReactionRow(fromLocal, msg.emojis) { showReactionDialog = msg.emojis } MessageItem( - shortName = msg.user.shortName.takeIf { !fromLocal }, + node = msg.node, messageText = msg.text, messageTime = msg.time, messageStatus = msg.status, selected = selected, - onClick = { if (inSelectionMode) toggle(msg.uuid) }, - onLongClick = { toggle(msg.uuid) }, + onClick = { if (inSelectionMode) selectedIds.toggle(msg.uuid) }, + onLongClick = { + selectedIds.toggle(msg.uuid) + haptics.performHapticFeedback(HapticFeedbackType.LongPress) + }, onChipClick = { onClick(msg) }, onStatusClick = { showStatusDialog = msg }, onSendReaction = { onSendReaction(it, msg.packetId) }, diff --git a/app/src/main/play/listings/en-US/graphics/phone-screenshots/1.jpg b/app/src/main/play/listings/en-US/graphics/phone-screenshots/1.jpg new file mode 100644 index 000000000..83442ac05 Binary files /dev/null and b/app/src/main/play/listings/en-US/graphics/phone-screenshots/1.jpg differ diff --git a/app/src/main/play/listings/en-US/graphics/phone-screenshots/1.png b/app/src/main/play/listings/en-US/graphics/phone-screenshots/1.png deleted file mode 100644 index 78a81dccc..000000000 Binary files a/app/src/main/play/listings/en-US/graphics/phone-screenshots/1.png and /dev/null differ diff --git a/app/src/main/play/listings/en-US/graphics/phone-screenshots/2.jpg b/app/src/main/play/listings/en-US/graphics/phone-screenshots/2.jpg new file mode 100644 index 000000000..9ce58fc3f Binary files /dev/null and b/app/src/main/play/listings/en-US/graphics/phone-screenshots/2.jpg differ diff --git a/app/src/main/play/listings/en-US/graphics/phone-screenshots/2.png b/app/src/main/play/listings/en-US/graphics/phone-screenshots/2.png deleted file mode 100644 index a884644e0..000000000 Binary files a/app/src/main/play/listings/en-US/graphics/phone-screenshots/2.png and /dev/null differ diff --git a/app/src/main/play/listings/en-US/graphics/phone-screenshots/3.jpg b/app/src/main/play/listings/en-US/graphics/phone-screenshots/3.jpg new file mode 100644 index 000000000..8e6966e1b Binary files /dev/null and b/app/src/main/play/listings/en-US/graphics/phone-screenshots/3.jpg differ diff --git a/app/src/main/play/listings/en-US/graphics/phone-screenshots/3.png b/app/src/main/play/listings/en-US/graphics/phone-screenshots/3.png deleted file mode 100644 index c198e8b39..000000000 Binary files a/app/src/main/play/listings/en-US/graphics/phone-screenshots/3.png and /dev/null differ diff --git a/app/src/main/play/listings/en-US/graphics/phone-screenshots/4.jpg b/app/src/main/play/listings/en-US/graphics/phone-screenshots/4.jpg new file mode 100644 index 000000000..97427e204 Binary files /dev/null and b/app/src/main/play/listings/en-US/graphics/phone-screenshots/4.jpg differ diff --git a/app/src/main/play/listings/en-US/graphics/phone-screenshots/4.png b/app/src/main/play/listings/en-US/graphics/phone-screenshots/4.png deleted file mode 100644 index 63d3f1e20..000000000 Binary files a/app/src/main/play/listings/en-US/graphics/phone-screenshots/4.png and /dev/null differ diff --git a/app/src/main/play/listings/en-US/graphics/phone-screenshots/5.jpg b/app/src/main/play/listings/en-US/graphics/phone-screenshots/5.jpg new file mode 100644 index 000000000..04340960d Binary files /dev/null and b/app/src/main/play/listings/en-US/graphics/phone-screenshots/5.jpg differ diff --git a/app/src/main/play/listings/en-US/graphics/phone-screenshots/5.png b/app/src/main/play/listings/en-US/graphics/phone-screenshots/5.png deleted file mode 100644 index 1ed4e9df3..000000000 Binary files a/app/src/main/play/listings/en-US/graphics/phone-screenshots/5.png and /dev/null differ diff --git a/app/src/main/res/drawable/hw_diy.xml b/app/src/main/res/drawable/hw_diy.xml new file mode 100644 index 000000000..feedd56c7 --- /dev/null +++ b/app/src/main/res/drawable/hw_diy.xml @@ -0,0 +1,1631 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/hw_heltec_ht62_esp32c3_sx1262.xml b/app/src/main/res/drawable/hw_heltec_ht62_esp32c3_sx1262.xml new file mode 100644 index 000000000..1a0c19777 --- /dev/null +++ b/app/src/main/res/drawable/hw_heltec_ht62_esp32c3_sx1262.xml @@ -0,0 +1,1714 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/hw_heltec_mesh_node_t114.xml b/app/src/main/res/drawable/hw_heltec_mesh_node_t114.xml new file mode 100644 index 000000000..457ffd6cd --- /dev/null +++ b/app/src/main/res/drawable/hw_heltec_mesh_node_t114.xml @@ -0,0 +1,277 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/hw_heltec_mesh_node_t114_case.xml b/app/src/main/res/drawable/hw_heltec_mesh_node_t114_case.xml new file mode 100644 index 000000000..e95e13e6c --- /dev/null +++ b/app/src/main/res/drawable/hw_heltec_mesh_node_t114_case.xml @@ -0,0 +1,179 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/hw_heltec_v3.xml b/app/src/main/res/drawable/hw_heltec_v3.xml new file mode 100644 index 000000000..2031cb196 --- /dev/null +++ b/app/src/main/res/drawable/hw_heltec_v3.xml @@ -0,0 +1,1141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/hw_heltec_v3_case.xml b/app/src/main/res/drawable/hw_heltec_v3_case.xml new file mode 100644 index 000000000..5e444b6f0 --- /dev/null +++ b/app/src/main/res/drawable/hw_heltec_v3_case.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/hw_heltec_vision_master_e213.xml b/app/src/main/res/drawable/hw_heltec_vision_master_e213.xml new file mode 100644 index 000000000..017b80bdd --- /dev/null +++ b/app/src/main/res/drawable/hw_heltec_vision_master_e213.xml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/hw_heltec_vision_master_e290.xml b/app/src/main/res/drawable/hw_heltec_vision_master_e290.xml new file mode 100644 index 000000000..f617ecd74 --- /dev/null +++ b/app/src/main/res/drawable/hw_heltec_vision_master_e290.xml @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/hw_heltec_vision_master_t190.xml b/app/src/main/res/drawable/hw_heltec_vision_master_t190.xml new file mode 100644 index 000000000..6fd688f1d --- /dev/null +++ b/app/src/main/res/drawable/hw_heltec_vision_master_t190.xml @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/hw_heltec_wireless_paper.xml b/app/src/main/res/drawable/hw_heltec_wireless_paper.xml new file mode 100644 index 000000000..66db18fc7 --- /dev/null +++ b/app/src/main/res/drawable/hw_heltec_wireless_paper.xml @@ -0,0 +1,428 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/hw_heltec_wireless_paper_v1_0.xml b/app/src/main/res/drawable/hw_heltec_wireless_paper_v1_0.xml new file mode 100644 index 000000000..66db18fc7 --- /dev/null +++ b/app/src/main/res/drawable/hw_heltec_wireless_paper_v1_0.xml @@ -0,0 +1,428 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/hw_heltec_wireless_tracker.xml b/app/src/main/res/drawable/hw_heltec_wireless_tracker.xml new file mode 100644 index 000000000..74d14932d --- /dev/null +++ b/app/src/main/res/drawable/hw_heltec_wireless_tracker.xml @@ -0,0 +1,1619 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/hw_heltec_wireless_tracker_v1_0.xml b/app/src/main/res/drawable/hw_heltec_wireless_tracker_v1_0.xml new file mode 100644 index 000000000..74d14932d --- /dev/null +++ b/app/src/main/res/drawable/hw_heltec_wireless_tracker_v1_0.xml @@ -0,0 +1,1619 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/hw_heltec_wsl_v3.xml b/app/src/main/res/drawable/hw_heltec_wsl_v3.xml new file mode 100644 index 000000000..f4246b21b --- /dev/null +++ b/app/src/main/res/drawable/hw_heltec_wsl_v3.xml @@ -0,0 +1,1496 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/hw_nano_g2_ultra.xml b/app/src/main/res/drawable/hw_nano_g2_ultra.xml new file mode 100644 index 000000000..60bfb06db --- /dev/null +++ b/app/src/main/res/drawable/hw_nano_g2_ultra.xml @@ -0,0 +1,185 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/hw_pico.xml b/app/src/main/res/drawable/hw_pico.xml new file mode 100644 index 000000000..eb3935ab3 --- /dev/null +++ b/app/src/main/res/drawable/hw_pico.xml @@ -0,0 +1,1717 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/hw_promicro.xml b/app/src/main/res/drawable/hw_promicro.xml new file mode 100644 index 000000000..2f69e14ca --- /dev/null +++ b/app/src/main/res/drawable/hw_promicro.xml @@ -0,0 +1,1555 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/hw_rak11310.xml b/app/src/main/res/drawable/hw_rak11310.xml new file mode 100644 index 000000000..5d39943f8 --- /dev/null +++ b/app/src/main/res/drawable/hw_rak11310.xml @@ -0,0 +1,1228 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/hw_rak4631.xml b/app/src/main/res/drawable/hw_rak4631.xml new file mode 100644 index 000000000..ff6225c06 --- /dev/null +++ b/app/src/main/res/drawable/hw_rak4631.xml @@ -0,0 +1,1999 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/hw_rak4631_case.xml b/app/src/main/res/drawable/hw_rak4631_case.xml new file mode 100644 index 000000000..d937ffbc7 --- /dev/null +++ b/app/src/main/res/drawable/hw_rak4631_case.xml @@ -0,0 +1,263 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/hw_rak_wismeshtap.xml b/app/src/main/res/drawable/hw_rak_wismeshtap.xml new file mode 100644 index 000000000..fb9084b49 --- /dev/null +++ b/app/src/main/res/drawable/hw_rak_wismeshtap.xml @@ -0,0 +1,180 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/hw_rpipicow.xml b/app/src/main/res/drawable/hw_rpipicow.xml new file mode 100644 index 000000000..961078561 --- /dev/null +++ b/app/src/main/res/drawable/hw_rpipicow.xml @@ -0,0 +1,1645 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/hw_seeed_sensecap_indicator.xml b/app/src/main/res/drawable/hw_seeed_sensecap_indicator.xml new file mode 100644 index 000000000..9aecd2b97 --- /dev/null +++ b/app/src/main/res/drawable/hw_seeed_sensecap_indicator.xml @@ -0,0 +1,292 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/hw_seeed_xiao_s3.xml b/app/src/main/res/drawable/hw_seeed_xiao_s3.xml new file mode 100644 index 000000000..242e2d734 --- /dev/null +++ b/app/src/main/res/drawable/hw_seeed_xiao_s3.xml @@ -0,0 +1,711 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/hw_station_g2.xml b/app/src/main/res/drawable/hw_station_g2.xml new file mode 100644 index 000000000..72f187ae5 --- /dev/null +++ b/app/src/main/res/drawable/hw_station_g2.xml @@ -0,0 +1,437 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/hw_t_deck.xml b/app/src/main/res/drawable/hw_t_deck.xml new file mode 100644 index 000000000..d13a6f1e2 --- /dev/null +++ b/app/src/main/res/drawable/hw_t_deck.xml @@ -0,0 +1,658 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/hw_t_echo.xml b/app/src/main/res/drawable/hw_t_echo.xml new file mode 100644 index 000000000..c9642c1bc --- /dev/null +++ b/app/src/main/res/drawable/hw_t_echo.xml @@ -0,0 +1,247 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/hw_t_watch_s3.xml b/app/src/main/res/drawable/hw_t_watch_s3.xml new file mode 100644 index 000000000..9da42a48d --- /dev/null +++ b/app/src/main/res/drawable/hw_t_watch_s3.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/hw_tbeam.xml b/app/src/main/res/drawable/hw_tbeam.xml new file mode 100644 index 000000000..1ab61b314 --- /dev/null +++ b/app/src/main/res/drawable/hw_tbeam.xml @@ -0,0 +1,2693 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/hw_tbeam_s3_core.xml b/app/src/main/res/drawable/hw_tbeam_s3_core.xml new file mode 100644 index 000000000..8387e402d --- /dev/null +++ b/app/src/main/res/drawable/hw_tbeam_s3_core.xml @@ -0,0 +1,1583 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/hw_tlora_c6.xml b/app/src/main/res/drawable/hw_tlora_c6.xml new file mode 100644 index 000000000..dad87ed5c --- /dev/null +++ b/app/src/main/res/drawable/hw_tlora_c6.xml @@ -0,0 +1,515 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/hw_tlora_t3s3_epaper.xml b/app/src/main/res/drawable/hw_tlora_t3s3_epaper.xml new file mode 100644 index 000000000..37cc0e043 --- /dev/null +++ b/app/src/main/res/drawable/hw_tlora_t3s3_epaper.xml @@ -0,0 +1,226 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/hw_tlora_t3s3_v1.xml b/app/src/main/res/drawable/hw_tlora_t3s3_v1.xml new file mode 100644 index 000000000..42e5695ab --- /dev/null +++ b/app/src/main/res/drawable/hw_tlora_t3s3_v1.xml @@ -0,0 +1,932 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/hw_tlora_v2_1_1_6.xml b/app/src/main/res/drawable/hw_tlora_v2_1_1_6.xml new file mode 100644 index 000000000..123a78383 --- /dev/null +++ b/app/src/main/res/drawable/hw_tlora_v2_1_1_6.xml @@ -0,0 +1,880 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/hw_tlora_v2_1_1_8.xml b/app/src/main/res/drawable/hw_tlora_v2_1_1_8.xml new file mode 100644 index 000000000..123a78383 --- /dev/null +++ b/app/src/main/res/drawable/hw_tlora_v2_1_1_8.xml @@ -0,0 +1,880 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/hw_tracker_t1000_e.xml b/app/src/main/res/drawable/hw_tracker_t1000_e.xml new file mode 100644 index 000000000..2efed4673 --- /dev/null +++ b/app/src/main/res/drawable/hw_tracker_t1000_e.xml @@ -0,0 +1,259 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/hw_unknown.xml b/app/src/main/res/drawable/hw_unknown.xml new file mode 100644 index 000000000..e12d55def --- /dev/null +++ b/app/src/main/res/drawable/hw_unknown.xml @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/hw_wio_tracker_wm1110.xml b/app/src/main/res/drawable/hw_wio_tracker_wm1110.xml new file mode 100644 index 000000000..27ac4e7dc --- /dev/null +++ b/app/src/main/res/drawable/hw_wio_tracker_wm1110.xml @@ -0,0 +1,2178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/hw_wm1110_dev_kit.xml b/app/src/main/res/drawable/hw_wm1110_dev_kit.xml new file mode 100644 index 000000000..94c9bdf22 --- /dev/null +++ b/app/src/main/res/drawable/hw_wm1110_dev_kit.xml @@ -0,0 +1,3080 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_filled_radioactive_24.xml b/app/src/main/res/drawable/ic_filled_radioactive_24.xml new file mode 100644 index 000000000..13fdb3021 --- /dev/null +++ b/app/src/main/res/drawable/ic_filled_radioactive_24.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/build.gradle b/build.gradle index d761fbff6..f82d8d8e5 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:8.7.2' + classpath 'com.android.tools.build:gradle:8.7.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" diff --git a/config/detekt/detekt-baseline.xml b/config/detekt/detekt-baseline.xml index c41d1c503..a1b8943b3 100644 --- a/config/detekt/detekt-baseline.xml +++ b/config/detekt/detekt-baseline.xml @@ -2,6 +2,7 @@ + AbsentOrWrongFileLicense:LazyColumnDragAndDropDemo.kt$com.geeksville.mesh.ui.components.LazyColumnDragAndDropDemo.kt ChainWrapping:Channel.kt$Channel$&& ChainWrapping:CustomTileSource.kt$CustomTileSource.Companion.<no name provided>$+ ChainWrapping:SqlTileWriterExt.kt$SqlTileWriterExt$+ diff --git a/config/detekt/detekt.yml b/config/detekt/detekt.yml index eb528e777..b27dfe37e 100644 --- a/config/detekt/detekt.yml +++ b/config/detekt/detekt.yml @@ -53,7 +53,7 @@ output-reports: comments: active: true AbsentOrWrongFileLicense: - active: false + active: true licenseTemplateFile: 'license.template' licenseTemplateIsRegex: false CommentOverPrivateFunction: diff --git a/config/detekt/license.template b/config/detekt/license.template new file mode 100644 index 000000000..63c871a20 --- /dev/null +++ b/config/detekt/license.template @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2024 Meshtastic LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +