From ee62c6dcdf4df6a6fa921e13e0428819519dd777 Mon Sep 17 00:00:00 2001 From: Daniel Shokouhi Date: Fri, 21 Jul 2023 13:58:20 -0700 Subject: [PATCH 01/16] Android auto: default to favorites if defined --- .../android/vehicle/DomainListScreen.kt | 74 ++++++ .../vehicle/EntityGridVehicleScreen.kt | 115 ++++++++- .../android/vehicle/MainVehicleScreen.kt | 227 ++++++++++-------- 3 files changed, 317 insertions(+), 99 deletions(-) create mode 100755 app/src/main/java/io/homeassistant/companion/android/vehicle/DomainListScreen.kt diff --git a/app/src/main/java/io/homeassistant/companion/android/vehicle/DomainListScreen.kt b/app/src/main/java/io/homeassistant/companion/android/vehicle/DomainListScreen.kt new file mode 100755 index 00000000000..ed32df7e3c1 --- /dev/null +++ b/app/src/main/java/io/homeassistant/companion/android/vehicle/DomainListScreen.kt @@ -0,0 +1,74 @@ +package io.homeassistant.companion.android.vehicle + +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.car.app.CarContext +import androidx.car.app.Screen +import androidx.car.app.model.Action +import androidx.car.app.model.ListTemplate +import androidx.car.app.model.Template +import androidx.lifecycle.lifecycleScope +import io.homeassistant.companion.android.BuildConfig +import io.homeassistant.companion.android.common.R +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.prefs.PrefsRepository +import io.homeassistant.companion.android.common.data.servers.ServerManager +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch + +@RequiresApi(Build.VERSION_CODES.O) +class DomainListScreen( + carContext: CarContext, + val serverManager: ServerManager, + val integrationRepository: IntegrationRepository, + private val serverId: StateFlow, + private val allEntities: Flow>>, + private val prefsRepository: PrefsRepository +) : Screen(carContext) { + + private val domains = mutableSetOf() + + init { + lifecycleScope.launch { + allEntities.collect { entities -> + val newDomains = entities.values + .map { it.domain } + .distinct() + .filter { it in MainVehicleScreen.SUPPORTED_DOMAINS } + .toSet() + if (newDomains.size != domains.size || newDomains != domains) { + domains.clear() + domains.addAll(newDomains) + invalidate() + } + } + } + } + override fun onGetTemplate(): Template { + val screen = MainVehicleScreen( + carContext, + serverManager, + serverId, + allEntities, + prefsRepository + ) { } + val domainList = screen.addDomainList(domains) + + return ListTemplate.Builder().apply { + setTitle(carContext.getString(R.string.all_entities)) + setHeaderAction(Action.BACK) + if (screen.isAutomotive && !screen.iDrivingOptimized && BuildConfig.FLAVOR != "full") { + setActionStrip(screen.nativeModeActionStrip()) + } + if (domainList.build().items.isEmpty()) { + setLoading(true) + } else { + setLoading(false) + setSingleList(domainList.build()) + } + }.build() + } +} diff --git a/app/src/main/java/io/homeassistant/companion/android/vehicle/EntityGridVehicleScreen.kt b/app/src/main/java/io/homeassistant/companion/android/vehicle/EntityGridVehicleScreen.kt index 16edb48b929..08f27cc8b20 100644 --- a/app/src/main/java/io/homeassistant/companion/android/vehicle/EntityGridVehicleScreen.kt +++ b/app/src/main/java/io/homeassistant/companion/android/vehicle/EntityGridVehicleScreen.kt @@ -20,30 +20,42 @@ import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial import com.mikepenz.iconics.utils.sizeDp import com.mikepenz.iconics.utils.toAndroidIconCompat +import io.homeassistant.companion.android.common.R 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.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.isExecuting import io.homeassistant.companion.android.common.data.integration.onPressed +import io.homeassistant.companion.android.common.data.prefs.PrefsRepository +import io.homeassistant.companion.android.common.data.servers.ServerManager import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @RequiresApi(Build.VERSION_CODES.O) class EntityGridVehicleScreen( carContext: CarContext, + val serverManager: ServerManager, + val serverId: StateFlow, + val prefsRepository: PrefsRepository, val integrationRepository: IntegrationRepository, val title: String, - val entitiesFlow: Flow>> + private val entitiesFlow: Flow>>, + private val allEntities: Flow>>, + private val onChangeServer: (Int) -> Unit ) : Screen(carContext) { companion object { private const val TAG = "EntityGridVehicleScreen" } - var loading = true + private var loading = true var entities: List> = listOf() + private val isFavorites = title == carContext.getString(R.string.favorites) init { lifecycleScope.launch { @@ -58,7 +70,7 @@ class EntityGridVehicleScreen( } } - override fun onGetTemplate(): Template { + fun getEntityGridItems(entities: List>): ItemList.Builder { val manager = carContext.getCarService(ConstraintManager::class.java) val gridLimit = manager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_GRID) @@ -98,6 +110,101 @@ class EntityGridVehicleScreen( listBuilder.addItem(gridItem.build()) } + if (isFavorites) { + val navGridItem = GridItem.Builder().apply { + setTitle(carContext.getString(R.string.aa_navigation)) + setImage( + CarIcon.Builder( + IconicsDrawable( + carContext, + CommunityMaterial.Icon3.cmd_map_outline + ).apply { + sizeDp = 48 + }.toAndroidIconCompat() + ) + .setTint(CarColor.DEFAULT) + .build() + ) + setOnClickListener { + Log.i(TAG, "Navigation clicked") + screenManager.push( + MapVehicleScreen( + carContext, + integrationRepository, + allEntities.map { it.values.filter { entity -> entity.domain in MainVehicleScreen.MAP_DOMAINS } } + ) + ) + } + } + listBuilder.addItem(navGridItem.build()) + val categoryItem = GridItem.Builder().apply { + setTitle(carContext.getString(R.string.all_entities)) + setImage( + CarIcon.Builder( + IconicsDrawable( + carContext, + CommunityMaterial.Icon3.cmd_view_list + ).apply { + sizeDp = 48 + }.toAndroidIconCompat() + ) + .setTint(CarColor.DEFAULT) + .build() + ) + setOnClickListener { + Log.i(TAG, "Categories clicked") + screenManager.push( + DomainListScreen( + carContext, + serverManager, + integrationRepository, + serverId, + allEntities, + prefsRepository + ) + ) + } + } + listBuilder.addItem(categoryItem.build()) + if (serverManager.defaultServers.size > 1) { + val changeServerItem = GridItem.Builder().apply { + setTitle(carContext.getString(R.string.aa_change_server)) + setImage( + CarIcon.Builder( + IconicsDrawable( + carContext, + CommunityMaterial.Icon2.cmd_home_switch + ).apply { + sizeDp = 48 + }.toAndroidIconCompat() + ) + .setTint(CarColor.DEFAULT) + .build() + ) + setOnClickListener { + Log.i(TAG, "Change server clicked") + screenManager.pushForResult( + ChangeServerScreen( + carContext, + serverManager, + serverId + ) + ) { + it?.toString()?.toIntOrNull()?.let { serverId -> + onChangeServer(serverId) + } + } + } + } + listBuilder.addItem(changeServerItem.build()) + } + } + return listBuilder + } + + override fun onGetTemplate(): Template { + val entityGrid = getEntityGridItems(entities) + return GridTemplate.Builder().apply { setTitle(title) setHeaderAction(Action.BACK) @@ -105,7 +212,7 @@ class EntityGridVehicleScreen( setLoading(true) } else { setLoading(false) - setSingleList(listBuilder.build()) + setSingleList(entityGrid.build()) } }.build() } diff --git a/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt b/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt index 17bdbd849cf..e70da545f36 100644 --- a/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt +++ b/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt @@ -13,6 +13,7 @@ import androidx.car.app.model.Action import androidx.car.app.model.ActionStrip import androidx.car.app.model.CarColor import androidx.car.app.model.CarIcon +import androidx.car.app.model.GridTemplate import androidx.car.app.model.ItemList import androidx.car.app.model.ListTemplate import androidx.car.app.model.Row @@ -38,6 +39,7 @@ import io.homeassistant.companion.android.launch.LaunchActivity import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import java.util.Calendar @@ -70,7 +72,7 @@ class MainVehicleScreen( ) val SUPPORTED_DOMAINS = SUPPORTED_DOMAINS_WITH_STRING.keys - private val MAP_DOMAINS = listOf( + val MAP_DOMAINS = listOf( "device_tracker", "person", "sensor", @@ -78,19 +80,22 @@ class MainVehicleScreen( ) } + private var favoriteEntites: Flow>> = flowOf() + private var entities: List> = listOf() + private var loading = true private var favoritesList = emptyList() private var isLoggedIn: Boolean? = null private val domains = mutableSetOf() private var car: Car? = null private var carRestrictionManager: CarUxRestrictionsManager? = null - private val iDrivingOptimized + val iDrivingOptimized get() = car?.let { ( it.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE) as CarUxRestrictionsManager ).getCurrentCarUxRestrictions().isRequiresDistractionOptimization() } ?: false - private val isAutomotive get() = carContext.packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) + val isAutomotive get() = carContext.packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) init { lifecycleScope.launch { @@ -121,6 +126,18 @@ class MainVehicleScreen( } } } + lifecycleScope.launch { + favoriteEntites = allEntities.map { + it.values.filter { entity -> favoritesList.contains("${serverId.value}-${entity.entityId}") } + .sortedBy { entity -> favoritesList.indexOf("${serverId.value}-${entity.entityId}") } + } + favoriteEntites.collect { + loading = false + val hasChanged = entities.size != it.size || entities.toSet() != it.toSet() + entities = it + if (hasChanged) invalidate() + } + } lifecycle.addObserver(object : DefaultLifecycleObserver { @@ -137,78 +154,26 @@ class MainVehicleScreen( } override fun onGetTemplate(): Template { - val listBuilder = ItemList.Builder() + var listBuilder = ItemList.Builder() + var favoritesGrid: ItemList.Builder? = null if (favoritesList.isNotEmpty()) { - listBuilder.addItem( - Row.Builder().apply { - setImage( - CarIcon.Builder( - IconicsDrawable(carContext, CommunityMaterial.Icon3.cmd_star).apply { - sizeDp = 48 - }.toAndroidIconCompat() - ) - .setTint(CarColor.DEFAULT) - .build() - ) - setTitle(carContext.getString(commonR.string.favorites)) - setOnClickListener { - Log.i(TAG, "Favorites clicked: $favoritesList, current server: ${serverId.value}") - screenManager.push( - EntityGridVehicleScreen( - carContext, - serverManager.integrationRepository(serverId.value), - carContext.getString(commonR.string.favorites), - allEntities.map { it.values.filter { entity -> favoritesList.contains("${serverId.value}-${entity.entityId}") }.sortedBy { entity -> favoritesList.indexOf("${serverId.value}-${entity.entityId}") } } - ) - ) - } - }.build() - ) + val favoriteEntities = allEntities.map { + it.values.filter { entity -> favoritesList.contains("${serverId.value}-${entity.entityId}") } + .sortedBy { entity -> favoritesList.indexOf("${serverId.value}-${entity.entityId}") } + } + favoritesGrid = EntityGridVehicleScreen( + carContext, + serverManager, + serverId, + prefsRepository, + serverManager.integrationRepository(serverId.value), + carContext.getString(commonR.string.favorites), + favoriteEntities, + allEntities + ) { }.getEntityGridItems(entities) } - domains.forEach { domain -> - val friendlyDomain = - SUPPORTED_DOMAINS_WITH_STRING[domain]?.let { carContext.getString(it) } - ?: domain.split("_").joinToString(" ") { word -> - word.capitalize(Locale.getDefault()) - } - val icon = Entity( - "$domain.ha_android_placeholder", - "", - mapOf(), - Calendar.getInstance(), - Calendar.getInstance(), - null - ).getIcon(carContext) - - listBuilder.addItem( - Row.Builder().apply { - if (icon != null) { - setImage( - CarIcon.Builder( - IconicsDrawable(carContext, icon) - .apply { - sizeDp = 48 - }.toAndroidIconCompat() - ) - .setTint(CarColor.DEFAULT) - .build() - ) - } - } - .setTitle(friendlyDomain) - .setOnClickListener { - Log.i(TAG, "Domain:$domain clicked") - screenManager.push( - EntityGridVehicleScreen( - carContext, - serverManager.integrationRepository(serverId.value), - friendlyDomain, - allEntities.map { it.values.filter { entity -> entity.domain == domain } } - ) - ) - } - .build() - ) + if (domains.isNotEmpty()) { + listBuilder = addDomainList(domains) } listBuilder.addItem( @@ -273,27 +238,35 @@ class MainVehicleScreen( ) } - return ListTemplate.Builder().apply { - setTitle(carContext.getString(commonR.string.app_name)) - setHeaderAction(Action.APP_ICON) - if (isAutomotive && !iDrivingOptimized && BuildConfig.FLAVOR != "full") { - setActionStrip( - ActionStrip.Builder().addAction( - Action.Builder() - .setTitle(carContext.getString(commonR.string.aa_launch_native)) - .setOnClickListener { - startNativeActivity() - }.build() - ).build() - ) - } - if (domains.isEmpty()) { - setLoading(true) - } else { - setLoading(false) - setSingleList(listBuilder.build()) - } - }.build() + return if (favoritesList.isEmpty()) { + ListTemplate.Builder().apply { + setTitle(carContext.getString(commonR.string.app_name)) + setHeaderAction(Action.APP_ICON) + if (isAutomotive && !iDrivingOptimized && BuildConfig.FLAVOR != "full") { + setActionStrip(nativeModeActionStrip()) + } + if (domains.isEmpty()) { + setLoading(true) + } else { + setLoading(false) + setSingleList(listBuilder.build()) + } + }.build() + } else { + GridTemplate.Builder().apply { + setTitle(carContext.getString(commonR.string.app_name)) + setHeaderAction(Action.APP_ICON) + if (isAutomotive && !iDrivingOptimized && BuildConfig.FLAVOR != "full") { + setActionStrip(nativeModeActionStrip()) + } + if (loading) { + setLoading(true) + } else { + setLoading(false) + setSingleList(favoritesGrid!!.build()) + } + }.build() + } } private fun registerAutomotiveRestrictionListener() { @@ -310,7 +283,7 @@ class MainVehicleScreen( } } - private fun startNativeActivity() { + fun startNativeActivity() { Log.i(TAG, "Starting login activity") with(carContext) { startActivity( @@ -326,4 +299,68 @@ class MainVehicleScreen( } } } + + fun addDomainList(domains: MutableSet): ItemList.Builder { + val listBuilder = ItemList.Builder() + domains.forEach { domain -> + val friendlyDomain = + SUPPORTED_DOMAINS_WITH_STRING[domain]?.let { carContext.getString(it) } + ?: domain.split("_").joinToString(" ") { word -> + word.capitalize(Locale.getDefault()) + } + val icon = Entity( + "$domain.ha_android_placeholder", + "", + mapOf(), + Calendar.getInstance(), + Calendar.getInstance(), + null + ).getIcon(carContext) + + listBuilder.addItem( + Row.Builder().apply { + if (icon != null) { + setImage( + CarIcon.Builder( + IconicsDrawable(carContext, icon) + .apply { + sizeDp = 48 + }.toAndroidIconCompat() + ) + .setTint(CarColor.DEFAULT) + .build() + ) + } + } + .setTitle(friendlyDomain) + .setOnClickListener { + Log.i(TAG, "Domain:$domain clicked") + screenManager.push( + EntityGridVehicleScreen( + carContext, + serverManager, + serverId, + prefsRepository, + serverManager.integrationRepository(serverId.value), + friendlyDomain, + allEntities.map { it.values.filter { entity -> entity.domain == domain } }, + allEntities + ) { } + ) + } + .build() + ) + } + return listBuilder + } + + fun nativeModeActionStrip(): ActionStrip { + return ActionStrip.Builder().addAction( + Action.Builder() + .setTitle(carContext.getString(commonR.string.aa_launch_native)) + .setOnClickListener { + startNativeActivity() + }.build() + ).build() + } } From 7082ffcefece54c4099538134228bef358a8c5a6 Mon Sep 17 00:00:00 2001 From: Daniel Shokouhi Date: Fri, 21 Jul 2023 14:37:24 -0700 Subject: [PATCH 02/16] Fix changing servers --- .../companion/android/vehicle/MainVehicleScreen.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt b/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt index e70da545f36..9cfb5784d1f 100644 --- a/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt +++ b/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt @@ -170,7 +170,11 @@ class MainVehicleScreen( carContext.getString(commonR.string.favorites), favoriteEntities, allEntities - ) { }.getEntityGridItems(entities) + ) { + it.toString().toIntOrNull()?.let { serverId -> + onChangeServer(serverId) + } + }.getEntityGridItems(entities) } if (domains.isNotEmpty()) { listBuilder = addDomainList(domains) From 521fe95986435de066d007e79b80c94a6e3852e0 Mon Sep 17 00:00:00 2001 From: Daniel Shokouhi Date: Fri, 21 Jul 2023 14:43:29 -0700 Subject: [PATCH 03/16] Fix empty favorites list --- .../companion/android/common/data/prefs/PrefsRepositoryImpl.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/io/homeassistant/companion/android/common/data/prefs/PrefsRepositoryImpl.kt b/common/src/main/java/io/homeassistant/companion/android/common/data/prefs/PrefsRepositoryImpl.kt index 6fea9ee6cb3..2eb0244fb3c 100644 --- a/common/src/main/java/io/homeassistant/companion/android/common/data/prefs/PrefsRepositoryImpl.kt +++ b/common/src/main/java/io/homeassistant/companion/android/common/data/prefs/PrefsRepositoryImpl.kt @@ -200,7 +200,7 @@ class PrefsRepositoryImpl @Inject constructor( } override suspend fun getAutoFavorites(): List { - return localStorage.getString(PREF_AUTO_FAVORITES)?.removeSurrounding("[", "]")?.split(", ") ?: emptyList() + return localStorage.getString(PREF_AUTO_FAVORITES)?.removeSurrounding("[", "]")?.split(", ")?.filter { it.isNotBlank() } ?: emptyList() } override suspend fun setAutoFavorites(favorites: List) { From 8ca48b4e1a99ef1e533c492cfac43f5ba9adfad1 Mon Sep 17 00:00:00 2001 From: Daniel Shokouhi Date: Fri, 21 Jul 2023 15:20:56 -0700 Subject: [PATCH 04/16] Review comments --- .../android/vehicle/MainVehicleScreen.kt | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt b/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt index 9cfb5784d1f..13c70e7bd80 100644 --- a/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt +++ b/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt @@ -80,9 +80,8 @@ class MainVehicleScreen( ) } - private var favoriteEntites: Flow>> = flowOf() + private var favoriteEntities = flowOf>>() private var entities: List> = listOf() - private var loading = true private var favoritesList = emptyList() private var isLoggedIn: Boolean? = null private val domains = mutableSetOf() @@ -127,12 +126,11 @@ class MainVehicleScreen( } } lifecycleScope.launch { - favoriteEntites = allEntities.map { + favoriteEntities = allEntities.map { it.values.filter { entity -> favoritesList.contains("${serverId.value}-${entity.entityId}") } .sortedBy { entity -> favoritesList.indexOf("${serverId.value}-${entity.entityId}") } } - favoriteEntites.collect { - loading = false + favoriteEntities.collect { val hasChanged = entities.size != it.size || entities.toSet() != it.toSet() entities = it if (hasChanged) invalidate() @@ -157,10 +155,6 @@ class MainVehicleScreen( var listBuilder = ItemList.Builder() var favoritesGrid: ItemList.Builder? = null if (favoritesList.isNotEmpty()) { - val favoriteEntities = allEntities.map { - it.values.filter { entity -> favoritesList.contains("${serverId.value}-${entity.entityId}") } - .sortedBy { entity -> favoritesList.indexOf("${serverId.value}-${entity.entityId}") } - } favoritesGrid = EntityGridVehicleScreen( carContext, serverManager, @@ -170,11 +164,7 @@ class MainVehicleScreen( carContext.getString(commonR.string.favorites), favoriteEntities, allEntities - ) { - it.toString().toIntOrNull()?.let { serverId -> - onChangeServer(serverId) - } - }.getEntityGridItems(entities) + ) { onChangeServer(it) }.getEntityGridItems(entities) } if (domains.isNotEmpty()) { listBuilder = addDomainList(domains) @@ -263,7 +253,7 @@ class MainVehicleScreen( if (isAutomotive && !iDrivingOptimized && BuildConfig.FLAVOR != "full") { setActionStrip(nativeModeActionStrip()) } - if (loading) { + if (domains.isEmpty()) { setLoading(true) } else { setLoading(false) From bddf48556e8351b45a52dc07e45d4ee5aec366fb Mon Sep 17 00:00:00 2001 From: Daniel Shokouhi Date: Fri, 21 Jul 2023 15:31:10 -0700 Subject: [PATCH 05/16] Account for extra grid items when showing favorites --- .../companion/android/vehicle/EntityGridVehicleScreen.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/io/homeassistant/companion/android/vehicle/EntityGridVehicleScreen.kt b/app/src/main/java/io/homeassistant/companion/android/vehicle/EntityGridVehicleScreen.kt index 08f27cc8b20..713843bd3ae 100644 --- a/app/src/main/java/io/homeassistant/companion/android/vehicle/EntityGridVehicleScreen.kt +++ b/app/src/main/java/io/homeassistant/companion/android/vehicle/EntityGridVehicleScreen.kt @@ -73,10 +73,11 @@ class EntityGridVehicleScreen( fun getEntityGridItems(entities: List>): ItemList.Builder { val manager = carContext.getCarService(ConstraintManager::class.java) val gridLimit = manager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_GRID) - + val shouldSwitchServers = serverManager.defaultServers.size > 1 + val extraGrid = if (shouldSwitchServers) 3 else 2 val listBuilder = ItemList.Builder() entities.forEachIndexed { index, entity -> - if (index >= gridLimit) { + if (index >= (gridLimit - if (isFavorites) extraGrid else 0)) { Log.i(TAG, "Grid limit ($gridLimit) reached, not adding more entities (${entities.size}) for $title ") return@forEachIndexed } @@ -166,7 +167,7 @@ class EntityGridVehicleScreen( } } listBuilder.addItem(categoryItem.build()) - if (serverManager.defaultServers.size > 1) { + if (shouldSwitchServers) { val changeServerItem = GridItem.Builder().apply { setTitle(carContext.getString(R.string.aa_change_server)) setImage( From 9992c1c00747792524bd242e9a110f3483b5251b Mon Sep 17 00:00:00 2001 From: Daniel Shokouhi Date: Fri, 21 Jul 2023 16:54:55 -0700 Subject: [PATCH 06/16] Switch to grid view for home screen, show all domains when there are no favorites for the server --- .../android/vehicle/DomainListScreen.kt | 4 +- .../vehicle/EntityGridVehicleScreen.kt | 74 ++++++++++++------- .../android/vehicle/MainVehicleScreen.kt | 53 +++++-------- 3 files changed, 67 insertions(+), 64 deletions(-) diff --git a/app/src/main/java/io/homeassistant/companion/android/vehicle/DomainListScreen.kt b/app/src/main/java/io/homeassistant/companion/android/vehicle/DomainListScreen.kt index ed32df7e3c1..f3aa0fe360b 100755 --- a/app/src/main/java/io/homeassistant/companion/android/vehicle/DomainListScreen.kt +++ b/app/src/main/java/io/homeassistant/companion/android/vehicle/DomainListScreen.kt @@ -5,7 +5,7 @@ import androidx.annotation.RequiresApi import androidx.car.app.CarContext import androidx.car.app.Screen import androidx.car.app.model.Action -import androidx.car.app.model.ListTemplate +import androidx.car.app.model.GridTemplate import androidx.car.app.model.Template import androidx.lifecycle.lifecycleScope import io.homeassistant.companion.android.BuildConfig @@ -57,7 +57,7 @@ class DomainListScreen( ) { } val domainList = screen.addDomainList(domains) - return ListTemplate.Builder().apply { + return GridTemplate.Builder().apply { setTitle(carContext.getString(R.string.all_entities)) setHeaderAction(Action.BACK) if (screen.isAutomotive && !screen.iDrivingOptimized && BuildConfig.FLAVOR != "full") { diff --git a/app/src/main/java/io/homeassistant/companion/android/vehicle/EntityGridVehicleScreen.kt b/app/src/main/java/io/homeassistant/companion/android/vehicle/EntityGridVehicleScreen.kt index 713843bd3ae..522d3335127 100644 --- a/app/src/main/java/io/homeassistant/companion/android/vehicle/EntityGridVehicleScreen.kt +++ b/app/src/main/java/io/homeassistant/companion/android/vehicle/EntityGridVehicleScreen.kt @@ -44,6 +44,7 @@ class EntityGridVehicleScreen( val prefsRepository: PrefsRepository, val integrationRepository: IntegrationRepository, val title: String, + val domains: MutableSet, private val entitiesFlow: Flow>>, private val allEntities: Flow>>, private val onChangeServer: (Int) -> Unit @@ -76,6 +77,13 @@ class EntityGridVehicleScreen( val shouldSwitchServers = serverManager.defaultServers.size > 1 val extraGrid = if (shouldSwitchServers) 3 else 2 val listBuilder = ItemList.Builder() + val domainGrid = MainVehicleScreen( + carContext, + serverManager, + serverId, + allEntities, + prefsRepository + ) { }.addDomainList(domains) entities.forEachIndexed { index, entity -> if (index >= (gridLimit - if (isFavorites) extraGrid else 0)) { Log.i(TAG, "Grid limit ($gridLimit) reached, not adding more entities (${entities.size}) for $title ") @@ -137,36 +145,42 @@ class EntityGridVehicleScreen( ) } } - listBuilder.addItem(navGridItem.build()) - val categoryItem = GridItem.Builder().apply { - setTitle(carContext.getString(R.string.all_entities)) - setImage( - CarIcon.Builder( - IconicsDrawable( - carContext, - CommunityMaterial.Icon3.cmd_view_list - ).apply { - sizeDp = 48 - }.toAndroidIconCompat() - ) - .setTint(CarColor.DEFAULT) - .build() - ) - setOnClickListener { - Log.i(TAG, "Categories clicked") - screenManager.push( - DomainListScreen( - carContext, - serverManager, - integrationRepository, - serverId, - allEntities, - prefsRepository + if (entities.isNotEmpty()) { + listBuilder.addItem(navGridItem.build()) + } else { + domainGrid.addItem(navGridItem.build()) + } + if (entities.isNotEmpty()) { + val categoryItem = GridItem.Builder().apply { + setTitle(carContext.getString(R.string.all_entities)) + setImage( + CarIcon.Builder( + IconicsDrawable( + carContext, + CommunityMaterial.Icon3.cmd_view_list + ).apply { + sizeDp = 48 + }.toAndroidIconCompat() ) + .setTint(CarColor.DEFAULT) + .build() ) + setOnClickListener { + Log.i(TAG, "Categories clicked") + screenManager.push( + DomainListScreen( + carContext, + serverManager, + integrationRepository, + serverId, + allEntities, + prefsRepository + ) + ) + } } + listBuilder.addItem(categoryItem.build()) } - listBuilder.addItem(categoryItem.build()) if (shouldSwitchServers) { val changeServerItem = GridItem.Builder().apply { setTitle(carContext.getString(R.string.aa_change_server)) @@ -197,10 +211,14 @@ class EntityGridVehicleScreen( } } } - listBuilder.addItem(changeServerItem.build()) + if (entities.isNotEmpty()) { + listBuilder.addItem(changeServerItem.build()) + } else { + domainGrid.addItem(changeServerItem.build()) + } } } - return listBuilder + return if (entities.isNotEmpty()) listBuilder else domainGrid } override fun onGetTemplate(): Template { diff --git a/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt b/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt index 13c70e7bd80..18c142ca46d 100644 --- a/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt +++ b/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt @@ -13,10 +13,9 @@ import androidx.car.app.model.Action import androidx.car.app.model.ActionStrip import androidx.car.app.model.CarColor import androidx.car.app.model.CarIcon +import androidx.car.app.model.GridItem import androidx.car.app.model.GridTemplate import androidx.car.app.model.ItemList -import androidx.car.app.model.ListTemplate -import androidx.car.app.model.Row import androidx.car.app.model.Template import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.Lifecycle @@ -162,6 +161,7 @@ class MainVehicleScreen( prefsRepository, serverManager.integrationRepository(serverId.value), carContext.getString(commonR.string.favorites), + domains, favoriteEntities, allEntities ) { onChangeServer(it) }.getEntityGridItems(entities) @@ -171,7 +171,7 @@ class MainVehicleScreen( } listBuilder.addItem( - Row.Builder() + GridItem.Builder() .setImage( CarIcon.Builder( IconicsDrawable( @@ -200,7 +200,7 @@ class MainVehicleScreen( if (serverManager.defaultServers.size > 1) { listBuilder.addItem( - Row.Builder() + GridItem.Builder() .setImage( CarIcon.Builder( IconicsDrawable( @@ -232,35 +232,19 @@ class MainVehicleScreen( ) } - return if (favoritesList.isEmpty()) { - ListTemplate.Builder().apply { - setTitle(carContext.getString(commonR.string.app_name)) - setHeaderAction(Action.APP_ICON) - if (isAutomotive && !iDrivingOptimized && BuildConfig.FLAVOR != "full") { - setActionStrip(nativeModeActionStrip()) - } - if (domains.isEmpty()) { - setLoading(true) - } else { - setLoading(false) - setSingleList(listBuilder.build()) - } - }.build() - } else { - GridTemplate.Builder().apply { - setTitle(carContext.getString(commonR.string.app_name)) - setHeaderAction(Action.APP_ICON) - if (isAutomotive && !iDrivingOptimized && BuildConfig.FLAVOR != "full") { - setActionStrip(nativeModeActionStrip()) - } - if (domains.isEmpty()) { - setLoading(true) - } else { - setLoading(false) - setSingleList(favoritesGrid!!.build()) - } - }.build() - } + return GridTemplate.Builder().apply { + setTitle(carContext.getString(commonR.string.app_name)) + setHeaderAction(Action.APP_ICON) + if (isAutomotive && !iDrivingOptimized && BuildConfig.FLAVOR != "full") { + setActionStrip(nativeModeActionStrip()) + } + if (domains.isEmpty()) { + setLoading(true) + } else { + setLoading(false) + setSingleList(if (favoritesList.isNotEmpty()) favoritesGrid!!.build() else listBuilder.build()) + } + }.build() } private fun registerAutomotiveRestrictionListener() { @@ -312,7 +296,7 @@ class MainVehicleScreen( ).getIcon(carContext) listBuilder.addItem( - Row.Builder().apply { + GridItem.Builder().apply { if (icon != null) { setImage( CarIcon.Builder( @@ -337,6 +321,7 @@ class MainVehicleScreen( prefsRepository, serverManager.integrationRepository(serverId.value), friendlyDomain, + domains, allEntities.map { it.values.filter { entity -> entity.domain == domain } }, allEntities ) { } From a639d71e10505f4639a1c89fd37e826d348052be Mon Sep 17 00:00:00 2001 From: Daniel Shokouhi Date: Sat, 22 Jul 2023 14:28:42 -0700 Subject: [PATCH 07/16] Review comments --- .../util/vehicle/NativeModeActionStrip.kt | 39 +++ .../android/vehicle/DomainListScreen.kt | 3 +- .../vehicle/EntityGridVehicleScreen.kt | 238 +++++++++--------- .../android/vehicle/MainVehicleScreen.kt | 144 +++++------ 4 files changed, 223 insertions(+), 201 deletions(-) create mode 100755 app/src/main/java/io/homeassistant/companion/android/util/vehicle/NativeModeActionStrip.kt diff --git a/app/src/main/java/io/homeassistant/companion/android/util/vehicle/NativeModeActionStrip.kt b/app/src/main/java/io/homeassistant/companion/android/util/vehicle/NativeModeActionStrip.kt new file mode 100755 index 00000000000..fbc701b630b --- /dev/null +++ b/app/src/main/java/io/homeassistant/companion/android/util/vehicle/NativeModeActionStrip.kt @@ -0,0 +1,39 @@ +package io.homeassistant.companion.android.util.vehicle + +import android.content.Intent +import android.content.pm.PackageManager +import android.util.Log +import androidx.car.app.CarContext +import androidx.car.app.model.Action +import androidx.car.app.model.ActionStrip +import io.homeassistant.companion.android.common.R +import io.homeassistant.companion.android.launch.LaunchActivity + +private const val TAG = "NativeActionStrip" + +fun nativeModeActionStrip(carContext: CarContext): ActionStrip { + return ActionStrip.Builder().addAction( + Action.Builder() + .setTitle(carContext.getString(R.string.aa_launch_native)) + .setOnClickListener { + startNativeActivity(carContext) + }.build() + ).build() +} + +fun startNativeActivity(carContext: CarContext) { + Log.i(TAG, "Starting login activity") + with(carContext) { + startActivity( + Intent( + carContext, + LaunchActivity::class.java + ).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK + } + ) + if (carContext.packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) { + finishCarApp() + } + } +} diff --git a/app/src/main/java/io/homeassistant/companion/android/vehicle/DomainListScreen.kt b/app/src/main/java/io/homeassistant/companion/android/vehicle/DomainListScreen.kt index f3aa0fe360b..07c05e875eb 100755 --- a/app/src/main/java/io/homeassistant/companion/android/vehicle/DomainListScreen.kt +++ b/app/src/main/java/io/homeassistant/companion/android/vehicle/DomainListScreen.kt @@ -15,6 +15,7 @@ import io.homeassistant.companion.android.common.data.integration.IntegrationRep import io.homeassistant.companion.android.common.data.integration.domain import io.homeassistant.companion.android.common.data.prefs.PrefsRepository import io.homeassistant.companion.android.common.data.servers.ServerManager +import io.homeassistant.companion.android.util.vehicle.nativeModeActionStrip import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch @@ -61,7 +62,7 @@ class DomainListScreen( setTitle(carContext.getString(R.string.all_entities)) setHeaderAction(Action.BACK) if (screen.isAutomotive && !screen.iDrivingOptimized && BuildConfig.FLAVOR != "full") { - setActionStrip(screen.nativeModeActionStrip()) + setActionStrip(nativeModeActionStrip(carContext)) } if (domainList.build().items.isEmpty()) { setLoading(true) diff --git a/app/src/main/java/io/homeassistant/companion/android/vehicle/EntityGridVehicleScreen.kt b/app/src/main/java/io/homeassistant/companion/android/vehicle/EntityGridVehicleScreen.kt index 522d3335127..a1614f46a2a 100644 --- a/app/src/main/java/io/homeassistant/companion/android/vehicle/EntityGridVehicleScreen.kt +++ b/app/src/main/java/io/homeassistant/companion/android/vehicle/EntityGridVehicleScreen.kt @@ -44,7 +44,7 @@ class EntityGridVehicleScreen( val prefsRepository: PrefsRepository, val integrationRepository: IntegrationRepository, val title: String, - val domains: MutableSet, + private val domains: MutableSet, private val entitiesFlow: Flow>>, private val allEntities: Flow>>, private val onChangeServer: (Int) -> Unit @@ -57,6 +57,7 @@ class EntityGridVehicleScreen( private var loading = true var entities: List> = listOf() private val isFavorites = title == carContext.getString(R.string.favorites) + private val shouldSwitchServers = serverManager.defaultServers.size > 1 init { lifecycleScope.launch { @@ -72,18 +73,41 @@ class EntityGridVehicleScreen( } fun getEntityGridItems(entities: List>): ItemList.Builder { + val listBuilder = if (entities.isNotEmpty()) { + createEntityGrid(entities) + } else { + createDomainGrid() + } + if (isFavorites) { + listBuilder.addItem(getNavigationGridItem().build()) + listBuilder.addItem(getDomainsGridItem().build()) + if (shouldSwitchServers) { + listBuilder.addItem(getChangeServerGridItem().build()) + } + } + return listBuilder + } + + override fun onGetTemplate(): Template { + val entityGrid = getEntityGridItems(entities) + + return GridTemplate.Builder().apply { + setTitle(title) + setHeaderAction(Action.BACK) + if (loading) { + setLoading(true) + } else { + setLoading(false) + setSingleList(entityGrid.build()) + } + }.build() + } + + private fun createEntityGrid(entities: List>): ItemList.Builder { + val listBuilder = ItemList.Builder() val manager = carContext.getCarService(ConstraintManager::class.java) val gridLimit = manager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_GRID) - val shouldSwitchServers = serverManager.defaultServers.size > 1 val extraGrid = if (shouldSwitchServers) 3 else 2 - val listBuilder = ItemList.Builder() - val domainGrid = MainVehicleScreen( - carContext, - serverManager, - serverId, - allEntities, - prefsRepository - ) { }.addDomainList(domains) entities.forEachIndexed { index, entity -> if (index >= (gridLimit - if (isFavorites) extraGrid else 0)) { Log.i(TAG, "Grid limit ($gridLimit) reached, not adding more entities (${entities.size}) for $title ") @@ -118,121 +142,107 @@ class EntityGridVehicleScreen( } listBuilder.addItem(gridItem.build()) } + return listBuilder + } - if (isFavorites) { - val navGridItem = GridItem.Builder().apply { - setTitle(carContext.getString(R.string.aa_navigation)) - setImage( - CarIcon.Builder( - IconicsDrawable( - carContext, - CommunityMaterial.Icon3.cmd_map_outline - ).apply { - sizeDp = 48 - }.toAndroidIconCompat() - ) - .setTint(CarColor.DEFAULT) - .build() + private fun createDomainGrid(): ItemList.Builder { + return MainVehicleScreen( + carContext, + serverManager, + serverId, + allEntities, + prefsRepository + ) { }.addDomainList(domains) + } + + private fun getChangeServerGridItem(): GridItem.Builder { + return GridItem.Builder().apply { + setTitle(carContext.getString(R.string.aa_change_server)) + setImage( + CarIcon.Builder( + IconicsDrawable( + carContext, + CommunityMaterial.Icon2.cmd_home_switch + ).apply { + sizeDp = 48 + }.toAndroidIconCompat() ) - setOnClickListener { - Log.i(TAG, "Navigation clicked") - screenManager.push( - MapVehicleScreen( - carContext, - integrationRepository, - allEntities.map { it.values.filter { entity -> entity.domain in MainVehicleScreen.MAP_DOMAINS } } - ) + .setTint(CarColor.DEFAULT) + .build() + ) + setOnClickListener { + Log.i(TAG, "Change server clicked") + screenManager.pushForResult( + ChangeServerScreen( + carContext, + serverManager, + serverId ) - } - } - if (entities.isNotEmpty()) { - listBuilder.addItem(navGridItem.build()) - } else { - domainGrid.addItem(navGridItem.build()) - } - if (entities.isNotEmpty()) { - val categoryItem = GridItem.Builder().apply { - setTitle(carContext.getString(R.string.all_entities)) - setImage( - CarIcon.Builder( - IconicsDrawable( - carContext, - CommunityMaterial.Icon3.cmd_view_list - ).apply { - sizeDp = 48 - }.toAndroidIconCompat() - ) - .setTint(CarColor.DEFAULT) - .build() - ) - setOnClickListener { - Log.i(TAG, "Categories clicked") - screenManager.push( - DomainListScreen( - carContext, - serverManager, - integrationRepository, - serverId, - allEntities, - prefsRepository - ) - ) + ) { + it?.toString()?.toIntOrNull()?.let { serverId -> + onChangeServer(serverId) } } - listBuilder.addItem(categoryItem.build()) } - if (shouldSwitchServers) { - val changeServerItem = GridItem.Builder().apply { - setTitle(carContext.getString(R.string.aa_change_server)) - setImage( - CarIcon.Builder( - IconicsDrawable( - carContext, - CommunityMaterial.Icon2.cmd_home_switch - ).apply { - sizeDp = 48 - }.toAndroidIconCompat() - ) - .setTint(CarColor.DEFAULT) - .build() + } + } + + private fun getDomainsGridItem(): GridItem.Builder { + return GridItem.Builder().apply { + setTitle(carContext.getString(R.string.all_entities)) + setImage( + CarIcon.Builder( + IconicsDrawable( + carContext, + CommunityMaterial.Icon3.cmd_view_list + ).apply { + sizeDp = 48 + }.toAndroidIconCompat() + ) + .setTint(CarColor.DEFAULT) + .build() + ) + setOnClickListener { + Log.i(TAG, "Categories clicked") + screenManager.push( + DomainListScreen( + carContext, + serverManager, + integrationRepository, + serverId, + allEntities, + prefsRepository ) - setOnClickListener { - Log.i(TAG, "Change server clicked") - screenManager.pushForResult( - ChangeServerScreen( - carContext, - serverManager, - serverId - ) - ) { - it?.toString()?.toIntOrNull()?.let { serverId -> - onChangeServer(serverId) - } - } - } - } - if (entities.isNotEmpty()) { - listBuilder.addItem(changeServerItem.build()) - } else { - domainGrid.addItem(changeServerItem.build()) - } + ) } } - return if (entities.isNotEmpty()) listBuilder else domainGrid } - override fun onGetTemplate(): Template { - val entityGrid = getEntityGridItems(entities) - - return GridTemplate.Builder().apply { - setTitle(title) - setHeaderAction(Action.BACK) - if (loading) { - setLoading(true) - } else { - setLoading(false) - setSingleList(entityGrid.build()) + private fun getNavigationGridItem(): GridItem.Builder { + return GridItem.Builder().apply { + setTitle(carContext.getString(R.string.aa_navigation)) + setImage( + CarIcon.Builder( + IconicsDrawable( + carContext, + CommunityMaterial.Icon3.cmd_map_outline + ).apply { + sizeDp = 48 + }.toAndroidIconCompat() + ) + .setTint(CarColor.DEFAULT) + .build() + ) + setOnClickListener { + Log.i(TAG, "Navigation clicked") + screenManager.push( + MapVehicleScreen( + carContext, + integrationRepository, + allEntities.map { it.values.filter { entity -> entity.domain in MainVehicleScreen.MAP_DOMAINS } } + ) + ) } - }.build() + } } } diff --git a/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt b/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt index 18c142ca46d..64f2738bb17 100644 --- a/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt +++ b/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt @@ -2,7 +2,6 @@ package io.homeassistant.companion.android.vehicle import android.car.Car import android.car.drivingstate.CarUxRestrictionsManager -import android.content.Intent import android.content.pm.PackageManager import android.os.Build import android.util.Log @@ -10,7 +9,6 @@ import androidx.annotation.RequiresApi import androidx.car.app.CarContext import androidx.car.app.Screen import androidx.car.app.model.Action -import androidx.car.app.model.ActionStrip import androidx.car.app.model.CarColor import androidx.car.app.model.CarIcon import androidx.car.app.model.GridItem @@ -34,7 +32,7 @@ import io.homeassistant.companion.android.common.data.integration.getIcon import io.homeassistant.companion.android.common.data.prefs.PrefsRepository import io.homeassistant.companion.android.common.data.servers.ServerManager import io.homeassistant.companion.android.common.util.capitalize -import io.homeassistant.companion.android.launch.LaunchActivity +import io.homeassistant.companion.android.util.vehicle.nativeModeActionStrip import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow @@ -80,7 +78,7 @@ class MainVehicleScreen( } private var favoriteEntities = flowOf>>() - private var entities: List> = listOf() + private var entityList: List> = listOf() private var favoritesList = emptyList() private var isLoggedIn: Boolean? = null private val domains = mutableSetOf() @@ -130,8 +128,8 @@ class MainVehicleScreen( .sortedBy { entity -> favoritesList.indexOf("${serverId.value}-${entity.entityId}") } } favoriteEntities.collect { - val hasChanged = entities.size != it.size || entities.toSet() != it.toSet() - entities = it + val hasChanged = entityList.size != it.size || entityList.toSet() != it.toSet() + entityList = it if (hasChanged) invalidate() } } @@ -151,10 +149,8 @@ class MainVehicleScreen( } override fun onGetTemplate(): Template { - var listBuilder = ItemList.Builder() - var favoritesGrid: ItemList.Builder? = null - if (favoritesList.isNotEmpty()) { - favoritesGrid = EntityGridVehicleScreen( + val listBuilder = if (favoritesList.isNotEmpty()) { + EntityGridVehicleScreen( carContext, serverManager, serverId, @@ -164,48 +160,20 @@ class MainVehicleScreen( domains, favoriteEntities, allEntities - ) { onChangeServer(it) }.getEntityGridItems(entities) - } - if (domains.isNotEmpty()) { - listBuilder = addDomainList(domains) - } - - listBuilder.addItem( - GridItem.Builder() - .setImage( - CarIcon.Builder( - IconicsDrawable( - carContext, - CommunityMaterial.Icon3.cmd_map_outline - ).apply { - sizeDp = 48 - }.toAndroidIconCompat() - ) - .setTint(CarColor.DEFAULT) - .build() - ) - .setTitle(carContext.getString(commonR.string.aa_navigation)) - .setOnClickListener { - Log.i(TAG, "Navigation clicked") - screenManager.push( - MapVehicleScreen( - carContext, - serverManager.integrationRepository(serverId.value), - allEntities.map { it.values.filter { entity -> entity.domain in MAP_DOMAINS } } - ) - ) - } - .build() - ) + ) { onChangeServer(it) }.getEntityGridItems(entityList) + } else { + var builder = ItemList.Builder() + if (domains.isNotEmpty()) { + builder = addDomainList(domains) + } - if (serverManager.defaultServers.size > 1) { - listBuilder.addItem( + builder.addItem( GridItem.Builder() .setImage( CarIcon.Builder( IconicsDrawable( carContext, - CommunityMaterial.Icon2.cmd_home_switch + CommunityMaterial.Icon3.cmd_map_outline ).apply { sizeDp = 48 }.toAndroidIconCompat() @@ -213,36 +181,67 @@ class MainVehicleScreen( .setTint(CarColor.DEFAULT) .build() ) - .setTitle(carContext.getString(commonR.string.aa_change_server)) + .setTitle(carContext.getString(commonR.string.aa_navigation)) .setOnClickListener { - Log.i(TAG, "Change server clicked") - screenManager.pushForResult( - ChangeServerScreen( + Log.i(TAG, "Navigation clicked") + screenManager.push( + MapVehicleScreen( carContext, - serverManager, - serverId + serverManager.integrationRepository(serverId.value), + allEntities.map { it.values.filter { entity -> entity.domain in MAP_DOMAINS } } ) - ) { - it?.toString()?.toIntOrNull()?.let { serverId -> - onChangeServer(serverId) - } - } + ) } .build() ) + + if (serverManager.defaultServers.size > 1) { + builder.addItem( + GridItem.Builder() + .setImage( + CarIcon.Builder( + IconicsDrawable( + carContext, + CommunityMaterial.Icon2.cmd_home_switch + ).apply { + sizeDp = 48 + }.toAndroidIconCompat() + ) + .setTint(CarColor.DEFAULT) + .build() + ) + .setTitle(carContext.getString(commonR.string.aa_change_server)) + .setOnClickListener { + Log.i(TAG, "Change server clicked") + screenManager.pushForResult( + ChangeServerScreen( + carContext, + serverManager, + serverId + ) + ) { + it?.toString()?.toIntOrNull()?.let { serverId -> + onChangeServer(serverId) + } + } + } + .build() + ) + } + builder } return GridTemplate.Builder().apply { setTitle(carContext.getString(commonR.string.app_name)) setHeaderAction(Action.APP_ICON) if (isAutomotive && !iDrivingOptimized && BuildConfig.FLAVOR != "full") { - setActionStrip(nativeModeActionStrip()) + setActionStrip(nativeModeActionStrip(carContext)) } if (domains.isEmpty()) { setLoading(true) } else { setLoading(false) - setSingleList(if (favoritesList.isNotEmpty()) favoritesGrid!!.build() else listBuilder.build()) + setSingleList(listBuilder.build()) } }.build() } @@ -261,23 +260,6 @@ class MainVehicleScreen( } } - fun startNativeActivity() { - Log.i(TAG, "Starting login activity") - with(carContext) { - startActivity( - Intent( - carContext, - LaunchActivity::class.java - ).apply { - flags = Intent.FLAG_ACTIVITY_NEW_TASK - } - ) - if (isAutomotive) { - finishCarApp() - } - } - } - fun addDomainList(domains: MutableSet): ItemList.Builder { val listBuilder = ItemList.Builder() domains.forEach { domain -> @@ -332,14 +314,4 @@ class MainVehicleScreen( } return listBuilder } - - fun nativeModeActionStrip(): ActionStrip { - return ActionStrip.Builder().addAction( - Action.Builder() - .setTitle(carContext.getString(commonR.string.aa_launch_native)) - .setOnClickListener { - startNativeActivity() - }.build() - ).build() - } } From dc2625c7ac4194e488b51c521399db52a5adb6b4 Mon Sep 17 00:00:00 2001 From: Daniel Shokouhi Date: Sat, 22 Jul 2023 17:08:39 -0700 Subject: [PATCH 08/16] Clean up and move some methods to util --- .../android/util/vehicle/GridItems.kt | 211 ++++++++++++++++++ .../android/vehicle/DomainListScreen.kt | 11 +- .../vehicle/EntityGridVehicleScreen.kt | 147 ++++-------- .../android/vehicle/MainVehicleScreen.kt | 147 +++--------- 4 files changed, 286 insertions(+), 230 deletions(-) create mode 100755 app/src/main/java/io/homeassistant/companion/android/util/vehicle/GridItems.kt diff --git a/app/src/main/java/io/homeassistant/companion/android/util/vehicle/GridItems.kt b/app/src/main/java/io/homeassistant/companion/android/util/vehicle/GridItems.kt new file mode 100755 index 00000000000..30ae608a488 --- /dev/null +++ b/app/src/main/java/io/homeassistant/companion/android/util/vehicle/GridItems.kt @@ -0,0 +1,211 @@ +package io.homeassistant.companion.android.util.vehicle + +import android.os.Build +import android.util.Log +import androidx.annotation.RequiresApi +import androidx.car.app.CarContext +import androidx.car.app.ScreenManager +import androidx.car.app.model.CarColor +import androidx.car.app.model.CarIcon +import androidx.car.app.model.GridItem +import androidx.car.app.model.ItemList +import com.mikepenz.iconics.IconicsDrawable +import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial +import com.mikepenz.iconics.utils.sizeDp +import com.mikepenz.iconics.utils.toAndroidIconCompat +import io.homeassistant.companion.android.common.R +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.common.data.prefs.PrefsRepository +import io.homeassistant.companion.android.common.data.servers.ServerManager +import io.homeassistant.companion.android.common.util.capitalize +import io.homeassistant.companion.android.vehicle.ChangeServerScreen +import io.homeassistant.companion.android.vehicle.DomainListScreen +import io.homeassistant.companion.android.vehicle.EntityGridVehicleScreen +import io.homeassistant.companion.android.vehicle.MainVehicleScreen +import io.homeassistant.companion.android.vehicle.MapVehicleScreen +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import java.util.Calendar +import java.util.Locale + +private const val TAG = "GridItems" + +fun getChangeServerGridItem( + carContext: CarContext, + screenManager: ScreenManager, + serverManager: ServerManager, + serverId: StateFlow, + onChangeServer: (Int) -> Unit +): GridItem.Builder { + return GridItem.Builder().apply { + setTitle(carContext.getString(R.string.aa_change_server)) + setImage( + CarIcon.Builder( + IconicsDrawable( + carContext, + CommunityMaterial.Icon2.cmd_home_switch + ).apply { + sizeDp = 48 + }.toAndroidIconCompat() + ) + .setTint(CarColor.DEFAULT) + .build() + ) + setOnClickListener { + Log.i(TAG, "Change server clicked") + screenManager.pushForResult( + ChangeServerScreen( + carContext, + serverManager, + serverId + ) + ) { + it?.toString()?.toIntOrNull()?.let { serverId -> + onChangeServer(serverId) + } + } + } + } +} + +@RequiresApi(Build.VERSION_CODES.O) +fun getNavigationGridItem( + carContext: CarContext, + screenManager: ScreenManager, + integrationRepository: IntegrationRepository, + allEntities: Flow>> +): GridItem.Builder { + return GridItem.Builder().apply { + setTitle(carContext.getString(R.string.aa_navigation)) + setImage( + CarIcon.Builder( + IconicsDrawable( + carContext, + CommunityMaterial.Icon3.cmd_map_outline + ).apply { + sizeDp = 48 + }.toAndroidIconCompat() + ) + .setTint(CarColor.DEFAULT) + .build() + ) + setOnClickListener { + Log.i(TAG, "Navigation clicked") + screenManager.push( + MapVehicleScreen( + carContext, + integrationRepository, + allEntities.map { it.values.filter { entity -> entity.domain in MainVehicleScreen.MAP_DOMAINS } } + ) + ) + } + } +} + +@RequiresApi(Build.VERSION_CODES.O) +fun getDomainList( + domains: MutableSet, + carContext: CarContext, + screenManager: ScreenManager, + serverManager: ServerManager, + serverId: StateFlow, + prefsRepository: PrefsRepository, + allEntities: Flow>> +): ItemList.Builder { + val listBuilder = ItemList.Builder() + domains.forEach { domain -> + val friendlyDomain = + MainVehicleScreen.SUPPORTED_DOMAINS_WITH_STRING[domain]?.let { carContext.getString(it) } + ?: domain.split("_").joinToString(" ") { word -> + word.capitalize(Locale.getDefault()) + } + val icon = Entity( + "$domain.ha_android_placeholder", + "", + mapOf(), + Calendar.getInstance(), + Calendar.getInstance(), + null + ).getIcon(carContext) + + listBuilder.addItem( + GridItem.Builder().apply { + if (icon != null) { + setImage( + CarIcon.Builder( + IconicsDrawable(carContext, icon) + .apply { + sizeDp = 48 + }.toAndroidIconCompat() + ) + .setTint(CarColor.DEFAULT) + .build() + ) + } + } + .setTitle(friendlyDomain) + .setOnClickListener { + Log.i(TAG, "Domain:$domain clicked") + screenManager.push( + EntityGridVehicleScreen( + carContext, + serverManager, + serverId, + prefsRepository, + serverManager.integrationRepository(serverId.value), + friendlyDomain, + domains, + allEntities.map { it.values.filter { entity -> entity.domain == domain } }, + allEntities + ) { } + ) + } + .build() + ) + } + return listBuilder +} + +@RequiresApi(Build.VERSION_CODES.O) +fun getDomainsGridItem( + carContext: CarContext, + screenManager: ScreenManager, + serverManager: ServerManager, + integrationRepository: IntegrationRepository, + serverId: StateFlow, + allEntities: Flow>>, + prefsRepository: PrefsRepository +): GridItem.Builder { + return GridItem.Builder().apply { + setTitle(carContext.getString(R.string.all_entities)) + setImage( + CarIcon.Builder( + IconicsDrawable( + carContext, + CommunityMaterial.Icon3.cmd_view_list + ).apply { + sizeDp = 48 + }.toAndroidIconCompat() + ) + .setTint(CarColor.DEFAULT) + .build() + ) + setOnClickListener { + Log.i(TAG, "Categories clicked") + screenManager.push( + DomainListScreen( + carContext, + serverManager, + integrationRepository, + serverId, + allEntities, + prefsRepository + ) + ) + } + } +} diff --git a/app/src/main/java/io/homeassistant/companion/android/vehicle/DomainListScreen.kt b/app/src/main/java/io/homeassistant/companion/android/vehicle/DomainListScreen.kt index 07c05e875eb..d18dc7c3d72 100755 --- a/app/src/main/java/io/homeassistant/companion/android/vehicle/DomainListScreen.kt +++ b/app/src/main/java/io/homeassistant/companion/android/vehicle/DomainListScreen.kt @@ -15,6 +15,7 @@ import io.homeassistant.companion.android.common.data.integration.IntegrationRep import io.homeassistant.companion.android.common.data.integration.domain import io.homeassistant.companion.android.common.data.prefs.PrefsRepository import io.homeassistant.companion.android.common.data.servers.ServerManager +import io.homeassistant.companion.android.util.vehicle.getDomainList import io.homeassistant.companion.android.util.vehicle.nativeModeActionStrip import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow @@ -56,7 +57,15 @@ class DomainListScreen( allEntities, prefsRepository ) { } - val domainList = screen.addDomainList(domains) + val domainList = getDomainList( + domains, + carContext, + screenManager, + serverManager, + serverId, + prefsRepository, + allEntities + ) return GridTemplate.Builder().apply { setTitle(carContext.getString(R.string.all_entities)) diff --git a/app/src/main/java/io/homeassistant/companion/android/vehicle/EntityGridVehicleScreen.kt b/app/src/main/java/io/homeassistant/companion/android/vehicle/EntityGridVehicleScreen.kt index a1614f46a2a..2aef7ce83f8 100644 --- a/app/src/main/java/io/homeassistant/companion/android/vehicle/EntityGridVehicleScreen.kt +++ b/app/src/main/java/io/homeassistant/companion/android/vehicle/EntityGridVehicleScreen.kt @@ -23,7 +23,6 @@ import com.mikepenz.iconics.utils.toAndroidIconCompat import io.homeassistant.companion.android.common.R 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.friendlyName import io.homeassistant.companion.android.common.data.integration.friendlyState import io.homeassistant.companion.android.common.data.integration.getIcon @@ -31,9 +30,12 @@ import io.homeassistant.companion.android.common.data.integration.isExecuting import io.homeassistant.companion.android.common.data.integration.onPressed import io.homeassistant.companion.android.common.data.prefs.PrefsRepository import io.homeassistant.companion.android.common.data.servers.ServerManager +import io.homeassistant.companion.android.util.vehicle.getChangeServerGridItem +import io.homeassistant.companion.android.util.vehicle.getDomainList +import io.homeassistant.companion.android.util.vehicle.getDomainsGridItem +import io.homeassistant.companion.android.util.vehicle.getNavigationGridItem import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @RequiresApi(Build.VERSION_CODES.O) @@ -76,13 +78,45 @@ class EntityGridVehicleScreen( val listBuilder = if (entities.isNotEmpty()) { createEntityGrid(entities) } else { - createDomainGrid() + getDomainList( + domains, + carContext, + screenManager, + serverManager, + serverId, + prefsRepository, + allEntities + ) } if (isFavorites) { - listBuilder.addItem(getNavigationGridItem().build()) - listBuilder.addItem(getDomainsGridItem().build()) + listBuilder.addItem( + getNavigationGridItem( + carContext, + screenManager, + integrationRepository, + allEntities + ).build() + ) + listBuilder.addItem( + getDomainsGridItem( + carContext, + screenManager, + serverManager, + integrationRepository, + serverId, + allEntities, + prefsRepository + ).build() + ) if (shouldSwitchServers) { - listBuilder.addItem(getChangeServerGridItem().build()) + listBuilder.addItem( + getChangeServerGridItem( + carContext, + screenManager, + serverManager, + serverId + ) { onChangeServer(it) }.build() + ) } } return listBuilder @@ -144,105 +178,4 @@ class EntityGridVehicleScreen( } return listBuilder } - - private fun createDomainGrid(): ItemList.Builder { - return MainVehicleScreen( - carContext, - serverManager, - serverId, - allEntities, - prefsRepository - ) { }.addDomainList(domains) - } - - private fun getChangeServerGridItem(): GridItem.Builder { - return GridItem.Builder().apply { - setTitle(carContext.getString(R.string.aa_change_server)) - setImage( - CarIcon.Builder( - IconicsDrawable( - carContext, - CommunityMaterial.Icon2.cmd_home_switch - ).apply { - sizeDp = 48 - }.toAndroidIconCompat() - ) - .setTint(CarColor.DEFAULT) - .build() - ) - setOnClickListener { - Log.i(TAG, "Change server clicked") - screenManager.pushForResult( - ChangeServerScreen( - carContext, - serverManager, - serverId - ) - ) { - it?.toString()?.toIntOrNull()?.let { serverId -> - onChangeServer(serverId) - } - } - } - } - } - - private fun getDomainsGridItem(): GridItem.Builder { - return GridItem.Builder().apply { - setTitle(carContext.getString(R.string.all_entities)) - setImage( - CarIcon.Builder( - IconicsDrawable( - carContext, - CommunityMaterial.Icon3.cmd_view_list - ).apply { - sizeDp = 48 - }.toAndroidIconCompat() - ) - .setTint(CarColor.DEFAULT) - .build() - ) - setOnClickListener { - Log.i(TAG, "Categories clicked") - screenManager.push( - DomainListScreen( - carContext, - serverManager, - integrationRepository, - serverId, - allEntities, - prefsRepository - ) - ) - } - } - } - - private fun getNavigationGridItem(): GridItem.Builder { - return GridItem.Builder().apply { - setTitle(carContext.getString(R.string.aa_navigation)) - setImage( - CarIcon.Builder( - IconicsDrawable( - carContext, - CommunityMaterial.Icon3.cmd_map_outline - ).apply { - sizeDp = 48 - }.toAndroidIconCompat() - ) - .setTint(CarColor.DEFAULT) - .build() - ) - setOnClickListener { - Log.i(TAG, "Navigation clicked") - screenManager.push( - MapVehicleScreen( - carContext, - integrationRepository, - allEntities.map { it.values.filter { entity -> entity.domain in MainVehicleScreen.MAP_DOMAINS } } - ) - ) - } - } - } } diff --git a/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt b/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt index 64f2738bb17..a789db6271b 100644 --- a/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt +++ b/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt @@ -9,9 +9,6 @@ import androidx.annotation.RequiresApi import androidx.car.app.CarContext import androidx.car.app.Screen import androidx.car.app.model.Action -import androidx.car.app.model.CarColor -import androidx.car.app.model.CarIcon -import androidx.car.app.model.GridItem import androidx.car.app.model.GridTemplate import androidx.car.app.model.ItemList import androidx.car.app.model.Template @@ -20,18 +17,15 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle -import com.mikepenz.iconics.IconicsDrawable -import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial -import com.mikepenz.iconics.utils.sizeDp -import com.mikepenz.iconics.utils.toAndroidIconCompat import io.homeassistant.companion.android.BuildConfig import io.homeassistant.companion.android.common.data.authentication.SessionState 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.common.data.prefs.PrefsRepository import io.homeassistant.companion.android.common.data.servers.ServerManager -import io.homeassistant.companion.android.common.util.capitalize +import io.homeassistant.companion.android.util.vehicle.getChangeServerGridItem +import io.homeassistant.companion.android.util.vehicle.getDomainList +import io.homeassistant.companion.android.util.vehicle.getNavigationGridItem import io.homeassistant.companion.android.util.vehicle.nativeModeActionStrip import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow @@ -39,8 +33,6 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch -import java.util.Calendar -import java.util.Locale import io.homeassistant.companion.android.common.R as commonR @RequiresApi(Build.VERSION_CODES.O) @@ -56,7 +48,7 @@ class MainVehicleScreen( companion object { private const val TAG = "MainVehicleScreen" - private val SUPPORTED_DOMAINS_WITH_STRING = mapOf( + val SUPPORTED_DOMAINS_WITH_STRING = mapOf( "button" to commonR.string.buttons, "cover" to commonR.string.covers, "input_boolean" to commonR.string.input_booleans, @@ -164,68 +156,34 @@ class MainVehicleScreen( } else { var builder = ItemList.Builder() if (domains.isNotEmpty()) { - builder = addDomainList(domains) + builder = getDomainList( + domains, + carContext, + screenManager, + serverManager, + serverId, + prefsRepository, + allEntities + ) } builder.addItem( - GridItem.Builder() - .setImage( - CarIcon.Builder( - IconicsDrawable( - carContext, - CommunityMaterial.Icon3.cmd_map_outline - ).apply { - sizeDp = 48 - }.toAndroidIconCompat() - ) - .setTint(CarColor.DEFAULT) - .build() - ) - .setTitle(carContext.getString(commonR.string.aa_navigation)) - .setOnClickListener { - Log.i(TAG, "Navigation clicked") - screenManager.push( - MapVehicleScreen( - carContext, - serverManager.integrationRepository(serverId.value), - allEntities.map { it.values.filter { entity -> entity.domain in MAP_DOMAINS } } - ) - ) - } - .build() + getNavigationGridItem( + carContext, + screenManager, + serverManager.integrationRepository(serverId.value), + allEntities + ).build() ) if (serverManager.defaultServers.size > 1) { builder.addItem( - GridItem.Builder() - .setImage( - CarIcon.Builder( - IconicsDrawable( - carContext, - CommunityMaterial.Icon2.cmd_home_switch - ).apply { - sizeDp = 48 - }.toAndroidIconCompat() - ) - .setTint(CarColor.DEFAULT) - .build() - ) - .setTitle(carContext.getString(commonR.string.aa_change_server)) - .setOnClickListener { - Log.i(TAG, "Change server clicked") - screenManager.pushForResult( - ChangeServerScreen( - carContext, - serverManager, - serverId - ) - ) { - it?.toString()?.toIntOrNull()?.let { serverId -> - onChangeServer(serverId) - } - } - } - .build() + getChangeServerGridItem( + carContext, + screenManager, + serverManager, + serverId + ) { onChangeServer(it) }.build() ) } builder @@ -259,59 +217,4 @@ class MainVehicleScreen( carRestrictionManager?.registerListener(listener) } } - - fun addDomainList(domains: MutableSet): ItemList.Builder { - val listBuilder = ItemList.Builder() - domains.forEach { domain -> - val friendlyDomain = - SUPPORTED_DOMAINS_WITH_STRING[domain]?.let { carContext.getString(it) } - ?: domain.split("_").joinToString(" ") { word -> - word.capitalize(Locale.getDefault()) - } - val icon = Entity( - "$domain.ha_android_placeholder", - "", - mapOf(), - Calendar.getInstance(), - Calendar.getInstance(), - null - ).getIcon(carContext) - - listBuilder.addItem( - GridItem.Builder().apply { - if (icon != null) { - setImage( - CarIcon.Builder( - IconicsDrawable(carContext, icon) - .apply { - sizeDp = 48 - }.toAndroidIconCompat() - ) - .setTint(CarColor.DEFAULT) - .build() - ) - } - } - .setTitle(friendlyDomain) - .setOnClickListener { - Log.i(TAG, "Domain:$domain clicked") - screenManager.push( - EntityGridVehicleScreen( - carContext, - serverManager, - serverId, - prefsRepository, - serverManager.integrationRepository(serverId.value), - friendlyDomain, - domains, - allEntities.map { it.values.filter { entity -> entity.domain == domain } }, - allEntities - ) { } - ) - } - .build() - ) - } - return listBuilder - } } From e1ffdf8a1406b363e7dcf6c9901196e836a25991 Mon Sep 17 00:00:00 2001 From: Daniel Shokouhi Date: Sat, 22 Jul 2023 23:47:15 -0700 Subject: [PATCH 09/16] Collect favorites with allEntities --- .../android/vehicle/MainVehicleScreen.kt | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt b/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt index a789db6271b..5a6ef8f9590 100644 --- a/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt +++ b/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt @@ -111,18 +111,9 @@ class MainVehicleScreen( domains.addAll(newDomains) invalidate() } + entityList = getFavoritesList(entities) } - } - } - lifecycleScope.launch { - favoriteEntities = allEntities.map { - it.values.filter { entity -> favoritesList.contains("${serverId.value}-${entity.entityId}") } - .sortedBy { entity -> favoritesList.indexOf("${serverId.value}-${entity.entityId}") } - } - favoriteEntities.collect { - val hasChanged = entityList.size != it.size || entityList.toSet() != it.toSet() - entityList = it - if (hasChanged) invalidate() + favoriteEntities = allEntities.map { getFavoritesList(it) } } } @@ -217,4 +208,9 @@ class MainVehicleScreen( carRestrictionManager?.registerListener(listener) } } + + private fun getFavoritesList(entities: Map>): List> { + return entities.values.filter { entity -> favoritesList.contains("${serverId.value}-${entity.entityId}") } + .sortedBy { entity -> favoritesList.indexOf("${serverId.value}-${entity.entityId}") } + } } From 236072f7c2390ae4dd8acf79b16a11fcec2ebb11 Mon Sep 17 00:00:00 2001 From: Daniel Shokouhi Date: Mon, 24 Jul 2023 13:31:35 -0700 Subject: [PATCH 10/16] Review comments --- .../android/vehicle/DomainListScreen.kt | 16 +++----- .../android/vehicle/HaCarAppService.kt | 30 +++++++++++++++ .../android/vehicle/MainVehicleScreen.kt | 37 ++++--------------- 3 files changed, 43 insertions(+), 40 deletions(-) diff --git a/app/src/main/java/io/homeassistant/companion/android/vehicle/DomainListScreen.kt b/app/src/main/java/io/homeassistant/companion/android/vehicle/DomainListScreen.kt index d18dc7c3d72..cda2bf357a2 100755 --- a/app/src/main/java/io/homeassistant/companion/android/vehicle/DomainListScreen.kt +++ b/app/src/main/java/io/homeassistant/companion/android/vehicle/DomainListScreen.kt @@ -1,5 +1,6 @@ package io.homeassistant.companion.android.vehicle +import android.content.pm.PackageManager import android.os.Build import androidx.annotation.RequiresApi import androidx.car.app.CarContext @@ -50,13 +51,7 @@ class DomainListScreen( } } override fun onGetTemplate(): Template { - val screen = MainVehicleScreen( - carContext, - serverManager, - serverId, - allEntities, - prefsRepository - ) { } + val isAutomotive = carContext.packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) val domainList = getDomainList( domains, carContext, @@ -70,14 +65,15 @@ class DomainListScreen( return GridTemplate.Builder().apply { setTitle(carContext.getString(R.string.all_entities)) setHeaderAction(Action.BACK) - if (screen.isAutomotive && !screen.iDrivingOptimized && BuildConfig.FLAVOR != "full") { + if (isAutomotive && !HaCarAppService().isDrivingOptimized && BuildConfig.FLAVOR != "full") { setActionStrip(nativeModeActionStrip(carContext)) } - if (domainList.build().items.isEmpty()) { + val domainBuild = domainList.build() + if (domainBuild.items.isEmpty()) { setLoading(true) } else { setLoading(false) - setSingleList(domainList.build()) + setSingleList(domainBuild) } }.build() } diff --git a/app/src/main/java/io/homeassistant/companion/android/vehicle/HaCarAppService.kt b/app/src/main/java/io/homeassistant/companion/android/vehicle/HaCarAppService.kt index a560ceea376..9f20703036d 100644 --- a/app/src/main/java/io/homeassistant/companion/android/vehicle/HaCarAppService.kt +++ b/app/src/main/java/io/homeassistant/companion/android/vehicle/HaCarAppService.kt @@ -1,5 +1,7 @@ package io.homeassistant.companion.android.vehicle +import android.car.Car +import android.car.drivingstate.CarUxRestrictionsManager import android.content.Intent import android.content.pm.ApplicationInfo import android.os.Build @@ -13,6 +15,8 @@ import androidx.car.app.SessionInfo import androidx.car.app.hardware.CarHardwareManager import androidx.car.app.hardware.info.CarInfo import androidx.car.app.validation.HostValidator +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope import dagger.hilt.android.AndroidEntryPoint import io.homeassistant.companion.android.R @@ -48,6 +52,14 @@ class HaCarAppService : CarAppService() { private val serverId = MutableStateFlow(0) private val allEntities = MutableStateFlow>>(emptyMap()) private var allEntitiesJob: Job? = null + var car: Car? = null + var carRestrictionManager: CarUxRestrictionsManager? = null + val isDrivingOptimized + get() = car?.let { + ( + it.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE) as CarUxRestrictionsManager + ).getCurrentCarUxRestrictions().isRequiresDistractionOptimization() + } ?: false override fun createHostValidator(): HostValidator { return if (applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE != 0) { @@ -65,6 +77,24 @@ class HaCarAppService : CarAppService() { serverManager.getServer()?.let { loadEntities(lifecycleScope, it.id) } + lifecycle.addObserver(object : DefaultLifecycleObserver { + + override fun onResume(owner: LifecycleOwner) { + MainVehicleScreen( + carContext, + serverManager, + serverIdFlow, + entityFlow, + prefsRepository + ) { loadEntities(lifecycleScope, it) }.registerAutomotiveRestrictionListener() + } + + override fun onPause(owner: LifecycleOwner) { + HaCarAppService().carRestrictionManager?.unregisterListener() + HaCarAppService().car?.disconnect() + HaCarAppService().car = null + } + }) } val serverIdFlow = serverId.asStateFlow() diff --git a/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt b/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt index 5a6ef8f9590..6e21d8af290 100644 --- a/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt +++ b/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt @@ -12,9 +12,7 @@ import androidx.car.app.model.Action import androidx.car.app.model.GridTemplate import androidx.car.app.model.ItemList import androidx.car.app.model.Template -import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import io.homeassistant.companion.android.BuildConfig @@ -74,16 +72,8 @@ class MainVehicleScreen( private var favoritesList = emptyList() private var isLoggedIn: Boolean? = null private val domains = mutableSetOf() - private var car: Car? = null - private var carRestrictionManager: CarUxRestrictionsManager? = null - val iDrivingOptimized - get() = car?.let { - ( - it.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE) as CarUxRestrictionsManager - ).getCurrentCarUxRestrictions().isRequiresDistractionOptimization() - } ?: false - val isAutomotive get() = carContext.packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) + private val isAutomotive get() = carContext.packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) init { lifecycleScope.launch { @@ -116,19 +106,6 @@ class MainVehicleScreen( favoriteEntities = allEntities.map { getFavoritesList(it) } } } - - lifecycle.addObserver(object : DefaultLifecycleObserver { - - override fun onResume(owner: LifecycleOwner) { - registerAutomotiveRestrictionListener() - } - - override fun onPause(owner: LifecycleOwner) { - carRestrictionManager?.unregisterListener() - car?.disconnect() - car = null - } - }) } override fun onGetTemplate(): Template { @@ -183,7 +160,7 @@ class MainVehicleScreen( return GridTemplate.Builder().apply { setTitle(carContext.getString(commonR.string.app_name)) setHeaderAction(Action.APP_ICON) - if (isAutomotive && !iDrivingOptimized && BuildConfig.FLAVOR != "full") { + if (isAutomotive && !HaCarAppService().isDrivingOptimized && BuildConfig.FLAVOR != "full") { setActionStrip(nativeModeActionStrip(carContext)) } if (domains.isEmpty()) { @@ -195,17 +172,17 @@ class MainVehicleScreen( }.build() } - private fun registerAutomotiveRestrictionListener() { + fun registerAutomotiveRestrictionListener() { if (carContext.packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) { Log.i(TAG, "Register for Automotive Restrictions") - car = Car.createCar(carContext) - carRestrictionManager = - car?.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE) as CarUxRestrictionsManager + HaCarAppService().car = Car.createCar(carContext) + HaCarAppService().carRestrictionManager = + HaCarAppService().car?.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE) as CarUxRestrictionsManager val listener = CarUxRestrictionsManager.OnUxRestrictionsChangedListener { restrictions -> invalidate() } - carRestrictionManager?.registerListener(listener) + HaCarAppService().carRestrictionManager?.registerListener(listener) } } From de62afb0b1e4a6393393119094c572aafef6f718 Mon Sep 17 00:00:00 2001 From: Daniel Shokouhi Date: Mon, 24 Jul 2023 14:32:10 -0700 Subject: [PATCH 11/16] Move native mode logic back --- .../android/vehicle/DomainListScreen.kt | 9 ++++- .../android/vehicle/HaCarAppService.kt | 30 ----------------- .../android/vehicle/MainVehicleScreen.kt | 33 ++++++++++++++++--- 3 files changed, 36 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/io/homeassistant/companion/android/vehicle/DomainListScreen.kt b/app/src/main/java/io/homeassistant/companion/android/vehicle/DomainListScreen.kt index cda2bf357a2..da49f28d1f5 100755 --- a/app/src/main/java/io/homeassistant/companion/android/vehicle/DomainListScreen.kt +++ b/app/src/main/java/io/homeassistant/companion/android/vehicle/DomainListScreen.kt @@ -52,6 +52,13 @@ class DomainListScreen( } override fun onGetTemplate(): Template { val isAutomotive = carContext.packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) + val screen = MainVehicleScreen( + carContext, + serverManager, + serverId, + allEntities, + prefsRepository + ) { } val domainList = getDomainList( domains, carContext, @@ -65,7 +72,7 @@ class DomainListScreen( return GridTemplate.Builder().apply { setTitle(carContext.getString(R.string.all_entities)) setHeaderAction(Action.BACK) - if (isAutomotive && !HaCarAppService().isDrivingOptimized && BuildConfig.FLAVOR != "full") { + if (isAutomotive && !screen.isDrivingOptimized && BuildConfig.FLAVOR != "full") { setActionStrip(nativeModeActionStrip(carContext)) } val domainBuild = domainList.build() diff --git a/app/src/main/java/io/homeassistant/companion/android/vehicle/HaCarAppService.kt b/app/src/main/java/io/homeassistant/companion/android/vehicle/HaCarAppService.kt index 9f20703036d..a560ceea376 100644 --- a/app/src/main/java/io/homeassistant/companion/android/vehicle/HaCarAppService.kt +++ b/app/src/main/java/io/homeassistant/companion/android/vehicle/HaCarAppService.kt @@ -1,7 +1,5 @@ package io.homeassistant.companion.android.vehicle -import android.car.Car -import android.car.drivingstate.CarUxRestrictionsManager import android.content.Intent import android.content.pm.ApplicationInfo import android.os.Build @@ -15,8 +13,6 @@ import androidx.car.app.SessionInfo import androidx.car.app.hardware.CarHardwareManager import androidx.car.app.hardware.info.CarInfo import androidx.car.app.validation.HostValidator -import androidx.lifecycle.DefaultLifecycleObserver -import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope import dagger.hilt.android.AndroidEntryPoint import io.homeassistant.companion.android.R @@ -52,14 +48,6 @@ class HaCarAppService : CarAppService() { private val serverId = MutableStateFlow(0) private val allEntities = MutableStateFlow>>(emptyMap()) private var allEntitiesJob: Job? = null - var car: Car? = null - var carRestrictionManager: CarUxRestrictionsManager? = null - val isDrivingOptimized - get() = car?.let { - ( - it.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE) as CarUxRestrictionsManager - ).getCurrentCarUxRestrictions().isRequiresDistractionOptimization() - } ?: false override fun createHostValidator(): HostValidator { return if (applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE != 0) { @@ -77,24 +65,6 @@ class HaCarAppService : CarAppService() { serverManager.getServer()?.let { loadEntities(lifecycleScope, it.id) } - lifecycle.addObserver(object : DefaultLifecycleObserver { - - override fun onResume(owner: LifecycleOwner) { - MainVehicleScreen( - carContext, - serverManager, - serverIdFlow, - entityFlow, - prefsRepository - ) { loadEntities(lifecycleScope, it) }.registerAutomotiveRestrictionListener() - } - - override fun onPause(owner: LifecycleOwner) { - HaCarAppService().carRestrictionManager?.unregisterListener() - HaCarAppService().car?.disconnect() - HaCarAppService().car = null - } - }) } val serverIdFlow = serverId.asStateFlow() diff --git a/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt b/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt index 6e21d8af290..6e6679dc9dd 100644 --- a/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt +++ b/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt @@ -12,7 +12,9 @@ import androidx.car.app.model.Action import androidx.car.app.model.GridTemplate import androidx.car.app.model.ItemList import androidx.car.app.model.Template +import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import io.homeassistant.companion.android.BuildConfig @@ -72,6 +74,14 @@ class MainVehicleScreen( private var favoritesList = emptyList() private var isLoggedIn: Boolean? = null private val domains = mutableSetOf() + var car: Car? = null + var carRestrictionManager: CarUxRestrictionsManager? = null + val isDrivingOptimized + get() = car?.let { + ( + it.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE) as CarUxRestrictionsManager + ).getCurrentCarUxRestrictions().isRequiresDistractionOptimization() + } ?: false private val isAutomotive get() = carContext.packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) @@ -106,6 +116,19 @@ class MainVehicleScreen( favoriteEntities = allEntities.map { getFavoritesList(it) } } } + + lifecycle.addObserver(object : DefaultLifecycleObserver { + + override fun onResume(owner: LifecycleOwner) { + registerAutomotiveRestrictionListener() + } + + override fun onPause(owner: LifecycleOwner) { + carRestrictionManager?.unregisterListener() + car?.disconnect() + car = null + } + }) } override fun onGetTemplate(): Template { @@ -160,7 +183,7 @@ class MainVehicleScreen( return GridTemplate.Builder().apply { setTitle(carContext.getString(commonR.string.app_name)) setHeaderAction(Action.APP_ICON) - if (isAutomotive && !HaCarAppService().isDrivingOptimized && BuildConfig.FLAVOR != "full") { + if (isAutomotive && !isDrivingOptimized && BuildConfig.FLAVOR != "full") { setActionStrip(nativeModeActionStrip(carContext)) } if (domains.isEmpty()) { @@ -175,14 +198,14 @@ class MainVehicleScreen( fun registerAutomotiveRestrictionListener() { if (carContext.packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) { Log.i(TAG, "Register for Automotive Restrictions") - HaCarAppService().car = Car.createCar(carContext) - HaCarAppService().carRestrictionManager = - HaCarAppService().car?.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE) as CarUxRestrictionsManager + car = Car.createCar(carContext) + carRestrictionManager = + car?.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE) as CarUxRestrictionsManager val listener = CarUxRestrictionsManager.OnUxRestrictionsChangedListener { restrictions -> invalidate() } - HaCarAppService().carRestrictionManager?.registerListener(listener) + carRestrictionManager?.registerListener(listener) } } From 8cfa61aaedc4f8bde04fd7ace4f7430ba355b3b2 Mon Sep 17 00:00:00 2001 From: Daniel Shokouhi Date: Mon, 24 Jul 2023 14:48:59 -0700 Subject: [PATCH 12/16] Check distraction again in domain screen --- .../companion/android/vehicle/DomainListScreen.kt | 9 ++++++++- .../companion/android/vehicle/MainVehicleScreen.kt | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/io/homeassistant/companion/android/vehicle/DomainListScreen.kt b/app/src/main/java/io/homeassistant/companion/android/vehicle/DomainListScreen.kt index da49f28d1f5..f136cb3c8e9 100755 --- a/app/src/main/java/io/homeassistant/companion/android/vehicle/DomainListScreen.kt +++ b/app/src/main/java/io/homeassistant/companion/android/vehicle/DomainListScreen.kt @@ -1,5 +1,7 @@ package io.homeassistant.companion.android.vehicle +import android.car.Car +import android.car.drivingstate.CarUxRestrictionsManager import android.content.pm.PackageManager import android.os.Build import androidx.annotation.RequiresApi @@ -59,6 +61,11 @@ class DomainListScreen( allEntities, prefsRepository ) { } + val isDrivingOptimized = screen.car?.let { + ( + it.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE) as CarUxRestrictionsManager + ).getCurrentCarUxRestrictions().isRequiresDistractionOptimization() + } ?: false val domainList = getDomainList( domains, carContext, @@ -72,7 +79,7 @@ class DomainListScreen( return GridTemplate.Builder().apply { setTitle(carContext.getString(R.string.all_entities)) setHeaderAction(Action.BACK) - if (isAutomotive && !screen.isDrivingOptimized && BuildConfig.FLAVOR != "full") { + if (isAutomotive && !isDrivingOptimized && BuildConfig.FLAVOR != "full") { setActionStrip(nativeModeActionStrip(carContext)) } val domainBuild = domainList.build() diff --git a/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt b/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt index 6e6679dc9dd..8620eef49c8 100644 --- a/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt +++ b/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt @@ -76,7 +76,7 @@ class MainVehicleScreen( private val domains = mutableSetOf() var car: Car? = null var carRestrictionManager: CarUxRestrictionsManager? = null - val isDrivingOptimized + private val isDrivingOptimized get() = car?.let { ( it.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE) as CarUxRestrictionsManager From 9980933d875d25068136ac361026f2aba4b90403 Mon Sep 17 00:00:00 2001 From: Daniel Shokouhi Date: Tue, 25 Jul 2023 09:02:43 -0700 Subject: [PATCH 13/16] Use base class to get distraction optimized variable --- .../android/vehicle/BaseVehicleScreen.kt | 58 +++++++++++++++++++ .../android/vehicle/DomainListScreen.kt | 26 ++++----- .../android/vehicle/MainVehicleScreen.kt | 45 ++------------ 3 files changed, 72 insertions(+), 57 deletions(-) create mode 100755 app/src/main/java/io/homeassistant/companion/android/vehicle/BaseVehicleScreen.kt diff --git a/app/src/main/java/io/homeassistant/companion/android/vehicle/BaseVehicleScreen.kt b/app/src/main/java/io/homeassistant/companion/android/vehicle/BaseVehicleScreen.kt new file mode 100755 index 00000000000..3baa0bfce49 --- /dev/null +++ b/app/src/main/java/io/homeassistant/companion/android/vehicle/BaseVehicleScreen.kt @@ -0,0 +1,58 @@ +package io.homeassistant.companion.android.vehicle + +import android.car.Car +import android.car.drivingstate.CarUxRestrictionsManager +import android.content.pm.PackageManager +import android.util.Log +import androidx.car.app.CarContext +import androidx.car.app.Screen +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner + +abstract class BaseVehicleScreen( + carContext: CarContext +) : Screen(carContext) { + + companion object { + private const val TAG = "BaseVehicle" + } + var car: Car? = null + var carRestrictionManager: CarUxRestrictionsManager? = null + protected val isDrivingOptimized + get() = car?.let { + ( + it.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE) as CarUxRestrictionsManager + ).getCurrentCarUxRestrictions().isRequiresDistractionOptimization() + } ?: false + + init { + lifecycle.addObserver(object : DefaultLifecycleObserver { + + override fun onResume(owner: LifecycleOwner) { + registerAutomotiveRestrictionListener() + } + + override fun onPause(owner: LifecycleOwner) { + carRestrictionManager?.unregisterListener() + car?.disconnect() + car = null + } + }) + } + + abstract fun onDrivingOptimizedChanged(newState: Boolean) + + private fun registerAutomotiveRestrictionListener() { + if (carContext.packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) { + Log.i(TAG, "Register for Automotive Restrictions") + car = Car.createCar(carContext) + carRestrictionManager = + car?.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE) as CarUxRestrictionsManager + val listener = + CarUxRestrictionsManager.OnUxRestrictionsChangedListener { restrictions -> + onDrivingOptimizedChanged(restrictions.isRequiresDistractionOptimization()) + } + carRestrictionManager?.registerListener(listener) + } + } +} diff --git a/app/src/main/java/io/homeassistant/companion/android/vehicle/DomainListScreen.kt b/app/src/main/java/io/homeassistant/companion/android/vehicle/DomainListScreen.kt index f136cb3c8e9..acacfdc91f8 100755 --- a/app/src/main/java/io/homeassistant/companion/android/vehicle/DomainListScreen.kt +++ b/app/src/main/java/io/homeassistant/companion/android/vehicle/DomainListScreen.kt @@ -1,12 +1,9 @@ package io.homeassistant.companion.android.vehicle -import android.car.Car -import android.car.drivingstate.CarUxRestrictionsManager import android.content.pm.PackageManager import android.os.Build import androidx.annotation.RequiresApi import androidx.car.app.CarContext -import androidx.car.app.Screen import androidx.car.app.model.Action import androidx.car.app.model.GridTemplate import androidx.car.app.model.Template @@ -32,10 +29,18 @@ class DomainListScreen( private val serverId: StateFlow, private val allEntities: Flow>>, private val prefsRepository: PrefsRepository -) : Screen(carContext) { +) : BaseVehicleScreen(carContext) { + + companion object { + private const val TAG = "DomainList" + } private val domains = mutableSetOf() + override fun onDrivingOptimizedChanged(newState: Boolean) { + invalidate() + } + init { lifecycleScope.launch { allEntities.collect { entities -> @@ -52,20 +57,9 @@ class DomainListScreen( } } } + override fun onGetTemplate(): Template { val isAutomotive = carContext.packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) - val screen = MainVehicleScreen( - carContext, - serverManager, - serverId, - allEntities, - prefsRepository - ) { } - val isDrivingOptimized = screen.car?.let { - ( - it.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE) as CarUxRestrictionsManager - ).getCurrentCarUxRestrictions().isRequiresDistractionOptimization() - } ?: false val domainList = getDomainList( domains, carContext, diff --git a/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt b/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt index 8620eef49c8..b2ff983db52 100644 --- a/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt +++ b/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt @@ -1,20 +1,14 @@ package io.homeassistant.companion.android.vehicle -import android.car.Car -import android.car.drivingstate.CarUxRestrictionsManager import android.content.pm.PackageManager import android.os.Build -import android.util.Log import androidx.annotation.RequiresApi import androidx.car.app.CarContext -import androidx.car.app.Screen import androidx.car.app.model.Action import androidx.car.app.model.GridTemplate import androidx.car.app.model.ItemList import androidx.car.app.model.Template -import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import io.homeassistant.companion.android.BuildConfig @@ -43,7 +37,7 @@ class MainVehicleScreen( private val allEntities: Flow>>, private val prefsRepository: PrefsRepository, private val onChangeServer: (Int) -> Unit -) : Screen(carContext) { +) : BaseVehicleScreen(carContext) { companion object { private const val TAG = "MainVehicleScreen" @@ -74,14 +68,6 @@ class MainVehicleScreen( private var favoritesList = emptyList() private var isLoggedIn: Boolean? = null private val domains = mutableSetOf() - var car: Car? = null - var carRestrictionManager: CarUxRestrictionsManager? = null - private val isDrivingOptimized - get() = car?.let { - ( - it.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE) as CarUxRestrictionsManager - ).getCurrentCarUxRestrictions().isRequiresDistractionOptimization() - } ?: false private val isAutomotive get() = carContext.packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) @@ -116,19 +102,10 @@ class MainVehicleScreen( favoriteEntities = allEntities.map { getFavoritesList(it) } } } + } - lifecycle.addObserver(object : DefaultLifecycleObserver { - - override fun onResume(owner: LifecycleOwner) { - registerAutomotiveRestrictionListener() - } - - override fun onPause(owner: LifecycleOwner) { - carRestrictionManager?.unregisterListener() - car?.disconnect() - car = null - } - }) + override fun onDrivingOptimizedChanged(newState: Boolean) { + invalidate() } override fun onGetTemplate(): Template { @@ -195,20 +172,6 @@ class MainVehicleScreen( }.build() } - fun registerAutomotiveRestrictionListener() { - if (carContext.packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) { - Log.i(TAG, "Register for Automotive Restrictions") - car = Car.createCar(carContext) - carRestrictionManager = - car?.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE) as CarUxRestrictionsManager - val listener = - CarUxRestrictionsManager.OnUxRestrictionsChangedListener { restrictions -> - invalidate() - } - carRestrictionManager?.registerListener(listener) - } - } - private fun getFavoritesList(entities: Map>): List> { return entities.values.filter { entity -> favoritesList.contains("${serverId.value}-${entity.entityId}") } .sortedBy { entity -> favoritesList.indexOf("${serverId.value}-${entity.entityId}") } From 21d7221f87aa74914e43dd5821b3a6d0f6609dd3 Mon Sep 17 00:00:00 2001 From: Daniel Shokouhi Date: Tue, 25 Jul 2023 11:35:14 -0700 Subject: [PATCH 14/16] Send blank template if user is not logged in --- .../companion/android/vehicle/MainVehicleScreen.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt b/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt index b2ff983db52..cad884be8c5 100644 --- a/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt +++ b/app/src/main/java/io/homeassistant/companion/android/vehicle/MainVehicleScreen.kt @@ -109,6 +109,13 @@ class MainVehicleScreen( } override fun onGetTemplate(): Template { + if (isLoggedIn != true) { + return GridTemplate.Builder().apply { + setTitle(carContext.getString(commonR.string.app_name)) + setHeaderAction(Action.APP_ICON) + setLoading(true) + }.build() + } val listBuilder = if (favoritesList.isNotEmpty()) { EntityGridVehicleScreen( carContext, From b22046a25e45580ffec6a066733bc67c736a5e25 Mon Sep 17 00:00:00 2001 From: Daniel Shokouhi Date: Tue, 25 Jul 2023 11:46:19 -0700 Subject: [PATCH 15/16] Mark variables as private --- .../companion/android/vehicle/BaseVehicleScreen.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/io/homeassistant/companion/android/vehicle/BaseVehicleScreen.kt b/app/src/main/java/io/homeassistant/companion/android/vehicle/BaseVehicleScreen.kt index 3baa0bfce49..010eab95a95 100755 --- a/app/src/main/java/io/homeassistant/companion/android/vehicle/BaseVehicleScreen.kt +++ b/app/src/main/java/io/homeassistant/companion/android/vehicle/BaseVehicleScreen.kt @@ -16,8 +16,8 @@ abstract class BaseVehicleScreen( companion object { private const val TAG = "BaseVehicle" } - var car: Car? = null - var carRestrictionManager: CarUxRestrictionsManager? = null + private var car: Car? = null + private var carRestrictionManager: CarUxRestrictionsManager? = null protected val isDrivingOptimized get() = car?.let { ( From 205d3fa185863e2ae138a9d91f7543e57978bf49 Mon Sep 17 00:00:00 2001 From: Daniel Shokouhi Date: Tue, 25 Jul 2023 13:55:50 -0700 Subject: [PATCH 16/16] Fix icon resolution for grid items --- .../companion/android/util/vehicle/GridItems.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/io/homeassistant/companion/android/util/vehicle/GridItems.kt b/app/src/main/java/io/homeassistant/companion/android/util/vehicle/GridItems.kt index 30ae608a488..5f6a3e4c1af 100755 --- a/app/src/main/java/io/homeassistant/companion/android/util/vehicle/GridItems.kt +++ b/app/src/main/java/io/homeassistant/companion/android/util/vehicle/GridItems.kt @@ -49,7 +49,7 @@ fun getChangeServerGridItem( carContext, CommunityMaterial.Icon2.cmd_home_switch ).apply { - sizeDp = 48 + sizeDp = 64 }.toAndroidIconCompat() ) .setTint(CarColor.DEFAULT) @@ -87,7 +87,7 @@ fun getNavigationGridItem( carContext, CommunityMaterial.Icon3.cmd_map_outline ).apply { - sizeDp = 48 + sizeDp = 64 }.toAndroidIconCompat() ) .setTint(CarColor.DEFAULT) @@ -139,7 +139,7 @@ fun getDomainList( CarIcon.Builder( IconicsDrawable(carContext, icon) .apply { - sizeDp = 48 + sizeDp = 64 }.toAndroidIconCompat() ) .setTint(CarColor.DEFAULT) @@ -188,7 +188,7 @@ fun getDomainsGridItem( carContext, CommunityMaterial.Icon3.cmd_view_list ).apply { - sizeDp = 48 + sizeDp = 64 }.toAndroidIconCompat() ) .setTint(CarColor.DEFAULT)