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..5f6a3e4c1af --- /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 = 64 + }.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 = 64 + }.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 = 64 + }.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 = 64 + }.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/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/BaseVehicleScreen.kt b/app/src/main/java/io/homeassistant/companion/android/vehicle/BaseVehicleScreen.kt new file mode 100755 index 00000000000..010eab95a95 --- /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" + } + private var car: Car? = null + private 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 new file mode 100755 index 00000000000..acacfdc91f8 --- /dev/null +++ b/app/src/main/java/io/homeassistant/companion/android/vehicle/DomainListScreen.kt @@ -0,0 +1,88 @@ +package io.homeassistant.companion.android.vehicle + +import android.content.pm.PackageManager +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.car.app.CarContext +import androidx.car.app.model.Action +import androidx.car.app.model.GridTemplate +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 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 +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 +) : 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 -> + 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 isAutomotive = carContext.packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) + val domainList = getDomainList( + domains, + carContext, + screenManager, + serverManager, + serverId, + prefsRepository, + allEntities + ) + + return GridTemplate.Builder().apply { + setTitle(carContext.getString(R.string.all_entities)) + setHeaderAction(Action.BACK) + if (isAutomotive && !isDrivingOptimized && BuildConfig.FLAVOR != "full") { + setActionStrip(nativeModeActionStrip(carContext)) + } + val domainBuild = domainList.build() + if (domainBuild.items.isEmpty()) { + setLoading(true) + } else { + setLoading(false) + setSingleList(domainBuild) + } + }.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..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 @@ -20,6 +20,7 @@ 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.friendlyName @@ -27,23 +28,38 @@ 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 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.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 domains: MutableSet, + 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) + private val shouldSwitchServers = serverManager.defaultServers.size > 1 init { lifecycleScope.launch { @@ -58,13 +74,76 @@ class EntityGridVehicleScreen( } } + fun getEntityGridItems(entities: List>): ItemList.Builder { + val listBuilder = if (entities.isNotEmpty()) { + createEntityGrid(entities) + } else { + getDomainList( + domains, + carContext, + screenManager, + serverManager, + serverId, + prefsRepository, + allEntities + ) + } + if (isFavorites) { + listBuilder.addItem( + getNavigationGridItem( + carContext, + screenManager, + integrationRepository, + allEntities + ).build() + ) + listBuilder.addItem( + getDomainsGridItem( + carContext, + screenManager, + serverManager, + integrationRepository, + serverId, + allEntities, + prefsRepository + ).build() + ) + if (shouldSwitchServers) { + listBuilder.addItem( + getChangeServerGridItem( + carContext, + screenManager, + serverManager, + serverId + ) { onChangeServer(it) }.build() + ) + } + } + return listBuilder + } + override fun onGetTemplate(): Template { - val manager = carContext.getCarService(ConstraintManager::class.java) - val gridLimit = manager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_GRID) + 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 extraGrid = if (shouldSwitchServers) 3 else 2 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 } @@ -97,16 +176,6 @@ class EntityGridVehicleScreen( } listBuilder.addItem(gridItem.build()) } - - return GridTemplate.Builder().apply { - setTitle(title) - setHeaderAction(Action.BACK) - if (loading) { - setLoading(true) - } else { - setLoading(false) - setSingleList(listBuilder.build()) - } - }.build() + return listBuilder } } 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..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 @@ -1,47 +1,32 @@ 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 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.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 -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.launch.LaunchActivity +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 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) @@ -52,12 +37,12 @@ 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" - 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, @@ -70,7 +55,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,17 +63,11 @@ class MainVehicleScreen( ) } + private var favoriteEntities = flowOf>>() + private var entityList: List> = listOf() 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 - 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) @@ -118,174 +97,78 @@ class MainVehicleScreen( domains.addAll(newDomains) invalidate() } + entityList = getFavoritesList(entities) } + 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 { - val listBuilder = ItemList.Builder() - 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() - ) + if (isLoggedIn != true) { + return GridTemplate.Builder().apply { + setTitle(carContext.getString(commonR.string.app_name)) + setHeaderAction(Action.APP_ICON) + setLoading(true) + }.build() } - 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) + val listBuilder = if (favoritesList.isNotEmpty()) { + EntityGridVehicleScreen( + carContext, + serverManager, + serverId, + prefsRepository, + serverManager.integrationRepository(serverId.value), + carContext.getString(commonR.string.favorites), + domains, + favoriteEntities, + allEntities + ) { onChangeServer(it) }.getEntityGridItems(entityList) + } else { + var builder = ItemList.Builder() + if (domains.isNotEmpty()) { + builder = getDomainList( + domains, + carContext, + screenManager, + serverManager, + serverId, + prefsRepository, + allEntities + ) + } - 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() + builder.addItem( + getNavigationGridItem( + carContext, + screenManager, + serverManager.integrationRepository(serverId.value), + allEntities + ).build() ) - } - listBuilder.addItem( - Row.Builder() - .setImage( - CarIcon.Builder( - IconicsDrawable( - carContext, - CommunityMaterial.Icon3.cmd_map_outline - ).apply { - sizeDp = 48 - }.toAndroidIconCompat() - ) - .setTint(CarColor.DEFAULT) - .build() + if (serverManager.defaultServers.size > 1) { + builder.addItem( + getChangeServerGridItem( + carContext, + screenManager, + serverManager, + serverId + ) { onChangeServer(it) }.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() - ) - - if (serverManager.defaultServers.size > 1) { - listBuilder.addItem( - Row.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 ListTemplate.Builder().apply { + return GridTemplate.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 (isAutomotive && !isDrivingOptimized && BuildConfig.FLAVOR != "full") { + setActionStrip(nativeModeActionStrip(carContext)) } if (domains.isEmpty()) { setLoading(true) @@ -296,34 +179,8 @@ class MainVehicleScreen( }.build() } - 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 -> - invalidate() - } - carRestrictionManager?.registerListener(listener) - } - } - - private 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() - } - } + 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}") } } } 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) {