Skip to content

Commit 4b9f001

Browse files
authored
Add to dialog (widget, android auto favorite) (#5622)
* Extract domain into IntegrationDomain and add FOR_ENTITY param to widget * Improve AndroidAuto favorites API
1 parent ff3ee4c commit 4b9f001

File tree

33 files changed

+1061
-75
lines changed

33 files changed

+1061
-75
lines changed

app/src/main/kotlin/io/homeassistant/companion/android/controls/HaControl.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import com.mikepenz.iconics.utils.sizeDp
1414
import com.mikepenz.iconics.utils.toAndroidIconCompat
1515
import io.homeassistant.companion.android.common.R
1616
import io.homeassistant.companion.android.common.data.integration.Entity
17+
import io.homeassistant.companion.android.common.data.integration.IntegrationDomains.CAMERA_DOMAIN
18+
import io.homeassistant.companion.android.common.data.integration.IntegrationDomains.MEDIA_PLAYER_DOMAIN
1719
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
1820
import io.homeassistant.companion.android.common.data.integration.domain
1921
import io.homeassistant.companion.android.common.data.integration.friendlyState
@@ -69,7 +71,7 @@ interface HaControl {
6971
if (iconDrawable.icon != null) {
7072
val colorTint = when {
7173
entity.domain == "light" && entity.state == "on" -> R.color.colorDeviceControlsLightOn
72-
entity.domain == "camera" -> R.color.colorDeviceControlsCamera
74+
entity.domain == CAMERA_DOMAIN -> R.color.colorDeviceControlsCamera
7375
entity.domain == "climate" && entity.state == "heat" -> R.color.colorDeviceControlsThermostatHeat
7476
entity.state in listOf(
7577
"off",
@@ -84,7 +86,7 @@ interface HaControl {
8486
}
8587
} else {
8688
// Specific override for some domain icons to match HA frontend rather than provided device type
87-
val iconOverride = listOf("media_player", "number")
89+
val iconOverride = listOf(MEDIA_PLAYER_DOMAIN, "number")
8890
if (entity.domain in iconOverride) {
8991
val icon = IconicsDrawable(context, entity.getIcon(context)).apply { sizeDp = 48 }
9092
val tint = if (entity.isActive()) {

app/src/main/kotlin/io/homeassistant/companion/android/controls/HaControlsProviderService.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import androidx.annotation.RequiresApi
88
import dagger.hilt.android.AndroidEntryPoint
99
import io.homeassistant.companion.android.common.data.integration.ControlsAuthRequiredSetting
1010
import io.homeassistant.companion.android.common.data.integration.Entity
11+
import io.homeassistant.companion.android.common.data.integration.IntegrationDomains.CAMERA_DOMAIN
12+
import io.homeassistant.companion.android.common.data.integration.IntegrationDomains.MEDIA_PLAYER_DOMAIN
1113
import io.homeassistant.companion.android.common.data.integration.applyCompressedStateDiff
1214
import io.homeassistant.companion.android.common.data.integration.domain
1315
import io.homeassistant.companion.android.common.data.prefs.PrefsRepository
@@ -41,7 +43,7 @@ class HaControlsProviderService : ControlsProviderService() {
4143
private val domainToHaControl = mapOf(
4244
"automation" to DefaultSwitchControl,
4345
"button" to DefaultButtonControl,
44-
"camera" to CameraControl,
46+
CAMERA_DOMAIN to CameraControl,
4547
"climate" to ClimateControl,
4648
"cover" to CoverControl,
4749
"fan" to FanControl,
@@ -52,7 +54,7 @@ class HaControlsProviderService : ControlsProviderService() {
5254
"input_number" to DefaultSliderControl,
5355
"light" to LightControl,
5456
"lock" to LockControl,
55-
"media_player" to MediaPlayerControl,
57+
MEDIA_PLAYER_DOMAIN to MediaPlayerControl,
5658
"number" to DefaultSliderControl,
5759
"remote" to DefaultSwitchControl,
5860
"scene" to DefaultButtonControl,
@@ -62,11 +64,10 @@ class HaControlsProviderService : ControlsProviderService() {
6264
"vacuum" to VacuumControl,
6365
)
6466
private val domainToMinimumApi = mapOf(
65-
"camera" to Build.VERSION_CODES.S,
67+
CAMERA_DOMAIN to Build.VERSION_CODES.S,
6668
)
6769

6870
fun getSupportedDomains(): List<String> = domainToHaControl
69-
.filter { it.value != null }
7071
.map { it.key }
7172
.filter {
7273
domainToMinimumApi[it] == null ||

app/src/main/kotlin/io/homeassistant/companion/android/settings/qs/ManageTilesFragment.kt

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import androidx.fragment.app.viewModels
1414
import com.mikepenz.iconics.typeface.IIcon
1515
import dagger.hilt.android.AndroidEntryPoint
1616
import io.homeassistant.companion.android.common.R as commonR
17-
import io.homeassistant.companion.android.common.data.integration.EntityExt
1817
import io.homeassistant.companion.android.settings.addHelpMenuProvider
1918
import io.homeassistant.companion.android.settings.qs.views.ManageTilesView
2019
import io.homeassistant.companion.android.util.compose.HomeAssistantAppTheme
@@ -23,11 +22,6 @@ import timber.log.Timber
2322

2423
@AndroidEntryPoint
2524
class ManageTilesFragment : Fragment() {
26-
27-
companion object {
28-
val validDomains = EntityExt.APP_PRESS_ACTION_DOMAINS
29-
}
30-
3125
val viewModel: ManageTilesViewModel by viewModels()
3226

3327
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {

app/src/main/kotlin/io/homeassistant/companion/android/settings/qs/ManageTilesViewModel.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ import com.mikepenz.iconics.typeface.library.community.material.CommunityMateria
2020
import dagger.hilt.android.lifecycle.HiltViewModel
2121
import io.homeassistant.companion.android.common.R as commonR
2222
import io.homeassistant.companion.android.common.data.integration.Entity
23-
import io.homeassistant.companion.android.common.data.integration.domain
2423
import io.homeassistant.companion.android.common.data.integration.getIcon
24+
import io.homeassistant.companion.android.common.data.integration.isUsableInTile
2525
import io.homeassistant.companion.android.common.data.servers.ServerManager
2626
import io.homeassistant.companion.android.database.qs.TileDao
2727
import io.homeassistant.companion.android.database.qs.TileEntity
@@ -184,7 +184,7 @@ class ManageTilesViewModel @Inject constructor(
184184
async {
185185
entities[it.id] = try {
186186
serverManager.integrationRepository(it.id).getEntities().orEmpty()
187-
.filter { it.domain in ManageTilesFragment.validDomains }
187+
.filter(Entity::isUsableInTile)
188188
} catch (e: Exception) {
189189
Timber.e(e, "Couldn't load entities for server")
190190
emptyList()

app/src/main/kotlin/io/homeassistant/companion/android/settings/vehicle/ManageAndroidAutoViewModel.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import androidx.lifecycle.AndroidViewModel
1313
import androidx.lifecycle.viewModelScope
1414
import dagger.hilt.android.lifecycle.HiltViewModel
1515
import io.homeassistant.companion.android.common.data.integration.Entity
16+
import io.homeassistant.companion.android.common.data.prefs.AutoFavorite
1617
import io.homeassistant.companion.android.common.data.prefs.PrefsRepository
1718
import io.homeassistant.companion.android.common.data.servers.ServerManager
1819
import io.homeassistant.companion.android.util.vehicle.isVehicleDomain
@@ -30,7 +31,7 @@ class ManageAndroidAutoViewModel @Inject constructor(
3031
application: Application,
3132
) : AndroidViewModel(application) {
3233

33-
val favoritesList = mutableStateListOf<String>()
34+
val favoritesList = mutableStateListOf<AutoFavorite>()
3435

3536
var sortedEntities by mutableStateOf<List<Entity>>(emptyList())
3637
private set
@@ -81,10 +82,11 @@ class ManageAndroidAutoViewModel @Inject constructor(
8182
}
8283

8384
fun onEntitySelected(checked: Boolean, entityId: String, serverId: Int) {
85+
val favorite = AutoFavorite(serverId, entityId)
8486
if (checked) {
85-
favoritesList.add("$serverId-$entityId")
87+
favoritesList.add(favorite)
8688
} else {
87-
favoritesList.remove("$serverId-$entityId")
89+
favoritesList.remove(favorite)
8890
}
8991
viewModelScope.launch { prefsRepository.setAutoFavorites(favoritesList.toList()) }
9092
}

app/src/main/kotlin/io/homeassistant/companion/android/settings/vehicle/views/AndroidAutoFavoritesView.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import androidx.compose.ui.unit.dp
2222
import io.homeassistant.companion.android.common.R as commonR
2323
import io.homeassistant.companion.android.common.data.integration.Entity
2424
import io.homeassistant.companion.android.common.data.integration.friendlyName
25+
import io.homeassistant.companion.android.common.data.prefs.AutoFavorite
2526
import io.homeassistant.companion.android.database.server.Server
2627
import io.homeassistant.companion.android.settings.vehicle.ManageAndroidAutoViewModel
2728
import io.homeassistant.companion.android.util.compose.FavoriteEntityRow
@@ -56,7 +57,7 @@ fun AndroidAutoFavoritesSettings(
5657
validEntities = withContext(Dispatchers.IO) {
5758
androidAutoViewModel.sortedEntities
5859
.filter {
59-
!favoriteEntities.contains("$selectedServer-${it.entityId}") &&
60+
!favoriteEntities.contains(AutoFavorite(selectedServer, it.entityId)) &&
6061
isVehicleDomain(it)
6162
}
6263
.toList()
@@ -103,11 +104,10 @@ fun AndroidAutoFavoritesSettings(
103104
}
104105
if (favoriteEntities.isNotEmpty() && androidAutoViewModel.sortedEntities.isNotEmpty()) {
105106
items(favoriteEntities.size, { favoriteEntities[it] }) { index ->
106-
val favoriteEntity =
107-
favoriteEntities[index].split("-")
107+
val favoriteEntity = favoriteEntities[index]
108108
androidAutoViewModel.sortedEntities.firstOrNull {
109-
it.entityId == favoriteEntity[1] &&
110-
favoriteEntity[0].toInt() == selectedServer
109+
it.entityId == favoriteEntity.entityId &&
110+
favoriteEntity.serverId == selectedServer
111111
}?.let {
112112
ReorderableItem(
113113
state = reorderState,

app/src/main/kotlin/io/homeassistant/companion/android/util/compose/EntityInfo.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@ package io.homeassistant.companion.android.util.compose
33
import androidx.compose.runtime.Composable
44
import androidx.compose.ui.res.stringResource
55
import io.homeassistant.companion.android.common.R as commonR
6+
import io.homeassistant.companion.android.common.data.integration.IntegrationDomains.CAMERA_DOMAIN
67

78
@Composable
89
fun getEntityDomainString(domain: String): String {
910
return when (domain) {
1011
"automation" -> stringResource(commonR.string.domain_automation)
1112
"button" -> stringResource(commonR.string.domain_button)
12-
"camera" -> stringResource(commonR.string.domain_camera)
13+
CAMERA_DOMAIN -> stringResource(commonR.string.domain_camera)
1314
"climate" -> stringResource(commonR.string.domain_climate)
1415
"cover" -> stringResource(commonR.string.domain_cover)
1516
"fan" -> stringResource(commonR.string.domain_fan)

app/src/main/kotlin/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import io.homeassistant.companion.android.common.R as commonR
2121
import io.homeassistant.companion.android.common.data.authentication.SessionState
2222
import io.homeassistant.companion.android.common.data.integration.Entity
2323
import io.homeassistant.companion.android.common.data.integration.domain
24+
import io.homeassistant.companion.android.common.data.prefs.AutoFavorite
2425
import io.homeassistant.companion.android.common.data.prefs.PrefsRepository
2526
import io.homeassistant.companion.android.common.data.servers.ServerManager
2627
import io.homeassistant.companion.android.common.data.websocket.impl.entities.EntityRegistryResponse
@@ -52,7 +53,7 @@ class MainVehicleScreen(
5253

5354
private var favoritesEntities: List<Entity> = listOf()
5455
private var entityRegistry: List<EntityRegistryResponse>? = null
55-
private var favoritesList = emptyList<String>()
56+
private var favoritesList = emptyList<AutoFavorite>()
5657
private var isLoggedIn: Boolean? = null
5758
private val domains = mutableSetOf<String>()
5859
private var domainsJob: Job? = null
@@ -133,7 +134,7 @@ class MainVehicleScreen(
133134
setLoading(true)
134135
}.build()
135136
}
136-
val serverHasFavorites = favoritesList.any { it.split("-")[0].toIntOrNull() == serverId.value }
137+
val serverHasFavorites = favoritesList.any { it.serverId == serverId.value }
137138
val listBuilder = if (serverHasFavorites) {
138139
EntityGridVehicleScreen(
139140
carContext,
@@ -217,7 +218,9 @@ class MainVehicleScreen(
217218
}
218219

219220
private fun getFavoritesList(entities: Map<String, Entity>): List<Entity> {
220-
return entities.values.filter { entity -> favoritesList.contains("${serverId.value}-${entity.entityId}") }
221-
.sortedBy { entity -> favoritesList.indexOf("${serverId.value}-${entity.entityId}") }
221+
return entities.values.filter { entity ->
222+
favoritesList.contains(AutoFavorite(serverId.value, entity.entityId))
223+
}
224+
.sortedBy { entity -> favoritesList.indexOf(AutoFavorite(serverId.value, entity.entityId)) }
222225
}
223226
}

app/src/main/kotlin/io/homeassistant/companion/android/webview/WebViewActivity.kt

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ import io.homeassistant.companion.android.common.data.prefs.NightModeTheme
8686
import io.homeassistant.companion.android.common.data.servers.ServerManager
8787
import io.homeassistant.companion.android.common.util.AppVersionProvider
8888
import io.homeassistant.companion.android.common.util.DisabledLocationHandler
89+
import io.homeassistant.companion.android.common.util.FailFast
8990
import io.homeassistant.companion.android.common.util.GestureAction
9091
import io.homeassistant.companion.android.common.util.GestureDirection
9192
import io.homeassistant.companion.android.common.util.getBooleanOrElse
@@ -119,8 +120,11 @@ import io.homeassistant.companion.android.util.compose.initializePlayer
119120
import io.homeassistant.companion.android.util.isStarted
120121
import io.homeassistant.companion.android.websocket.WebsocketManager
121122
import io.homeassistant.companion.android.webview.WebView.ErrorType
123+
import io.homeassistant.companion.android.webview.addto.EntityAddToHandler
124+
import io.homeassistant.companion.android.webview.externalbus.EntityAddToActionsResponse
122125
import io.homeassistant.companion.android.webview.externalbus.ExternalBusMessage
123126
import io.homeassistant.companion.android.webview.externalbus.ExternalConfigResponse
127+
import io.homeassistant.companion.android.webview.externalbus.ExternalEntityAddToAction
124128
import io.homeassistant.companion.android.webview.externalbus.NavigateTo
125129
import io.homeassistant.companion.android.webview.externalbus.ShowSidebar
126130
import javax.inject.Inject
@@ -223,6 +227,9 @@ class WebViewActivity :
223227
@Inject
224228
lateinit var appVersionProvider: AppVersionProvider
225229

230+
@Inject
231+
lateinit var entityAddToHandler: EntityAddToHandler
232+
226233
private lateinit var webView: WebView
227234
private lateinit var loadedUrl: String
228235
private lateinit var decor: FrameLayout
@@ -869,6 +876,8 @@ class WebViewActivity :
869876
json["payload"]?.jsonObjectOrNull()?.getStringOrNull("hapticType") ?: "",
870877
)
871878
"theme-update" -> getAndSetStatusBarNavigationBarColors()
879+
"entity/add_to/get_actions" -> getActions(json)
880+
"entity/add_to" -> addEntityTo(json)
872881
else -> presenter.onExternalBusMessage(json)
873882
}
874883
}
@@ -893,6 +902,40 @@ class WebViewActivity :
893902
}
894903
}
895904

905+
private fun addEntityTo(json: JsonObject) {
906+
val payload = json["payload"]?.jsonObjectOrNull()
907+
val entityId = payload?.getStringOrNull("entity_id")
908+
val appPayload = payload?.getStringOrNull("app_payload")
909+
if (entityId != null && appPayload != null) {
910+
val action = ExternalEntityAddToAction.appPayloadToAction(appPayload)
911+
lifecycleScope.launch {
912+
entityAddToHandler.execute(this@WebViewActivity, action, entityId)
913+
}
914+
} else {
915+
FailFast.fail { "Missing entity_id or app_payload to addEntityTo" }
916+
}
917+
}
918+
919+
private fun getActions(json: JsonObject) {
920+
val payload = json["payload"]?.jsonObjectOrNull()
921+
val entityId = payload?.getStringOrNull("entity_id")
922+
entityId?.let {
923+
lifecycleScope.launch {
924+
val actions = entityAddToHandler.actionsForEntity(entityId)
925+
sendExternalBusMessage(
926+
EntityAddToActionsResponse(
927+
id = json["id"],
928+
actions = actions.map { action ->
929+
ExternalEntityAddToAction.fromAction(this@WebViewActivity, action)
930+
},
931+
),
932+
)
933+
}
934+
} ?: FailFast.fail {
935+
"entity_id not present in response from External bus for `entity/add_to/get_actions`"
936+
}
937+
}
938+
896939
private fun handleWebViewGesture(direction: GestureDirection, pointerCount: Int) {
897940
lifecycleScope.launch {
898941
when (presenter.getGestureAction(direction, pointerCount)) {
@@ -1650,7 +1693,7 @@ class WebViewActivity :
16501693
val json = Json.encodeToString(jsonObject)
16511694
val script = "externalBus($json);"
16521695

1653-
Timber.d(script)
1696+
Timber.d("Sending: $script")
16541697

16551698
webView.evaluateJavascript(script, message.callback)
16561699
}

0 commit comments

Comments
 (0)