Entity state complication improvements (more flexibility) (#3465)

* Add ComplicationType.LONG_TEXT support, friendly state

 - Makes it possible for watch faces to request a long text complication
 - Use the friendly state to support translated states and dates

* Add show title option

 - Allows hiding the title of a complication in case it doesn't look right

* Implement/fix reading state from database

 - When a entity ID is provided in the configuration request, load data for that complication from the database to allow easy reconfiguration
 - Provide more appropriate error messages when complication isn't configured / the entity doesn't exist

* ktlint
This commit is contained in:
Joris Pelgröm 2023-04-08 22:33:16 +02:00 committed by GitHub
parent 95a322fc0b
commit 64ee62b8fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1222 additions and 53 deletions

View File

@ -163,7 +163,6 @@ dependencies {
implementation("com.google.android.material:material:1.8.0") implementation("com.google.android.material:material:1.8.0")
implementation("androidx.fragment:fragment-ktx:1.5.6") implementation("androidx.fragment:fragment-ktx:1.5.6")
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.5") implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.5")
implementation("com.squareup.okhttp3:okhttp:4.10.0") implementation("com.squareup.okhttp3:okhttp:4.10.0")
implementation("com.squareup.picasso:picasso:2.8") implementation("com.squareup.picasso:picasso:2.8")

View File

@ -68,7 +68,7 @@ dependencies {
api("androidx.work:work-runtime-ktx:2.8.1") api("androidx.work:work-runtime-ktx:2.8.1")
implementation("com.squareup.retrofit2:retrofit:2.9.0") api("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-jackson:2.9.0") implementation("com.squareup.retrofit2:converter-jackson:2.9.0")
implementation("com.squareup.okhttp3:okhttp:4.10.0") implementation("com.squareup.okhttp3:okhttp:4.10.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.10.0") implementation("com.squareup.okhttp3:logging-interceptor:4.10.0")

View File

@ -0,0 +1,994 @@
{
"formatVersion": 1,
"database": {
"version": 40,
"identityHash": "9ec60cb96f3febd24dc5713f23f65e50",
"entities": [
{
"tableName": "sensor_attributes",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sensor_id` TEXT NOT NULL, `name` TEXT NOT NULL, `value` TEXT NOT NULL, `value_type` TEXT NOT NULL, PRIMARY KEY(`sensor_id`, `name`))",
"fields": [
{
"fieldPath": "sensorId",
"columnName": "sensor_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "value",
"columnName": "value",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "valueType",
"columnName": "value_type",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"sensor_id",
"name"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "authentication_list",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`host` TEXT NOT NULL, `username` TEXT NOT NULL, `password` TEXT NOT NULL, PRIMARY KEY(`host`))",
"fields": [
{
"fieldPath": "host",
"columnName": "host",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "username",
"columnName": "username",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "password",
"columnName": "password",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"host"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "sensors",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `server_id` INTEGER NOT NULL DEFAULT 0, `enabled` INTEGER NOT NULL, `registered` INTEGER DEFAULT NULL, `state` TEXT NOT NULL, `last_sent_state` TEXT DEFAULT NULL, `last_sent_icon` TEXT DEFAULT NULL, `state_type` TEXT NOT NULL, `type` TEXT NOT NULL, `icon` TEXT NOT NULL, `name` TEXT NOT NULL, `device_class` TEXT, `unit_of_measurement` TEXT, `state_class` TEXT, `entity_category` TEXT, `core_registration` TEXT, `app_registration` TEXT, PRIMARY KEY(`id`, `server_id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "serverId",
"columnName": "server_id",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "enabled",
"columnName": "enabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "registered",
"columnName": "registered",
"affinity": "INTEGER",
"notNull": false,
"defaultValue": "NULL"
},
{
"fieldPath": "state",
"columnName": "state",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "lastSentState",
"columnName": "last_sent_state",
"affinity": "TEXT",
"notNull": false,
"defaultValue": "NULL"
},
{
"fieldPath": "lastSentIcon",
"columnName": "last_sent_icon",
"affinity": "TEXT",
"notNull": false,
"defaultValue": "NULL"
},
{
"fieldPath": "stateType",
"columnName": "state_type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "icon",
"columnName": "icon",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "deviceClass",
"columnName": "device_class",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "unitOfMeasurement",
"columnName": "unit_of_measurement",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "stateClass",
"columnName": "state_class",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "entityCategory",
"columnName": "entity_category",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "coreRegistration",
"columnName": "core_registration",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "appRegistration",
"columnName": "app_registration",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id",
"server_id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "sensor_settings",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sensor_id` TEXT NOT NULL, `name` TEXT NOT NULL, `value` TEXT NOT NULL, `value_type` TEXT NOT NULL, `enabled` INTEGER NOT NULL, `entries` TEXT NOT NULL, PRIMARY KEY(`sensor_id`, `name`))",
"fields": [
{
"fieldPath": "sensorId",
"columnName": "sensor_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "value",
"columnName": "value",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "valueType",
"columnName": "value_type",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "enabled",
"columnName": "enabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "entries",
"columnName": "entries",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"sensor_id",
"name"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "button_widgets",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `server_id` INTEGER NOT NULL DEFAULT 0, `icon_id` INTEGER NOT NULL, `domain` TEXT NOT NULL, `service` TEXT NOT NULL, `service_data` TEXT NOT NULL, `label` TEXT, `background_type` TEXT NOT NULL DEFAULT 'DAYNIGHT', `text_color` TEXT, `require_authentication` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serverId",
"columnName": "server_id",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "iconId",
"columnName": "icon_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "domain",
"columnName": "domain",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "service",
"columnName": "service",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "serviceData",
"columnName": "service_data",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "label",
"columnName": "label",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "backgroundType",
"columnName": "background_type",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "'DAYNIGHT'"
},
{
"fieldPath": "textColor",
"columnName": "text_color",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "requireAuthentication",
"columnName": "require_authentication",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "camera_widgets",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `server_id` INTEGER NOT NULL DEFAULT 0, `entity_id` TEXT NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serverId",
"columnName": "server_id",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "entityId",
"columnName": "entity_id",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "media_player_controls_widgets",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `server_id` INTEGER NOT NULL DEFAULT 0, `entity_id` TEXT NOT NULL, `label` TEXT, `show_skip` INTEGER NOT NULL, `show_seek` INTEGER NOT NULL, `show_volume` INTEGER NOT NULL, `show_source` INTEGER NOT NULL DEFAULT false, `background_type` TEXT NOT NULL DEFAULT 'DAYNIGHT', `text_color` TEXT, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serverId",
"columnName": "server_id",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "entityId",
"columnName": "entity_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "label",
"columnName": "label",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "showSkip",
"columnName": "show_skip",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "showSeek",
"columnName": "show_seek",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "showVolume",
"columnName": "show_volume",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "showSource",
"columnName": "show_source",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "backgroundType",
"columnName": "background_type",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "'DAYNIGHT'"
},
{
"fieldPath": "textColor",
"columnName": "text_color",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "static_widget",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `server_id` INTEGER NOT NULL DEFAULT 0, `entity_id` TEXT NOT NULL, `attribute_ids` TEXT, `label` TEXT, `text_size` REAL NOT NULL, `state_separator` TEXT NOT NULL, `attribute_separator` TEXT NOT NULL, `last_update` TEXT NOT NULL, `background_type` TEXT NOT NULL DEFAULT 'DAYNIGHT', `text_color` TEXT, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serverId",
"columnName": "server_id",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "entityId",
"columnName": "entity_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "attributeIds",
"columnName": "attribute_ids",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "label",
"columnName": "label",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "textSize",
"columnName": "text_size",
"affinity": "REAL",
"notNull": true
},
{
"fieldPath": "stateSeparator",
"columnName": "state_separator",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "attributeSeparator",
"columnName": "attribute_separator",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "lastUpdate",
"columnName": "last_update",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "backgroundType",
"columnName": "background_type",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "'DAYNIGHT'"
},
{
"fieldPath": "textColor",
"columnName": "text_color",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "template_widgets",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `server_id` INTEGER NOT NULL DEFAULT 0, `template` TEXT NOT NULL, `text_size` REAL NOT NULL DEFAULT 12.0, `last_update` TEXT NOT NULL, `background_type` TEXT NOT NULL DEFAULT 'DAYNIGHT', `text_color` TEXT, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "serverId",
"columnName": "server_id",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "template",
"columnName": "template",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "textSize",
"columnName": "text_size",
"affinity": "REAL",
"notNull": true,
"defaultValue": "12.0"
},
{
"fieldPath": "lastUpdate",
"columnName": "last_update",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "backgroundType",
"columnName": "background_type",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "'DAYNIGHT'"
},
{
"fieldPath": "textColor",
"columnName": "text_color",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "notification_history",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `received` INTEGER NOT NULL, `message` TEXT NOT NULL, `data` TEXT NOT NULL, `source` TEXT NOT NULL, `server_id` INTEGER)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "received",
"columnName": "received",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "message",
"columnName": "message",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "data",
"columnName": "data",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "source",
"columnName": "source",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "serverId",
"columnName": "server_id",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "qs_tiles",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `tile_id` TEXT NOT NULL, `added` INTEGER NOT NULL DEFAULT 1, `server_id` INTEGER NOT NULL DEFAULT 0, `icon_id` INTEGER, `entity_id` TEXT NOT NULL, `label` TEXT NOT NULL, `subtitle` TEXT, `should_vibrate` INTEGER NOT NULL DEFAULT 0, `auth_required` INTEGER NOT NULL DEFAULT 0)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "tileId",
"columnName": "tile_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "added",
"columnName": "added",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "1"
},
{
"fieldPath": "serverId",
"columnName": "server_id",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "iconId",
"columnName": "icon_id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "entityId",
"columnName": "entity_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "label",
"columnName": "label",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "subtitle",
"columnName": "subtitle",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "shouldVibrate",
"columnName": "should_vibrate",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
},
{
"fieldPath": "authRequired",
"columnName": "auth_required",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "0"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "favorites",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `position` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "position",
"columnName": "position",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "favorite_cache",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `friendly_name` TEXT NOT NULL, `icon` TEXT, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "friendlyName",
"columnName": "friendly_name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "icon",
"columnName": "icon",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "entity_state_complications",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `entity_id` TEXT NOT NULL, `show_title` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "entityId",
"columnName": "entity_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "showTitle",
"columnName": "show_title",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "1"
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "servers",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `_name` TEXT NOT NULL, `name_override` TEXT, `_version` TEXT, `list_order` INTEGER NOT NULL, `device_name` TEXT, `external_url` TEXT NOT NULL, `internal_url` TEXT, `cloud_url` TEXT, `webhook_id` TEXT, `secret` TEXT, `cloudhook_url` TEXT, `use_cloud` INTEGER NOT NULL, `internal_ssids` TEXT NOT NULL, `prioritize_internal` INTEGER NOT NULL, `access_token` TEXT, `refresh_token` TEXT, `token_expiration` INTEGER, `token_type` TEXT, `install_id` TEXT, `user_id` TEXT, `user_name` TEXT, `user_is_owner` INTEGER, `user_is_admin` INTEGER)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "_name",
"columnName": "_name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "nameOverride",
"columnName": "name_override",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "_version",
"columnName": "_version",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "listOrder",
"columnName": "list_order",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "deviceName",
"columnName": "device_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "connection.externalUrl",
"columnName": "external_url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "connection.internalUrl",
"columnName": "internal_url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "connection.cloudUrl",
"columnName": "cloud_url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "connection.webhookId",
"columnName": "webhook_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "connection.secret",
"columnName": "secret",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "connection.cloudhookUrl",
"columnName": "cloudhook_url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "connection.useCloud",
"columnName": "use_cloud",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "connection.internalSsids",
"columnName": "internal_ssids",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "connection.prioritizeInternal",
"columnName": "prioritize_internal",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "session.accessToken",
"columnName": "access_token",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "session.refreshToken",
"columnName": "refresh_token",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "session.tokenExpiration",
"columnName": "token_expiration",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "session.tokenType",
"columnName": "token_type",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "session.installId",
"columnName": "install_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "user.id",
"columnName": "user_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "user.name",
"columnName": "user_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "user.isOwner",
"columnName": "user_is_owner",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "user.isAdmin",
"columnName": "user_is_admin",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "settings",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `websocket_setting` TEXT NOT NULL, `sensor_update_frequency` TEXT NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "websocketSetting",
"columnName": "websocket_setting",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "sensorUpdateFrequency",
"columnName": "sensor_update_frequency",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '9ec60cb96f3febd24dc5713f23f65e50')"
]
}
}

View File

@ -87,7 +87,7 @@ import io.homeassistant.companion.android.common.R as commonR
Server::class, Server::class,
Setting::class Setting::class
], ],
version = 39, version = 40,
autoMigrations = [ autoMigrations = [
AutoMigration(from = 24, to = 25), AutoMigration(from = 24, to = 25),
AutoMigration(from = 25, to = 26), AutoMigration(from = 25, to = 26),
@ -103,7 +103,8 @@ import io.homeassistant.companion.android.common.R as commonR
AutoMigration(from = 35, to = 36), AutoMigration(from = 35, to = 36),
AutoMigration(from = 36, to = 37, spec = AppDatabase.Companion.Migration36to37::class), AutoMigration(from = 36, to = 37, spec = AppDatabase.Companion.Migration36to37::class),
AutoMigration(from = 37, to = 38, spec = AppDatabase.Companion.Migration37to38::class), AutoMigration(from = 37, to = 38, spec = AppDatabase.Companion.Migration37to38::class),
AutoMigration(from = 38, to = 39) AutoMigration(from = 38, to = 39),
AutoMigration(from = 39, to = 40)
] ]
) )
@TypeConverters( @TypeConverters(

View File

@ -13,5 +13,7 @@ data class EntityStateComplications(
@ColumnInfo(name = "id") @ColumnInfo(name = "id")
val id: Int, val id: Int,
@ColumnInfo(name = "entity_id") @ColumnInfo(name = "entity_id")
val entityId: String val entityId: String,
@ColumnInfo(name = "show_title", defaultValue = "1")
val showTitle: Boolean
) )

View File

@ -125,7 +125,7 @@
<string name="complication_entity_invalid">Invalid entity</string> <string name="complication_entity_invalid">Invalid entity</string>
<string name="complication_entity_state_content_description">Entity state</string> <string name="complication_entity_state_content_description">Entity state</string>
<string name="complication_entity_state_label">Entity state</string> <string name="complication_entity_state_label">Entity state</string>
<string name="complication_entity_state_preview">preview</string> <string name="complication_entity_state_preview">Preview</string>
<string name="config">Configuration</string> <string name="config">Configuration</string>
<string name="configure_service_call">Configure Service Call</string> <string name="configure_service_call">Configure Service Call</string>
<string name="configure_widget_label">Widget Label</string> <string name="configure_widget_label">Widget Label</string>
@ -721,6 +721,7 @@
<string name="show">Show</string> <string name="show">Show</string>
<string name="show_share_logs_summary">Sharing logs with the Home Assistant team will help to solve issues. Please share the logs only if you have been asked to do so by a Home Assistant developer</string> <string name="show_share_logs_summary">Sharing logs with the Home Assistant team will help to solve issues. Please share the logs only if you have been asked to do so by a Home Assistant developer</string>
<string name="show_share_logs">Show and Share Logs</string> <string name="show_share_logs">Show and Share Logs</string>
<string name="show_entity_title">Show entity name</string>
<string name="sign_in_on_phone">Sign in on phone</string> <string name="sign_in_on_phone">Sign in on phone</string>
<string name="slider_decreased">%1$s decreased</string> <string name="slider_decreased">%1$s decreased</string>
<string name="slider_increased">%1$s increased</string> <string name="slider_increased">%1$s increased</string>

View File

@ -96,6 +96,7 @@ dependencies {
implementation("com.google.android.material:material:1.8.0") implementation("com.google.android.material:material:1.8.0")
implementation("androidx.wear:wear:1.2.0") implementation("androidx.wear:wear:1.2.0")
implementation("androidx.core:core-ktx:1.10.0")
implementation("com.google.android.gms:play-services-wearable:18.0.0") implementation("com.google.android.gms:play-services-wearable:18.0.0")
implementation("androidx.wear:wear-input:1.2.0-alpha02") implementation("androidx.wear:wear-input:1.2.0-alpha02")
implementation("androidx.wear:wear-remote-interactions:1.0.0") implementation("androidx.wear:wear-remote-interactions:1.0.0")

View File

@ -154,7 +154,7 @@
<meta-data <meta-data
android:name="android.support.wearable.complications.SUPPORTED_TYPES" android:name="android.support.wearable.complications.SUPPORTED_TYPES"
android:value="SHORT_TEXT" /> android:value="SHORT_TEXT,LONG_TEXT" />
<meta-data <meta-data
android:name="android.support.wearable.complications.UPDATE_PERIOD_SECONDS" android:name="android.support.wearable.complications.UPDATE_PERIOD_SECONDS"
android:value="900" /> android:value="900" />

View File

@ -9,6 +9,7 @@ import android.util.Log
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.core.content.IntentCompat
import androidx.wear.watchface.complications.datasource.ComplicationDataSourceService.Companion.EXTRA_CONFIG_COMPLICATION_ID import androidx.wear.watchface.complications.datasource.ComplicationDataSourceService.Companion.EXTRA_CONFIG_COMPLICATION_ID
import androidx.wear.watchface.complications.datasource.ComplicationDataSourceService.Companion.EXTRA_CONFIG_COMPLICATION_TYPE import androidx.wear.watchface.complications.datasource.ComplicationDataSourceService.Companion.EXTRA_CONFIG_COMPLICATION_TYPE
import androidx.wear.watchface.complications.datasource.ComplicationDataSourceService.Companion.EXTRA_CONFIG_DATA_SOURCE_COMPONENT import androidx.wear.watchface.complications.datasource.ComplicationDataSourceService.Companion.EXTRA_CONFIG_DATA_SOURCE_COMPONENT
@ -33,9 +34,11 @@ class ComplicationConfigActivity : ComponentActivity() {
val id = intent.getIntExtra(EXTRA_CONFIG_COMPLICATION_ID, -1) val id = intent.getIntExtra(EXTRA_CONFIG_COMPLICATION_ID, -1)
val type = intent.getIntExtra(EXTRA_CONFIG_COMPLICATION_TYPE, -1) val type = intent.getIntExtra(EXTRA_CONFIG_COMPLICATION_TYPE, -1)
val component = intent.getParcelableExtra<ComponentName>(EXTRA_CONFIG_DATA_SOURCE_COMPONENT) val component = IntentCompat.getParcelableExtra(intent, EXTRA_CONFIG_DATA_SOURCE_COMPONENT, ComponentName::class.java)
Log.i(TAG, "Config for id $id of type $type for component ${component?.className}") Log.i(TAG, "Config for id $id of type $type for component ${component?.className}")
complicationConfigViewModel.setDataFromIntent(id)
setContent { setContent {
LoadConfigView( LoadConfigView(
complicationConfigViewModel complicationConfigViewModel

View File

@ -15,6 +15,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import io.homeassistant.companion.android.HomeAssistantApplication import io.homeassistant.companion.android.HomeAssistantApplication
import io.homeassistant.companion.android.common.data.integration.Entity import io.homeassistant.companion.android.common.data.integration.Entity
import io.homeassistant.companion.android.common.data.integration.domain import io.homeassistant.companion.android.common.data.integration.domain
import io.homeassistant.companion.android.common.data.integration.friendlyName
import io.homeassistant.companion.android.common.data.servers.ServerManager import io.homeassistant.companion.android.common.data.servers.ServerManager
import io.homeassistant.companion.android.common.data.websocket.WebSocketState import io.homeassistant.companion.android.common.data.websocket.WebSocketState
import io.homeassistant.companion.android.data.SimplifiedEntity import io.homeassistant.companion.android.data.SimplifiedEntity
@ -29,8 +30,8 @@ import javax.inject.Inject
@HiltViewModel @HiltViewModel
class ComplicationConfigViewModel @Inject constructor( class ComplicationConfigViewModel @Inject constructor(
application: Application, application: Application,
favoritesDao: FavoritesDao,
private val serverManager: ServerManager, private val serverManager: ServerManager,
private val favoritesDao: FavoritesDao,
private val entityStateComplicationsDao: EntityStateComplicationsDao private val entityStateComplicationsDao: EntityStateComplicationsDao
) : AndroidViewModel(application) { ) : AndroidViewModel(application) {
companion object { companion object {
@ -55,11 +56,28 @@ class ComplicationConfigViewModel @Inject constructor(
private set private set
var selectedEntity: SimplifiedEntity? by mutableStateOf(null) var selectedEntity: SimplifiedEntity? by mutableStateOf(null)
private set private set
var entityShowTitle by mutableStateOf(true)
private set
init { init {
loadEntities() loadEntities()
} }
fun setDataFromIntent(id: Int) {
viewModelScope.launch {
if (!serverManager.isRegistered() || id <= 0) return@launch
val stored = entityStateComplicationsDao.get(id)
stored?.let {
selectedEntity = SimplifiedEntity(entityId = it.entityId)
entityShowTitle = it.showTitle
if (loadingState == LoadingState.READY) {
updateSelectedEntity()
}
}
}
}
private fun loadEntities() { private fun loadEntities() {
viewModelScope.launch { viewModelScope.launch {
if (!serverManager.isRegistered()) { if (!serverManager.isRegistered()) {
@ -73,6 +91,7 @@ class ComplicationConfigViewModel @Inject constructor(
entities[it.entityId] = it entities[it.entityId] = it
} }
updateEntityDomains() updateEntityDomains()
updateSelectedEntity()
// Finished initial load, update state // Finished initial load, update state
val webSocketState = serverManager.webSocketRepository().getConnectionState() val webSocketState = serverManager.webSocketRepository().getConnectionState()
@ -111,13 +130,32 @@ class ComplicationConfigViewModel @Inject constructor(
entitiesByDomainOrder.addAll(domainsList) entitiesByDomainOrder.addAll(domainsList)
} }
private fun updateSelectedEntity() {
if (selectedEntity == null) return
val fullEntity = entities[selectedEntity!!.entityId]
selectedEntity = if (fullEntity == null) {
null // Clear invalid value
} else {
SimplifiedEntity(
entityId = fullEntity.entityId,
friendlyName = fullEntity.friendlyName,
icon = (fullEntity.attributes as? Map<*, *>)?.get("icon") as? String ?: ""
)
}
}
fun setEntity(entity: SimplifiedEntity) { fun setEntity(entity: SimplifiedEntity) {
selectedEntity = entity selectedEntity = entity
} }
fun setShowTitle(show: Boolean) {
entityShowTitle = show
}
fun addEntityStateComplication(id: Int, entity: SimplifiedEntity) { fun addEntityStateComplication(id: Int, entity: SimplifiedEntity) {
viewModelScope.launch { viewModelScope.launch {
entityStateComplicationsDao.add(EntityStateComplications(id, entity.entityId)) entityStateComplicationsDao.add(EntityStateComplications(id, entity.entityId, entityShowTitle))
} }
} }

View File

@ -5,6 +5,7 @@ import android.content.BroadcastReceiver
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import androidx.wear.watchface.complications.datasource.ComplicationDataSourceService.Companion.EXTRA_CONFIG_COMPLICATION_ID
import androidx.wear.watchface.complications.datasource.ComplicationDataSourceUpdateRequester import androidx.wear.watchface.complications.datasource.ComplicationDataSourceUpdateRequester
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import io.homeassistant.companion.android.common.data.servers.ServerManager import io.homeassistant.companion.android.common.data.servers.ServerManager
@ -92,6 +93,20 @@ class ComplicationReceiver : BroadcastReceiver() {
) )
} }
fun getComplicationConfigureIntent(
context: Context,
complicationInstanceId: Int
): PendingIntent {
return PendingIntent.getActivity(
context,
complicationInstanceId,
Intent(context, ComplicationConfigActivity::class.java).apply {
putExtra(EXTRA_CONFIG_COMPLICATION_ID, complicationInstanceId)
},
PendingIntent.FLAG_IMMUTABLE
)
}
fun getAssistIntent(context: Context): PendingIntent { fun getAssistIntent(context: Context): PendingIntent {
return PendingIntent.getActivity( return PendingIntent.getActivity(
context, context,

View File

@ -3,8 +3,10 @@ package io.homeassistant.companion.android.complications
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.Icon import android.graphics.drawable.Icon
import android.util.Log import android.util.Log
import androidx.annotation.StringRes
import androidx.wear.watchface.complications.data.ComplicationData import androidx.wear.watchface.complications.data.ComplicationData
import androidx.wear.watchface.complications.data.ComplicationType import androidx.wear.watchface.complications.data.ComplicationType
import androidx.wear.watchface.complications.data.LongTextComplicationData
import androidx.wear.watchface.complications.data.MonochromaticImage import androidx.wear.watchface.complications.data.MonochromaticImage
import androidx.wear.watchface.complications.data.PlainComplicationText import androidx.wear.watchface.complications.data.PlainComplicationText
import androidx.wear.watchface.complications.data.ShortTextComplicationData import androidx.wear.watchface.complications.data.ShortTextComplicationData
@ -15,9 +17,12 @@ import com.mikepenz.iconics.typeface.library.community.material.CommunityMateria
import com.mikepenz.iconics.utils.colorInt import com.mikepenz.iconics.utils.colorInt
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import io.homeassistant.companion.android.common.R import io.homeassistant.companion.android.common.R
import io.homeassistant.companion.android.common.data.integration.friendlyName
import io.homeassistant.companion.android.common.data.integration.friendlyState
import io.homeassistant.companion.android.common.data.integration.getIcon import io.homeassistant.companion.android.common.data.integration.getIcon
import io.homeassistant.companion.android.common.data.servers.ServerManager import io.homeassistant.companion.android.common.data.servers.ServerManager
import io.homeassistant.companion.android.database.wear.EntityStateComplicationsDao import io.homeassistant.companion.android.database.wear.EntityStateComplicationsDao
import retrofit2.HttpException
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@ -34,60 +39,131 @@ class EntityStateDataSourceService : SuspendingComplicationDataSourceService() {
} }
override suspend fun onComplicationRequest(request: ComplicationRequest): ComplicationData? { override suspend fun onComplicationRequest(request: ComplicationRequest): ComplicationData? {
if (request.complicationType != ComplicationType.SHORT_TEXT) { if (request.complicationType != ComplicationType.SHORT_TEXT && request.complicationType != ComplicationType.LONG_TEXT) {
return null return null
} }
val id = request.complicationInstanceId val settings = entityStateComplicationsDao.get(request.complicationInstanceId)
val entityId = settings?.entityId
val entityId = entityStateComplicationsDao.get(id)?.entityId ?: return getErrorComplication(request, R.string.complication_entity_invalid, true)
?: return ShortTextComplicationData.Builder(
text = PlainComplicationText.Builder(getText(R.string.complication_entity_invalid)).build(),
contentDescription = PlainComplicationText.Builder(getText(R.string.complication_entity_state_content_description))
.build()
).build()
val entity = try { val entity = try {
serverManager.integrationRepository().getEntity(entityId) serverManager.integrationRepository().getEntity(entityId)
?: return ShortTextComplicationData.Builder( ?: return getErrorComplication(request, R.string.state_unknown)
text = PlainComplicationText.Builder(getText(R.string.state_unknown)).build(),
contentDescription = PlainComplicationText.Builder(getText(R.string.complication_entity_state_content_description))
.build()
).build()
} catch (t: Throwable) { } catch (t: Throwable) {
Log.e(TAG, "Unable to get entity state for $entityId: ${t.message}") Log.e(TAG, "Unable to get entity state for $entityId: ${t.message}")
return null return if (t is HttpException && t.code() == 404) {
getErrorComplication(request, R.string.complication_entity_invalid)
} else {
null
}
} }
val attributes = entity.attributes as Map<*, *>
val icon = entity.getIcon(applicationContext) ?: CommunityMaterial.Icon.cmd_bookmark val icon = entity.getIcon(applicationContext) ?: CommunityMaterial.Icon.cmd_bookmark
val iconBitmap = IconicsDrawable(this, icon).apply { val iconBitmap = IconicsDrawable(this, icon).apply {
colorInt = Color.WHITE colorInt = Color.WHITE
}.toBitmap() }.toBitmap()
return ShortTextComplicationData.Builder(
text = PlainComplicationText.Builder(entity.state).build(), val title = if (settings.showTitle) {
contentDescription = PlainComplicationText.Builder(getText(R.string.complication_entity_state_content_description)) PlainComplicationText.Builder(entity.friendlyName).build()
.build() } else {
) null
.setTapAction(ComplicationReceiver.getComplicationToggleIntent(this, request.complicationInstanceId)) }
.setMonochromaticImage(MonochromaticImage.Builder(Icon.createWithBitmap(iconBitmap)).build()) val text = PlainComplicationText.Builder(entity.friendlyState(this)).build()
.setTitle(PlainComplicationText.Builder(attributes["friendly_name"] as String? ?: entity.entityId).build()) val contentDescription = PlainComplicationText.Builder(getText(R.string.complication_entity_state_content_description)).build()
.build() val monochromaticImage = MonochromaticImage.Builder(Icon.createWithBitmap(iconBitmap)).build()
val tapAction = ComplicationReceiver.getComplicationToggleIntent(this, request.complicationInstanceId)
return when (request.complicationType) {
ComplicationType.SHORT_TEXT -> {
ShortTextComplicationData.Builder(
text = text,
contentDescription = contentDescription
)
.setTitle(title)
.setTapAction(tapAction)
.setMonochromaticImage(monochromaticImage)
.build()
}
ComplicationType.LONG_TEXT -> {
LongTextComplicationData.Builder(
text = text,
contentDescription = contentDescription
)
.setTitle(title)
.setTapAction(tapAction)
.setMonochromaticImage(monochromaticImage)
.build()
}
else -> null // Already handled at the start of the function
}
} }
override fun getPreviewData(type: ComplicationType): ComplicationData = override fun getPreviewData(type: ComplicationType): ComplicationData? {
ShortTextComplicationData.Builder( val text = PlainComplicationText.Builder(getText(R.string.complication_entity_state_preview)).build()
text = PlainComplicationText.Builder(getText(R.string.complication_entity_state_preview)).build(), val contentDescription = PlainComplicationText.Builder(getText(R.string.complication_entity_state_content_description)).build()
contentDescription = PlainComplicationText.Builder(getText(R.string.complication_entity_state_content_description)).build() val title = PlainComplicationText.Builder(getText(R.string.entity)).build()
) val monochromaticImage = MonochromaticImage.Builder(
.setMonochromaticImage( Icon.createWithResource(
MonochromaticImage.Builder( this,
Icon.createWithResource( io.homeassistant.companion.android.R.drawable.ic_lightbulb
this,
io.homeassistant.companion.android.R.drawable.ic_lightbulb
)
).build()
) )
.setTitle(PlainComplicationText.Builder(getText(R.string.entity)).build()) ).build()
.build() return when (type) {
ComplicationType.SHORT_TEXT -> {
ShortTextComplicationData.Builder(
text = text,
contentDescription = contentDescription
)
.setTitle(title)
.setMonochromaticImage(monochromaticImage)
.build()
}
ComplicationType.LONG_TEXT -> {
LongTextComplicationData.Builder(
text = text,
contentDescription = contentDescription
)
.setTitle(title)
.setMonochromaticImage(monochromaticImage)
.build()
}
else -> {
Log.w(TAG, "Preview for unsupported complication type $type requested")
null
}
}
}
/**
* Get a simple complication for errors with [textRes] in the text slot.
*
* @param setTapAction If tapping the complication should open configuration
*/
private fun getErrorComplication(
request: ComplicationRequest,
@StringRes textRes: Int,
setTapAction: Boolean = false
): ComplicationData {
val text = PlainComplicationText.Builder(
if (setTapAction) { "+" } else { getText(textRes) }
).build()
val contentDescription = PlainComplicationText.Builder(getText(R.string.complication_entity_state_content_description)).build()
val tapAction = if (setTapAction) {
ComplicationReceiver.getComplicationConfigureIntent(this, request.complicationInstanceId)
} else {
null
}
return if (request.complicationType == ComplicationType.SHORT_TEXT) {
ShortTextComplicationData.Builder(
text = text,
contentDescription = contentDescription
).setTapAction(tapAction).build()
} else {
LongTextComplicationData.Builder(
text = text,
contentDescription = contentDescription
).setTapAction(tapAction).build()
}
}
} }

View File

@ -7,25 +7,29 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Devices
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.wear.compose.material.Button import androidx.wear.compose.material.Button
import androidx.wear.compose.material.ButtonDefaults import androidx.wear.compose.material.ButtonDefaults
import androidx.wear.compose.material.Chip import androidx.wear.compose.material.Chip
import androidx.wear.compose.material.ChipDefaults import androidx.wear.compose.material.ChipDefaults
import androidx.wear.compose.material.ExperimentalWearMaterialApi import androidx.wear.compose.material.Icon
import androidx.wear.compose.material.Text import androidx.wear.compose.material.Text
import androidx.wear.compose.material.ToggleChip
import androidx.wear.compose.material.ToggleChipDefaults
import androidx.wear.compose.navigation.SwipeDismissableNavHost import androidx.wear.compose.navigation.SwipeDismissableNavHost
import androidx.wear.compose.navigation.composable import androidx.wear.compose.navigation.composable
import androidx.wear.compose.navigation.rememberSwipeDismissableNavController import androidx.wear.compose.navigation.rememberSwipeDismissableNavController
import com.mikepenz.iconics.compose.Image import com.mikepenz.iconics.compose.Image
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import io.homeassistant.companion.android.HomeAssistantApplication
import io.homeassistant.companion.android.common.R import io.homeassistant.companion.android.common.R
import io.homeassistant.companion.android.complications.ComplicationConfigViewModel import io.homeassistant.companion.android.complications.ComplicationConfigViewModel
import io.homeassistant.companion.android.data.SimplifiedEntity import io.homeassistant.companion.android.data.SimplifiedEntity
import io.homeassistant.companion.android.theme.WearAppTheme import io.homeassistant.companion.android.theme.WearAppTheme
import io.homeassistant.companion.android.theme.wearColorPalette import io.homeassistant.companion.android.theme.wearColorPalette
import io.homeassistant.companion.android.util.getIcon import io.homeassistant.companion.android.util.getIcon
import io.homeassistant.companion.android.util.simplifiedEntity
import io.homeassistant.companion.android.views.ChooseEntityView import io.homeassistant.companion.android.views.ChooseEntityView
import io.homeassistant.companion.android.views.ListHeader import io.homeassistant.companion.android.views.ListHeader
import io.homeassistant.companion.android.views.ThemeLazyColumn import io.homeassistant.companion.android.views.ThemeLazyColumn
@ -33,7 +37,6 @@ import io.homeassistant.companion.android.views.ThemeLazyColumn
private const val SCREEN_MAIN = "main" private const val SCREEN_MAIN = "main"
private const val SCREEN_CHOOSE_ENTITY = "choose_entity" private const val SCREEN_CHOOSE_ENTITY = "choose_entity"
@OptIn(ExperimentalWearMaterialApi::class)
@Composable @Composable
fun LoadConfigView( fun LoadConfigView(
complicationConfigViewModel: ComplicationConfigViewModel, complicationConfigViewModel: ComplicationConfigViewModel,
@ -48,15 +51,16 @@ fun LoadConfigView(
composable(SCREEN_MAIN) { composable(SCREEN_MAIN) {
MainConfigView( MainConfigView(
entity = complicationConfigViewModel.selectedEntity, entity = complicationConfigViewModel.selectedEntity,
showTitle = complicationConfigViewModel.entityShowTitle,
loadingState = complicationConfigViewModel.loadingState, loadingState = complicationConfigViewModel.loadingState,
onChooseEntityClicked = { onChooseEntityClicked = {
swipeDismissableNavController.navigate(SCREEN_CHOOSE_ENTITY) swipeDismissableNavController.navigate(SCREEN_CHOOSE_ENTITY)
}, },
onShowTitleClicked = complicationConfigViewModel::setShowTitle,
onAcceptClicked = onAcceptClicked onAcceptClicked = onAcceptClicked
) )
} }
composable(SCREEN_CHOOSE_ENTITY) { composable(SCREEN_CHOOSE_ENTITY) {
val app = complicationConfigViewModel.getApplication<HomeAssistantApplication>()
ChooseEntityView( ChooseEntityView(
entitiesByDomainOrder = complicationConfigViewModel.entitiesByDomainOrder, entitiesByDomainOrder = complicationConfigViewModel.entitiesByDomainOrder,
entitiesByDomain = complicationConfigViewModel.entitiesByDomain, entitiesByDomain = complicationConfigViewModel.entitiesByDomain,
@ -76,8 +80,10 @@ fun LoadConfigView(
@Composable @Composable
fun MainConfigView( fun MainConfigView(
entity: SimplifiedEntity?, entity: SimplifiedEntity?,
showTitle: Boolean,
loadingState: ComplicationConfigViewModel.LoadingState, loadingState: ComplicationConfigViewModel.LoadingState,
onChooseEntityClicked: () -> Unit, onChooseEntityClicked: () -> Unit,
onShowTitleClicked: (Boolean) -> Unit,
onAcceptClicked: () -> Unit onAcceptClicked: () -> Unit
) { ) {
ThemeLazyColumn { ThemeLazyColumn {
@ -119,6 +125,26 @@ fun MainConfigView(
onClick = onChooseEntityClicked onClick = onChooseEntityClicked
) )
} }
item {
val isChecked = !loaded || showTitle
ToggleChip(
checked = isChecked,
onCheckedChange = onShowTitleClicked,
label = { Text(stringResource(R.string.show_entity_title)) },
toggleControl = {
Icon(
imageVector = ToggleChipDefaults.switchIcon(isChecked),
contentDescription = if (isChecked) {
stringResource(R.string.enabled)
} else {
stringResource(R.string.disabled)
}
)
},
modifier = Modifier.fillMaxWidth(),
enabled = loaded && entity != null
)
}
item { item {
Button( Button(
@ -139,3 +165,16 @@ fun MainConfigView(
} }
} }
} }
@Preview(device = Devices.WEAR_OS_LARGE_ROUND)
@Composable
fun PreviewMainConfigView() {
MainConfigView(
entity = simplifiedEntity,
showTitle = true,
loadingState = ComplicationConfigViewModel.LoadingState.READY,
onChooseEntityClicked = {},
onShowTitleClicked = {},
onAcceptClicked = {}
)
}