Android auto: Switch to GridTemplate for home screen and default to favorites if defined (#3694)

* Android auto: default to favorites if defined

* Fix changing servers

* Fix empty favorites list

* Review comments

* Account for extra grid items when showing favorites

* Switch to grid view for home screen, show all domains when there are no favorites for the server

* Review comments

* Clean up and move some methods to util

* Collect favorites with allEntities

* Review comments

* Move native mode logic back

* Check distraction again in domain screen

* Use base class to get distraction optimized variable

* Send blank template if user is not logged in

* Mark variables as private

* Fix icon resolution for grid items
This commit is contained in:
Daniel Shokouhi 2023-07-27 12:41:22 -07:00 committed by GitHub
parent d9db8a06df
commit 4069d29d0a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 553 additions and 231 deletions

View file

@ -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<Int>,
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<Map<String, Entity<*>>>
): 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<String>,
carContext: CarContext,
screenManager: ScreenManager,
serverManager: ServerManager,
serverId: StateFlow<Int>,
prefsRepository: PrefsRepository,
allEntities: Flow<Map<String, Entity<*>>>
): 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<Any, Any>(),
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<Int>,
allEntities: Flow<Map<String, Entity<*>>>,
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
)
)
}
}
}

View file

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

View file

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

View file

@ -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<Int>,
private val allEntities: Flow<Map<String, Entity<*>>>,
private val prefsRepository: PrefsRepository
) : BaseVehicleScreen(carContext) {
companion object {
private const val TAG = "DomainList"
}
private val domains = mutableSetOf<String>()
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()
}
}

View file

@ -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<Int>,
val prefsRepository: PrefsRepository,
val integrationRepository: IntegrationRepository,
val title: String,
val entitiesFlow: Flow<List<Entity<*>>>
private val domains: MutableSet<String>,
private val entitiesFlow: Flow<List<Entity<*>>>,
private val allEntities: Flow<Map<String, Entity<*>>>,
private val onChangeServer: (Int) -> Unit
) : Screen(carContext) {
companion object {
private const val TAG = "EntityGridVehicleScreen"
}
var loading = true
private var loading = true
var entities: List<Entity<*>> = 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<Entity<*>>): 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 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<Entity<*>>): ItemList.Builder {
val listBuilder = ItemList.Builder()
val manager = carContext.getCarService(ConstraintManager::class.java)
val gridLimit = manager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_GRID)
val listBuilder = ItemList.Builder()
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
}
}

View file

@ -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<Map<String, Entity<*>>>,
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<List<Entity<*>>>()
private var entityList: List<Entity<*>> = listOf()
private var favoritesList = emptyList<String>()
private var isLoggedIn: Boolean? = null
private val domains = mutableSetOf<String>()
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<Any, Any>(),
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()
)
}
listBuilder.addItem(
Row.Builder()
.setImage(
CarIcon.Builder(
IconicsDrawable(
carContext,
CommunityMaterial.Icon3.cmd_map_outline
).apply {
sizeDp = 48
}.toAndroidIconCompat()
)
.setTint(CarColor.DEFAULT)
.build()
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
)
.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.addItem(
getNavigationGridItem(
carContext,
screenManager,
serverManager.integrationRepository(serverId.value),
allEntities
).build()
)
if (serverManager.defaultServers.size > 1) {
builder.addItem(
getChangeServerGridItem(
carContext,
screenManager,
serverManager,
serverId
) { onChangeServer(it) }.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<String, Entity<*>>): List<Entity<*>> {
return entities.values.filter { entity -> favoritesList.contains("${serverId.value}-${entity.entityId}") }
.sortedBy { entity -> favoritesList.indexOf("${serverId.value}-${entity.entityId}") }
}
}

View file

@ -200,7 +200,7 @@ class PrefsRepositoryImpl @Inject constructor(
}
override suspend fun getAutoFavorites(): List<String> {
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<String>) {