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
This commit is contained in:
Joris Pelgröm 2022-12-18 21:31:01 +01:00 committed by GitHub
parent 7f32738e10
commit 7e5e3adfe9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 302 additions and 223 deletions

View file

@ -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() {

View file

@ -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<List<Entity<*>>>(emptyList())
private set
var selectedIconId by mutableStateOf<Int?>(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,

View file

@ -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),

View file

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

View file

@ -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 <T> Entity<T>.getLightColor(): Int? {
null
}
}
fun <T> Entity<T>.getIcon(context: Context): IIcon? {
val attributes = this.attributes as Map<String, Any?>
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<Map<String, Any?>>)
"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<Map<String, Any?>>): 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
}
}
}

View file

@ -875,7 +875,8 @@
<string name="info">Information</string>
<string name="show_changelog">Show Change Log</string>
<string name="show_changelog_summary">Show the change log dialog from when the app was updated</string>
<string name="tile_icon">Select a icon for the tile</string>
<string name="tile_icon">Tile Icon</string>
<string name="tile_icon_original">Use entity icon</string>
<string name="tile_select">Select a tile to edit</string>
<string name="shortcut_pinned">Pinned Shortcuts</string>
<string name="remote_debugging">WebView Remote Debugging</string>

View file

@ -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()

View file

@ -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<Map<String, Any>>, entity.domain, LocalContext.current)
val iconBitmap = entity.getIcon(LocalContext.current)
val friendlyName = attributes["friendly_name"].toString()
if (entity.domain in HomePresenterImpl.toggleDomains) {

View file

@ -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<Map<String, Any>>,
entity.domain,
LocalContext.current
)
val iconBitmap = entity.getIcon(LocalContext.current)
val entityId = entity.entityId
val checked = favoriteEntityIds.contains(entityId)

View file

@ -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<Map<String, Any>>, domain, context)
}
fun getIcon(entity: Entity<Map<String, Any>>?, 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<Map<String, Any>>?): 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) {

View file

@ -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<Map<String, Any>>,
entity.domain,
LocalContext.current
)
val iconBitmap = entity.getIcon(LocalContext.current)
Chip(
modifier = Modifier
.fillMaxWidth(),