From 7e5e3adfe943e3f0efdb124c81cf7ce57bb25698 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joris=20Pelgr=C3=B6m?= Date: Sun, 18 Dec 2022 21:31:01 +0100 Subject: [PATCH] Use entity icon for quick settings tiles by default (#3160) - When creating a new quick setting tile, use the entity's icon by default - Move Wear icon based on entity domain code to common --- .../companion/android/qs/TileExtensions.kt | 72 +++++--- .../settings/qs/ManageTilesViewModel.kt | 34 +++- .../settings/qs/views/ManageTilesView.kt | 57 +++--- common/build.gradle.kts | 3 + .../android/common/data/integration/Entity.kt | 173 ++++++++++++++++++ common/src/main/res/values/strings.xml | 3 +- .../EntityStateDataSourceService.kt | 5 +- .../companion/android/home/views/EntityUi.kt | 4 +- .../android/home/views/SetFavoriteView.kt | 9 +- .../companion/android/util/CommonFunctions.kt | 155 +--------------- .../android/views/ChooseEntityView.kt | 10 +- 11 files changed, 302 insertions(+), 223 deletions(-) diff --git a/app/src/main/java/io/homeassistant/companion/android/qs/TileExtensions.kt b/app/src/main/java/io/homeassistant/companion/android/qs/TileExtensions.kt index 740752652..89fa651b3 100755 --- a/app/src/main/java/io/homeassistant/companion/android/qs/TileExtensions.kt +++ b/app/src/main/java/io/homeassistant/companion/android/qs/TileExtensions.kt @@ -18,12 +18,16 @@ import androidx.core.graphics.drawable.toBitmap import com.maltaisn.icondialog.pack.IconPack import com.maltaisn.icondialog.pack.IconPackLoader import com.maltaisn.iconpack.mdi.createMaterialDesignIconPack +import com.mikepenz.iconics.IconicsDrawable +import com.mikepenz.iconics.utils.sizeDp import dagger.hilt.EntryPoint import dagger.hilt.InstallIn import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.EntryPointAccessors import dagger.hilt.components.SingletonComponent +import io.homeassistant.companion.android.common.data.integration.Entity import io.homeassistant.companion.android.common.data.integration.IntegrationRepository +import io.homeassistant.companion.android.common.data.integration.getIcon import io.homeassistant.companion.android.database.qs.TileDao import io.homeassistant.companion.android.database.qs.TileEntity import io.homeassistant.companion.android.database.qs.isSetup @@ -100,6 +104,9 @@ abstract class TileExtensions : TileService() { if (tileData != null && tileData.isSetup && tileData.entityId.split('.')[0] in toggleDomainsWithLock) integrationUseCase.getEntityUpdates(listOf(tileData.entityId))?.collect { tile.state = if (it.state in validActiveStates) Tile.STATE_ACTIVE else Tile.STATE_INACTIVE + getTileIcon(tileData.iconId, it, applicationContext)?.let { icon -> + tile.icon = Icon.createWithBitmap(icon) + } tile.updateTile() } } @@ -127,20 +134,30 @@ abstract class TileExtensions : TileService() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { tile.subtitle = tileData.subtitle } + val state: Entity<*>? = + if ( + tileData.entityId.split(".")[0] in toggleDomainsWithLock || + tileData.iconId == null + ) { + withContext(Dispatchers.IO) { + try { + integrationUseCase.getEntity(tileData.entityId) + } catch (e: Exception) { + Log.e(TAG, "Unable to get state for tile", e) + null + } + } + } else null if (tileData.entityId.split('.')[0] in toggleDomainsWithLock) { - try { - val state = withContext(Dispatchers.IO) { integrationUseCase.getEntity(tileData.entityId) } - tile.state = if (state?.state in validActiveStates) Tile.STATE_ACTIVE else Tile.STATE_INACTIVE - } catch (e: Exception) { - Log.e(TAG, "Unable to set state for tile", e) - tile.state = Tile.STATE_UNAVAILABLE + tile.state = when { + state?.state in validActiveStates -> Tile.STATE_ACTIVE + state?.state != null && state.state !in validActiveStates -> Tile.STATE_INACTIVE + else -> Tile.STATE_UNAVAILABLE } } else tile.state = Tile.STATE_INACTIVE - val iconId = tileData.iconId - if (iconId != null) { - val icon = getTileIcon(iconId, context) + getTileIcon(tileData.iconId, state, context)?.let { icon -> tile.icon = Icon.createWithBitmap(icon) } Log.d(TAG, "Tile data set for tile ID: $tileId") @@ -264,18 +281,9 @@ abstract class TileExtensions : TileService() { } } - companion object { - private const val TAG = "TileExtensions" - private var iconPack: IconPack? = null - private val toggleDomains = listOf( - "automation", "cover", "fan", "humidifier", "input_boolean", "light", - "media_player", "remote", "siren", "switch" - ) - private val toggleDomainsWithLock = toggleDomains.plus("lock") - private val validActiveStates = listOf("on", "open", "locked") - - private fun getTileIcon(tileIconId: Int, context: Context): Bitmap? { - // Create an icon pack and load all drawables. + private fun getTileIcon(tileIconId: Int?, entity: Entity<*>?, context: Context): Bitmap? { + // Create an icon pack and load all drawables. + if (tileIconId != null) { if (iconPack == null) { val loader = IconPackLoader(context) iconPack = createMaterialDesignIconPack(loader) @@ -286,8 +294,28 @@ abstract class TileExtensions : TileService() { if (iconDrawable != null) { return DrawableCompat.wrap(iconDrawable).toBitmap() } - return null + } else { + entity?.getIcon(context)?.let { + return DrawableCompat.wrap( + IconicsDrawable(context, it).apply { + sizeDp = 48 + } + ).toBitmap() + } } + + return null + } + + companion object { + private const val TAG = "TileExtensions" + private var iconPack: IconPack? = null + private val toggleDomains = listOf( + "automation", "cover", "fan", "humidifier", "input_boolean", "light", + "media_player", "remote", "siren", "switch" + ) + private val toggleDomainsWithLock = toggleDomains.plus("lock") + private val validActiveStates = listOf("on", "open", "locked") } private fun handleInject() { diff --git a/app/src/main/java/io/homeassistant/companion/android/settings/qs/ManageTilesViewModel.kt b/app/src/main/java/io/homeassistant/companion/android/settings/qs/ManageTilesViewModel.kt index ce37faffb..fc221d130 100755 --- a/app/src/main/java/io/homeassistant/companion/android/settings/qs/ManageTilesViewModel.kt +++ b/app/src/main/java/io/homeassistant/companion/android/settings/qs/ManageTilesViewModel.kt @@ -19,10 +19,13 @@ import com.maltaisn.icondialog.data.Icon import com.maltaisn.icondialog.pack.IconPack import com.maltaisn.icondialog.pack.IconPackLoader import com.maltaisn.iconpack.mdi.createMaterialDesignIconPack +import com.mikepenz.iconics.IconicsDrawable +import com.mikepenz.iconics.utils.sizeDp import dagger.hilt.android.lifecycle.HiltViewModel import io.homeassistant.companion.android.common.data.integration.Entity import io.homeassistant.companion.android.common.data.integration.IntegrationRepository import io.homeassistant.companion.android.common.data.integration.domain +import io.homeassistant.companion.android.common.data.integration.getIcon import io.homeassistant.companion.android.database.qs.TileDao import io.homeassistant.companion.android.database.qs.TileEntity import io.homeassistant.companion.android.database.qs.isSetup @@ -70,6 +73,8 @@ class ManageTilesViewModel @Inject constructor( var sortedEntities by mutableStateOf>>(emptyList()) private set + var selectedIconId by mutableStateOf(null) + private set var selectedIconDrawable by mutableStateOf(AppCompatResources.getDrawable(application, commonR.drawable.ic_stat_ic_notification)) private set var selectedEntityId by mutableStateOf("") @@ -78,8 +83,6 @@ class ManageTilesViewModel @Inject constructor( var submitButtonLabel by mutableStateOf(commonR.string.tile_save) private set var selectedShouldVibrate by mutableStateOf(false) - - private var selectedIcon: Int? = null private var selectedTileId = 0 private var selectedTileAdded = false @@ -101,6 +104,10 @@ class ManageTilesViewModel @Inject constructor( viewModelScope.launch(Dispatchers.IO) { sortedEntities = integrationUseCase.getEntities().orEmpty() .filter { it.domain in ManageTilesFragment.validDomains } + withContext(Dispatchers.Main) { + // The entities list might not have been loaded when the tile data was loaded + selectTile(slots.indexOf(selectedTile)) + } } viewModelScope.launch(Dispatchers.IO) { @@ -132,9 +139,26 @@ class ManageTilesViewModel @Inject constructor( } } + fun selectEntityId(entityId: String) { + selectedEntityId = entityId + if (selectedIconId == null) selectIcon(null) // trigger drawable update + } + fun selectIcon(icon: Icon?) { - selectedIcon = icon?.id - selectedIconDrawable = icon?.drawable?.let { DrawableCompat.wrap(it) } + selectedIconId = icon?.id + selectedIconDrawable = if (icon != null) { + icon.drawable?.let { DrawableCompat.wrap(it) } + } else { + sortedEntities.firstOrNull { it.entityId == selectedEntityId }?.let { + it.getIcon(app)?.let { iIcon -> + DrawableCompat.wrap( + IconicsDrawable(app, iIcon).apply { + sizeDp = 20 + } + ) + } + } + } } private fun updateExistingTileFields(currentTile: TileEntity) { @@ -156,7 +180,7 @@ class ManageTilesViewModel @Inject constructor( id = selectedTileId, tileId = selectedTile.id, added = selectedTileAdded, - iconId = selectedIcon, + iconId = selectedIconId, entityId = selectedEntityId, label = tileLabel, subtitle = tileSubtitle, diff --git a/app/src/main/java/io/homeassistant/companion/android/settings/qs/views/ManageTilesView.kt b/app/src/main/java/io/homeassistant/companion/android/settings/qs/views/ManageTilesView.kt index 9d7de5397..e3c792c2a 100755 --- a/app/src/main/java/io/homeassistant/companion/android/settings/qs/views/ManageTilesView.kt +++ b/app/src/main/java/io/homeassistant/companion/android/settings/qs/views/ManageTilesView.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.Button @@ -18,6 +19,7 @@ import androidx.compose.material.Scaffold import androidx.compose.material.Switch import androidx.compose.material.SwitchDefaults import androidx.compose.material.Text +import androidx.compose.material.TextButton import androidx.compose.material.TextField import androidx.compose.material.rememberScaffoldState import androidx.compose.runtime.Composable @@ -116,28 +118,6 @@ fun ManageTilesView( ) } - Row(verticalAlignment = Alignment.CenterVertically) { - Text( - text = stringResource(id = R.string.tile_icon), - fontSize = 15.sp, - modifier = Modifier.padding(end = 10.dp) - ) - OutlinedButton( - onClick = { onShowIconDialog(viewModel.selectedTile.id) } - ) { - val iconBitmap = remember(viewModel.selectedIconDrawable) { - viewModel.selectedIconDrawable?.toBitmap()?.asImageBitmap() - } - iconBitmap?.let { - Image( - iconBitmap, - contentDescription = stringResource(id = R.string.tile_icon), - colorFilter = ColorFilter.tint(colorResource(R.color.colorAccent)) - ) - } - } - } - Text( text = stringResource(id = R.string.tile_entity), fontSize = 15.sp @@ -149,7 +129,7 @@ fun ManageTilesView( DropdownMenu(expanded = expandedEntity, onDismissRequest = { expandedEntity = false }) { for (item in viewModel.sortedEntities) { DropdownMenuItem(onClick = { - viewModel.selectedEntityId = item.entityId + viewModel.selectEntityId(item.entityId) expandedEntity = false }) { Text(text = item.entityId, fontSize = 15.sp) @@ -157,6 +137,37 @@ fun ManageTilesView( } } + Row(verticalAlignment = Alignment.CenterVertically) { + Text( + text = stringResource(id = R.string.tile_icon), + fontSize = 15.sp, + modifier = Modifier.padding(end = 8.dp) + ) + OutlinedButton( + onClick = { onShowIconDialog(viewModel.selectedTile.id) } + ) { + val iconBitmap = remember(viewModel.selectedIconDrawable) { + viewModel.selectedIconDrawable?.toBitmap()?.asImageBitmap() + } + iconBitmap?.let { + Image( + iconBitmap, + contentDescription = stringResource(id = R.string.tile_icon), + colorFilter = ColorFilter.tint(colorResource(R.color.colorAccent)), + modifier = Modifier.size(20.dp) + ) + } + } + if (viewModel.selectedIconId != null && viewModel.selectedEntityId.isNotBlank()) { + TextButton( + modifier = Modifier.padding(start = 4.dp), + onClick = { viewModel.selectIcon(null) } + ) { + Text(text = stringResource(R.string.tile_icon_original)) + } + } + } + Row(verticalAlignment = Alignment.CenterVertically) { Text( text = stringResource(R.string.tile_vibrate), diff --git a/common/build.gradle.kts b/common/build.gradle.kts index d99059cfa..0580489b8 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -74,4 +74,7 @@ dependencies { implementation("com.squareup.okhttp3:logging-interceptor:4.10.0") implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.4") implementation("org.altbeacon:android-beacon-library:2.19.4") + + implementation("com.mikepenz:iconics-core:5.4.0") + implementation("com.mikepenz:community-material-typeface:7.0.96.0-kotlin@aar") } diff --git a/common/src/main/java/io/homeassistant/companion/android/common/data/integration/Entity.kt b/common/src/main/java/io/homeassistant/companion/android/common/data/integration/Entity.kt index 16a2451c6..fbd0be6d1 100644 --- a/common/src/main/java/io/homeassistant/companion/android/common/data/integration/Entity.kt +++ b/common/src/main/java/io/homeassistant/companion/android/common/data/integration/Entity.kt @@ -1,7 +1,11 @@ package io.homeassistant.companion.android.common.data.integration +import android.content.Context import android.graphics.Color import android.util.Log +import com.mikepenz.iconics.IconicsDrawable +import com.mikepenz.iconics.typeface.IIcon +import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial import io.homeassistant.companion.android.common.data.websocket.impl.entities.CompressedStateDiff import java.util.Calendar import kotlin.math.round @@ -222,3 +226,172 @@ fun Entity.getLightColor(): Int? { null } } + +fun Entity.getIcon(context: Context): IIcon? { + val attributes = this.attributes as Map + val icon = attributes["icon"] as? String + return if (icon?.startsWith("mdi") == true) { + val mdiIcon = icon.split(":")[1] + return IconicsDrawable(context, "cmd-$mdiIcon").icon ?: CommunityMaterial.Icon.cmd_bookmark + } else { + /** + * Return a default icon for the domain that matches the icon used in the frontend, see + * https://github.com/home-assistant/frontend/blob/dev/src/common/entity/domain_icon.ts. + * Note: for SimplifiedEntity sometimes return a more general icon because we don't have state. + */ + val compareState = + state.ifBlank { attributes["state"] as String? } + when (domain) { + "alert" -> CommunityMaterial.Icon.cmd_alert + "air_quality" -> CommunityMaterial.Icon.cmd_air_filter + "automation" -> CommunityMaterial.Icon3.cmd_robot + "button" -> when (attributes["device_class"]) { + "restart" -> CommunityMaterial.Icon3.cmd_restart + "update" -> CommunityMaterial.Icon3.cmd_package_up + else -> CommunityMaterial.Icon2.cmd_gesture_tap_button + } + "calendar" -> CommunityMaterial.Icon.cmd_calendar + "camera" -> CommunityMaterial.Icon3.cmd_video + "climate" -> CommunityMaterial.Icon3.cmd_thermostat + "configurator" -> CommunityMaterial.Icon.cmd_cog + "conversation" -> CommunityMaterial.Icon3.cmd_microphone_message + "cover" -> coverIcon(compareState, this as Entity>) + "counter" -> CommunityMaterial.Icon.cmd_counter + "fan" -> CommunityMaterial.Icon2.cmd_fan + "google_assistant" -> CommunityMaterial.Icon2.cmd_google_assistant + "group" -> CommunityMaterial.Icon2.cmd_google_circles_communities + "homeassistant" -> CommunityMaterial.Icon2.cmd_home_assistant + "homekit" -> CommunityMaterial.Icon2.cmd_home_automation + "humidifier" -> if (compareState == "off") + CommunityMaterial.Icon.cmd_air_humidifier_off + else + CommunityMaterial.Icon.cmd_air_humidifier + "image_processing" -> CommunityMaterial.Icon2.cmd_image_filter_frames + "input_boolean" -> if (!entityId.endsWith(".ha_android_placeholder")) { + if (compareState == "on") + CommunityMaterial.Icon.cmd_check_circle_outline + else + CommunityMaterial.Icon.cmd_close_circle_outline + } else { // For SimplifiedEntity without state, use a more generic icon + CommunityMaterial.Icon3.cmd_toggle_switch_outline + } + "input_button" -> CommunityMaterial.Icon2.cmd_gesture_tap_button + "input_datetime" -> if (attributes["has_date"] == false) + CommunityMaterial.Icon.cmd_clock + else if (attributes["has_time"] == false) + CommunityMaterial.Icon.cmd_calendar + else + CommunityMaterial.Icon.cmd_calendar_clock + "input_select" -> CommunityMaterial.Icon2.cmd_form_dropdown + "input_text" -> CommunityMaterial.Icon2.cmd_form_textbox + "light" -> CommunityMaterial.Icon2.cmd_lightbulb + "lock" -> when (compareState) { + "unlocked" -> CommunityMaterial.Icon2.cmd_lock_open + "jammed" -> CommunityMaterial.Icon2.cmd_lock_alert + "locking", "unlocking" -> CommunityMaterial.Icon2.cmd_lock_clock + else -> CommunityMaterial.Icon2.cmd_lock + } + "mailbox" -> CommunityMaterial.Icon3.cmd_mailbox + "media_player" -> when (attributes["device_class"]) { + "speaker" -> when (compareState) { + "playing" -> CommunityMaterial.Icon3.cmd_speaker_play + "paused" -> CommunityMaterial.Icon3.cmd_speaker_pause + "off" -> CommunityMaterial.Icon3.cmd_speaker_off + else -> CommunityMaterial.Icon3.cmd_speaker + } + "tv" -> when (compareState) { + "playing" -> CommunityMaterial.Icon3.cmd_television_play + "paused" -> CommunityMaterial.Icon3.cmd_television_pause + "off" -> CommunityMaterial.Icon3.cmd_television_off + else -> CommunityMaterial.Icon3.cmd_television + } + "receiver" -> when (compareState) { + "off" -> CommunityMaterial.Icon.cmd_audio_video_off + else -> CommunityMaterial.Icon.cmd_audio_video + } + else -> when (compareState) { + "playing", "paused" -> CommunityMaterial.Icon.cmd_cast_connected + "off" -> CommunityMaterial.Icon.cmd_cast_off + else -> CommunityMaterial.Icon.cmd_cast + } + } + "notify" -> CommunityMaterial.Icon.cmd_comment_alert + "number" -> CommunityMaterial.Icon3.cmd_ray_vertex + "persistent_notification" -> CommunityMaterial.Icon.cmd_bell + "person" -> CommunityMaterial.Icon.cmd_account + "plant" -> CommunityMaterial.Icon2.cmd_flower + "proximity" -> CommunityMaterial.Icon.cmd_apple_safari + "remote" -> CommunityMaterial.Icon3.cmd_remote + "scene" -> CommunityMaterial.Icon3.cmd_palette_outline // Different from frontend: outline version + "script" -> CommunityMaterial.Icon3.cmd_script_text_outline // Different from frontend: outline version + "select" -> CommunityMaterial.Icon2.cmd_format_list_bulleted + "sensor" -> CommunityMaterial.Icon.cmd_eye + "siren" -> CommunityMaterial.Icon.cmd_bullhorn + "simple_alarm" -> CommunityMaterial.Icon.cmd_bell + "sun" -> if (compareState == "above_horizon") + CommunityMaterial.Icon3.cmd_white_balance_sunny + else + CommunityMaterial.Icon3.cmd_weather_night + "switch" -> if (!entityId.endsWith(".ha_android_placeholder")) { + when (attributes["device_class"]) { + "outlet" -> if (compareState == "on") CommunityMaterial.Icon3.cmd_power_plug else CommunityMaterial.Icon3.cmd_power_plug_off + "switch" -> if (compareState == "on") CommunityMaterial.Icon3.cmd_toggle_switch else CommunityMaterial.Icon3.cmd_toggle_switch_off + else -> CommunityMaterial.Icon2.cmd_flash + } + } else { // For SimplifiedEntity without state, use a more generic icon + CommunityMaterial.Icon2.cmd_light_switch + } + "timer" -> CommunityMaterial.Icon3.cmd_timer_outline + "updater" -> CommunityMaterial.Icon.cmd_cloud_upload + "vacuum" -> CommunityMaterial.Icon3.cmd_robot_vacuum + "water_heater" -> CommunityMaterial.Icon3.cmd_thermometer + "weather" -> CommunityMaterial.Icon3.cmd_weather_cloudy + "zone" -> CommunityMaterial.Icon3.cmd_map_marker_radius + else -> CommunityMaterial.Icon.cmd_bookmark + } + } +} + +private fun coverIcon(state: String?, entity: Entity>): IIcon { + val open = state !== "closed" + + return when (entity.attributes?.get("device_class")) { + "garage" -> when (state) { + "opening" -> CommunityMaterial.Icon.cmd_arrow_up_box + "closing" -> CommunityMaterial.Icon.cmd_arrow_down_box + "closed" -> CommunityMaterial.Icon2.cmd_garage + else -> CommunityMaterial.Icon2.cmd_garage_open + } + "gate" -> when (state) { + "opening", "closing" -> CommunityMaterial.Icon2.cmd_gate_arrow_right + "closed" -> CommunityMaterial.Icon2.cmd_gate + else -> CommunityMaterial.Icon2.cmd_gate_open + } + "door" -> if (open) CommunityMaterial.Icon.cmd_door_open else CommunityMaterial.Icon.cmd_door_closed + "damper" -> if (open) CommunityMaterial.Icon.cmd_circle else CommunityMaterial.Icon.cmd_circle_slice_8 + "shutter" -> when (state) { + "opening" -> CommunityMaterial.Icon.cmd_arrow_up_box + "closing" -> CommunityMaterial.Icon.cmd_arrow_down_box + "closed" -> CommunityMaterial.Icon3.cmd_window_shutter + else -> CommunityMaterial.Icon3.cmd_window_shutter_open + } + "curtain" -> when (state) { + "opening" -> CommunityMaterial.Icon.cmd_arrow_split_vertical + "closing" -> CommunityMaterial.Icon.cmd_arrow_collapse_horizontal + "closed" -> CommunityMaterial.Icon.cmd_curtains_closed + else -> CommunityMaterial.Icon.cmd_curtains + } + "blind", "shade" -> when (state) { + "opening" -> CommunityMaterial.Icon.cmd_arrow_up_box + "closing" -> CommunityMaterial.Icon.cmd_arrow_down_box + "closed" -> CommunityMaterial.Icon.cmd_blinds + else -> CommunityMaterial.Icon.cmd_blinds_open + } + else -> when (state) { + "opening" -> CommunityMaterial.Icon.cmd_arrow_up_box + "closing" -> CommunityMaterial.Icon.cmd_arrow_down_box + "closed" -> CommunityMaterial.Icon3.cmd_window_closed + else -> CommunityMaterial.Icon3.cmd_window_open + } + } +} diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index 999d802b9..4476d6629 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -875,7 +875,8 @@ Information Show Change Log Show the change log dialog from when the app was updated - Select a icon for the tile + Tile Icon + Use entity icon Select a tile to edit Pinned Shortcuts WebView Remote Debugging diff --git a/wear/src/main/java/io/homeassistant/companion/android/complications/EntityStateDataSourceService.kt b/wear/src/main/java/io/homeassistant/companion/android/complications/EntityStateDataSourceService.kt index 92811682f..db9f8d5a2 100644 --- a/wear/src/main/java/io/homeassistant/companion/android/complications/EntityStateDataSourceService.kt +++ b/wear/src/main/java/io/homeassistant/companion/android/complications/EntityStateDataSourceService.kt @@ -16,9 +16,8 @@ import com.mikepenz.iconics.utils.colorInt import dagger.hilt.android.AndroidEntryPoint import io.homeassistant.companion.android.common.R import io.homeassistant.companion.android.common.data.integration.IntegrationRepository -import io.homeassistant.companion.android.common.data.integration.domain +import io.homeassistant.companion.android.common.data.integration.getIcon import io.homeassistant.companion.android.database.wear.EntityStateComplicationsDao -import io.homeassistant.companion.android.util.getIcon import javax.inject.Inject @AndroidEntryPoint @@ -59,7 +58,7 @@ class EntityStateDataSourceService : SuspendingComplicationDataSourceService() { } val attributes = entity.attributes as Map<*, *> - val icon = getIcon(entity, entity.domain, applicationContext) ?: CommunityMaterial.Icon.cmd_bookmark + val icon = entity.getIcon(applicationContext) ?: CommunityMaterial.Icon.cmd_bookmark val iconBitmap = IconicsDrawable(this, icon).apply { colorInt = Color.WHITE }.toBitmap() diff --git a/wear/src/main/java/io/homeassistant/companion/android/home/views/EntityUi.kt b/wear/src/main/java/io/homeassistant/companion/android/home/views/EntityUi.kt index 826cb0491..12482f862 100644 --- a/wear/src/main/java/io/homeassistant/companion/android/home/views/EntityUi.kt +++ b/wear/src/main/java/io/homeassistant/companion/android/home/views/EntityUi.kt @@ -24,10 +24,10 @@ import com.mikepenz.iconics.typeface.library.community.material.CommunityMateria import io.homeassistant.companion.android.common.R 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.getIcon import io.homeassistant.companion.android.home.HomePresenterImpl import io.homeassistant.companion.android.theme.wearColorPalette import io.homeassistant.companion.android.util.WearToggleChip -import io.homeassistant.companion.android.util.getIcon import io.homeassistant.companion.android.util.onEntityClickedFeedback import io.homeassistant.companion.android.util.previewEntity1 import io.homeassistant.companion.android.util.previewEntity3 @@ -43,7 +43,7 @@ fun EntityUi( val haptic = LocalHapticFeedback.current val context = LocalContext.current val attributes = entity.attributes as Map<*, *> - val iconBitmap = getIcon(entity as Entity>, entity.domain, LocalContext.current) + val iconBitmap = entity.getIcon(LocalContext.current) val friendlyName = attributes["friendly_name"].toString() if (entity.domain in HomePresenterImpl.toggleDomains) { diff --git a/wear/src/main/java/io/homeassistant/companion/android/home/views/SetFavoriteView.kt b/wear/src/main/java/io/homeassistant/companion/android/home/views/SetFavoriteView.kt index dcbc73a74..c523a858e 100644 --- a/wear/src/main/java/io/homeassistant/companion/android/home/views/SetFavoriteView.kt +++ b/wear/src/main/java/io/homeassistant/companion/android/home/views/SetFavoriteView.kt @@ -19,11 +19,10 @@ import androidx.wear.compose.material.rememberScalingLazyListState import com.mikepenz.iconics.compose.Image import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial 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.getIcon import io.homeassistant.companion.android.home.MainViewModel import io.homeassistant.companion.android.theme.WearAppTheme import io.homeassistant.companion.android.theme.wearColorPalette -import io.homeassistant.companion.android.util.getIcon import io.homeassistant.companion.android.views.ExpandableListHeader import io.homeassistant.companion.android.views.ListHeader import io.homeassistant.companion.android.views.ThemeLazyColumn @@ -88,11 +87,7 @@ private fun FavoriteToggleChip( onFavoriteSelected: (entityId: String, isSelected: Boolean) -> Unit ) { val attributes = entity.attributes as Map<*, *> - val iconBitmap = getIcon( - entity as Entity>, - entity.domain, - LocalContext.current - ) + val iconBitmap = entity.getIcon(LocalContext.current) val entityId = entity.entityId val checked = favoriteEntityIds.contains(entityId) diff --git a/wear/src/main/java/io/homeassistant/companion/android/util/CommonFunctions.kt b/wear/src/main/java/io/homeassistant/companion/android/util/CommonFunctions.kt index 17bb7989c..ccb37f557 100755 --- a/wear/src/main/java/io/homeassistant/companion/android/util/CommonFunctions.kt +++ b/wear/src/main/java/io/homeassistant/companion/android/util/CommonFunctions.kt @@ -4,10 +4,9 @@ import android.content.Context import android.widget.Toast import androidx.compose.ui.hapticfeedback.HapticFeedback import androidx.compose.ui.hapticfeedback.HapticFeedbackType -import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.typeface.IIcon -import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial import io.homeassistant.companion.android.common.data.integration.Entity +import io.homeassistant.companion.android.common.data.integration.getIcon import io.homeassistant.companion.android.home.HomePresenterImpl import java.util.Calendar import io.homeassistant.companion.android.common.R as commonR @@ -33,162 +32,14 @@ fun stringForDomain(domain: String, context: Context): String? = fun getIcon(icon: String?, domain: String, context: Context): IIcon? { val simpleEntity = Entity( - "", + "$domain.ha_android_placeholder", "", mapOf("icon" to icon), Calendar.getInstance(), Calendar.getInstance(), null ) - return getIcon(simpleEntity as Entity>, domain, context) -} - -fun getIcon(entity: Entity>?, domain: String, context: Context): IIcon? { - val icon = entity?.attributes?.get("icon") as? String - return if (icon?.startsWith("mdi") == true) { - getIconFromMdiString(icon, context) - } else { - /** - * Return a default icon for the domain that matches the icon used in the frontend, see - * https://github.com/home-assistant/frontend/blob/dev/src/common/entity/domain_icon.ts. - * Note: for SimplifiedEntity sometimes return a more general icon because we don't have state. - */ - val compareState = - if (entity?.state?.isNotBlank() == true) - entity.state - else - entity?.attributes?.get("state") as String? - when (domain) { - "alert" -> CommunityMaterial.Icon.cmd_alert - "air_quality" -> CommunityMaterial.Icon.cmd_air_filter - "automation" -> CommunityMaterial.Icon3.cmd_robot - "button" -> when (entity?.attributes?.get("device_class")) { - "restart" -> CommunityMaterial.Icon3.cmd_restart - "update" -> CommunityMaterial.Icon3.cmd_package_up - else -> CommunityMaterial.Icon2.cmd_gesture_tap_button - } - "calendar" -> CommunityMaterial.Icon.cmd_calendar - "camera" -> CommunityMaterial.Icon3.cmd_video - "climate" -> CommunityMaterial.Icon3.cmd_thermostat - "configurator" -> CommunityMaterial.Icon.cmd_cog - "conversation" -> CommunityMaterial.Icon3.cmd_microphone_message - "cover" -> coverIcon(compareState, entity) - "counter" -> CommunityMaterial.Icon.cmd_counter - "fan" -> CommunityMaterial.Icon2.cmd_fan - "google_assistant" -> CommunityMaterial.Icon2.cmd_google_assistant - "group" -> CommunityMaterial.Icon2.cmd_google_circles_communities - "homeassistant" -> CommunityMaterial.Icon2.cmd_home_assistant - "homekit" -> CommunityMaterial.Icon2.cmd_home_automation - "image_processing" -> CommunityMaterial.Icon2.cmd_image_filter_frames - "input_boolean" -> if (entity?.entityId?.isNotBlank() == true) { - if (compareState == "on") - CommunityMaterial.Icon.cmd_check_circle_outline - else - CommunityMaterial.Icon.cmd_close_circle_outline - } else { // For SimplifiedEntity without state, use a more generic icon - CommunityMaterial.Icon2.cmd_light_switch - } - "input_button" -> CommunityMaterial.Icon2.cmd_gesture_tap_button - "input_datetime" -> if (entity?.attributes?.get("has_date") == false) - CommunityMaterial.Icon.cmd_clock - else if (entity?.attributes?.get("has_time") == false) - CommunityMaterial.Icon.cmd_calendar - else - CommunityMaterial.Icon.cmd_calendar_clock - "input_select" -> CommunityMaterial.Icon2.cmd_format_list_bulleted - "input_text" -> CommunityMaterial.Icon2.cmd_form_textbox - "light" -> CommunityMaterial.Icon2.cmd_lightbulb - "lock" -> when (compareState) { - "unlocked" -> CommunityMaterial.Icon2.cmd_lock_open - "jammed" -> CommunityMaterial.Icon2.cmd_lock_alert - "locking", "unlocking" -> CommunityMaterial.Icon2.cmd_lock_clock - else -> CommunityMaterial.Icon2.cmd_lock - } - "mailbox" -> CommunityMaterial.Icon3.cmd_mailbox - "notify" -> CommunityMaterial.Icon.cmd_comment_alert - "number" -> CommunityMaterial.Icon3.cmd_ray_vertex - "persistent_notification" -> CommunityMaterial.Icon.cmd_bell - "person" -> CommunityMaterial.Icon.cmd_account - "plant" -> CommunityMaterial.Icon2.cmd_flower - "proximity" -> CommunityMaterial.Icon.cmd_apple_safari - "remote" -> CommunityMaterial.Icon3.cmd_remote - "scene" -> CommunityMaterial.Icon3.cmd_palette_outline // Different from frontend: outline version - "script" -> CommunityMaterial.Icon3.cmd_script_text_outline // Different from frontend: outline version - "select" -> CommunityMaterial.Icon2.cmd_format_list_bulleted - "sensor" -> CommunityMaterial.Icon.cmd_eye - "siren" -> CommunityMaterial.Icon.cmd_bullhorn - "simple_alarm" -> CommunityMaterial.Icon.cmd_bell - "sun" -> if (compareState == "above_horizon") - CommunityMaterial.Icon3.cmd_white_balance_sunny - else - CommunityMaterial.Icon3.cmd_weather_night - "switch" -> if (entity?.entityId?.isNotBlank() == true) { - when (entity.attributes["device_class"]) { - "outlet" -> if (compareState == "on") CommunityMaterial.Icon3.cmd_power_plug else CommunityMaterial.Icon3.cmd_power_plug_off - "switch" -> if (compareState == "on") CommunityMaterial.Icon3.cmd_toggle_switch else CommunityMaterial.Icon3.cmd_toggle_switch_off - else -> CommunityMaterial.Icon2.cmd_flash - } - } else { // For SimplifiedEntity without state, use a more generic icon - CommunityMaterial.Icon2.cmd_light_switch - } - "timer" -> CommunityMaterial.Icon3.cmd_timer_outline - "updater" -> CommunityMaterial.Icon.cmd_cloud_upload - "vacuum" -> CommunityMaterial.Icon3.cmd_robot_vacuum - "water_heater" -> CommunityMaterial.Icon3.cmd_thermometer - "weather" -> CommunityMaterial.Icon3.cmd_weather_cloudy - "zone" -> CommunityMaterial.Icon3.cmd_map_marker_radius - else -> CommunityMaterial.Icon.cmd_bookmark - } - } -} - -fun getIconFromMdiString(icon: String, context: Context): IIcon { - val mdiIcon = icon.split(":")[1] - return IconicsDrawable(context, "cmd-$mdiIcon").icon ?: CommunityMaterial.Icon.cmd_bookmark -} - -private fun coverIcon(state: String?, entity: Entity>?): IIcon? { - val open = state !== "closed" - - return when (entity?.attributes?.get("device_class")) { - "garage" -> when (state) { - "opening" -> CommunityMaterial.Icon.cmd_arrow_up_box - "closing" -> CommunityMaterial.Icon.cmd_arrow_down_box - "closed" -> CommunityMaterial.Icon2.cmd_garage - else -> CommunityMaterial.Icon2.cmd_garage_open - } - "gate" -> when (state) { - "opening", "closing" -> CommunityMaterial.Icon2.cmd_gate_arrow_right - "closed" -> CommunityMaterial.Icon2.cmd_gate - else -> CommunityMaterial.Icon2.cmd_gate_open - } - "door" -> if (open) CommunityMaterial.Icon.cmd_door_open else CommunityMaterial.Icon.cmd_door_closed - "damper" -> if (open) CommunityMaterial.Icon.cmd_circle else CommunityMaterial.Icon.cmd_circle_slice_8 - "shutter" -> when (state) { - "opening" -> CommunityMaterial.Icon.cmd_arrow_up_box - "closing" -> CommunityMaterial.Icon.cmd_arrow_down_box - "closed" -> CommunityMaterial.Icon3.cmd_window_shutter - else -> CommunityMaterial.Icon3.cmd_window_shutter_open - } - "curtain" -> when (state) { - "opening" -> CommunityMaterial.Icon.cmd_arrow_split_vertical - "closing" -> CommunityMaterial.Icon.cmd_arrow_collapse_horizontal - "closed" -> CommunityMaterial.Icon.cmd_curtains_closed - else -> CommunityMaterial.Icon.cmd_curtains - } - "blind", "shade" -> when (state) { - "opening" -> CommunityMaterial.Icon.cmd_arrow_up_box - "closing" -> CommunityMaterial.Icon.cmd_arrow_down_box - "closed" -> CommunityMaterial.Icon.cmd_blinds - else -> CommunityMaterial.Icon.cmd_blinds_open - } - else -> when (state) { - "opening" -> CommunityMaterial.Icon.cmd_arrow_up_box - "closing" -> CommunityMaterial.Icon.cmd_arrow_down_box - "closed" -> CommunityMaterial.Icon3.cmd_window_closed - else -> CommunityMaterial.Icon3.cmd_window_open - } - } + return simpleEntity.getIcon(context) } fun onEntityClickedFeedback(isToastEnabled: Boolean, isHapticEnabled: Boolean, context: Context, friendlyName: String, haptic: HapticFeedback) { diff --git a/wear/src/main/java/io/homeassistant/companion/android/views/ChooseEntityView.kt b/wear/src/main/java/io/homeassistant/companion/android/views/ChooseEntityView.kt index df1c8cc65..1158faacf 100644 --- a/wear/src/main/java/io/homeassistant/companion/android/views/ChooseEntityView.kt +++ b/wear/src/main/java/io/homeassistant/companion/android/views/ChooseEntityView.kt @@ -15,7 +15,6 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.capitalize import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.wear.compose.material.Chip @@ -25,10 +24,9 @@ import androidx.wear.compose.material.items import com.mikepenz.iconics.compose.Image import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial 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.getIcon import io.homeassistant.companion.android.data.SimplifiedEntity import io.homeassistant.companion.android.theme.WearAppTheme -import io.homeassistant.companion.android.util.getIcon import io.homeassistant.companion.android.util.stringForDomain import java.util.Locale import io.homeassistant.companion.android.common.R as commonR @@ -121,11 +119,7 @@ private fun ChooseEntityChip( onEntitySelected: (entity: SimplifiedEntity) -> Unit ) { val attributes = entity.attributes as Map<*, *> - val iconBitmap = getIcon( - entity as Entity>, - entity.domain, - LocalContext.current - ) + val iconBitmap = entity.getIcon(LocalContext.current) Chip( modifier = Modifier .fillMaxWidth(),