mirror of
https://github.com/home-assistant/android
synced 2024-07-23 03:14:14 +00:00
Add favorites selection for Android Auto (#3670)
* Add favorites selection for Android Auto * Move reorderable to implementation * Small clean up * Hide from minimal build * Show on automotive builds and change some titles to match that device * Update strings to be more precise about driving optimized * Review comments * Switch from string set to string to preserve order * Move some conversion logic to PrefsRepository * clean up * Review comments * Consistency updates
This commit is contained in:
parent
455da053d1
commit
605e6ec914
|
@ -204,7 +204,7 @@ dependencies {
|
|||
implementation("com.mikepenz:iconics-compose:5.4.0")
|
||||
implementation("com.mikepenz:community-material-typeface:7.0.96.0-kotlin@aar")
|
||||
|
||||
"fullImplementation"("org.burnoutcrew.composereorderable:reorderable:0.9.6")
|
||||
implementation("org.burnoutcrew.composereorderable:reorderable:0.9.6")
|
||||
implementation("com.github.AppDevNext:ChangeLog:3.4")
|
||||
|
||||
implementation("androidx.car.app:app:1.3.0-rc01")
|
||||
|
|
|
@ -1,47 +1,26 @@
|
|||
package io.homeassistant.companion.android.settings.wear.views
|
||||
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material.ContentAlpha
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.LocalContentAlpha
|
||||
import androidx.compose.material.LocalContentColor
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Clear
|
||||
import androidx.compose.material.rememberScaffoldState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.mikepenz.iconics.compose.Image
|
||||
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
|
||||
import io.homeassistant.companion.android.common.data.integration.Entity
|
||||
import io.homeassistant.companion.android.common.data.integration.friendlyName
|
||||
import io.homeassistant.companion.android.settings.wear.SettingsWearViewModel
|
||||
import io.homeassistant.companion.android.util.compose.FavoriteEntityRow
|
||||
import io.homeassistant.companion.android.util.compose.SingleEntityPicker
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
|
@ -49,8 +28,6 @@ import kotlinx.coroutines.flow.launchIn
|
|||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.burnoutcrew.reorderable.ReorderableItem
|
||||
import org.burnoutcrew.reorderable.ReorderableLazyListState
|
||||
import org.burnoutcrew.reorderable.detectReorderAfterLongPress
|
||||
import org.burnoutcrew.reorderable.rememberReorderableLazyListState
|
||||
import org.burnoutcrew.reorderable.reorderable
|
||||
import io.homeassistant.companion.android.common.R as commonR
|
||||
|
@ -136,7 +113,7 @@ fun LoadWearFavoritesSettings(
|
|||
reorderableState = reorderState,
|
||||
key = favoriteEntities[index]
|
||||
) { isDragging ->
|
||||
WearFavoriteEntityRow(
|
||||
FavoriteEntityRow(
|
||||
entityName = it.friendlyName,
|
||||
entityId = favoriteEntityID,
|
||||
onClick = {
|
||||
|
@ -156,56 +133,3 @@ fun LoadWearFavoritesSettings(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun WearFavoriteEntityRow(
|
||||
entityName: String,
|
||||
entityId: String,
|
||||
onClick: () -> Unit,
|
||||
checked: Boolean,
|
||||
draggable: Boolean = false,
|
||||
isDragging: Boolean = false,
|
||||
reorderableState: ReorderableLazyListState? = null
|
||||
) {
|
||||
val surfaceElevation = animateDpAsState(targetValue = if (isDragging) 8.dp else 0.dp)
|
||||
var rowModifier = Modifier.fillMaxWidth().heightIn(min = 72.dp)
|
||||
if (draggable && reorderableState != null) {
|
||||
rowModifier = rowModifier.then(Modifier.detectReorderAfterLongPress(reorderableState))
|
||||
}
|
||||
Surface(
|
||||
elevation = surfaceElevation.value
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = rowModifier
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.weight(1f).padding(start = 16.dp)
|
||||
) {
|
||||
Text(text = entityName, style = MaterialTheme.typography.body1)
|
||||
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
|
||||
Text(text = entityId, style = MaterialTheme.typography.body2)
|
||||
}
|
||||
}
|
||||
IconButton(onClick = onClick) {
|
||||
Icon(
|
||||
imageVector = if (checked) Icons.Default.Clear else Icons.Default.Add,
|
||||
contentDescription = stringResource(if (checked) commonR.string.delete else commonR.string.add_favorite)
|
||||
)
|
||||
}
|
||||
if (draggable) {
|
||||
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
|
||||
Image(
|
||||
asset = CommunityMaterial.Icon.cmd_drag_horizontal_variant,
|
||||
contentDescription = stringResource(commonR.string.hold_to_reorder),
|
||||
colorFilter = ColorFilter.tint(LocalContentColor.current),
|
||||
modifier = Modifier
|
||||
.size(width = 40.dp, height = 24.dp)
|
||||
.padding(end = 16.dp)
|
||||
.alpha(LocalContentAlpha.current)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ import io.homeassistant.companion.android.settings.sensor.SensorSettingsFragment
|
|||
import io.homeassistant.companion.android.settings.sensor.SensorUpdateFrequencyFragment
|
||||
import io.homeassistant.companion.android.settings.server.ServerSettingsFragment
|
||||
import io.homeassistant.companion.android.settings.shortcuts.ManageShortcutsSettingsFragment
|
||||
import io.homeassistant.companion.android.settings.vehicle.ManageAndroidAutoSettingsFragment
|
||||
import io.homeassistant.companion.android.settings.wear.SettingsWearActivity
|
||||
import io.homeassistant.companion.android.settings.wear.SettingsWearDetection
|
||||
import io.homeassistant.companion.android.settings.widgets.ManageWidgetsSettingsFragment
|
||||
|
@ -324,6 +325,28 @@ class SettingsFragment(
|
|||
}
|
||||
return@setOnPreferenceClickListener true
|
||||
}
|
||||
|
||||
val isAutomotive = requireContext().packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
|
||||
findPreference<PreferenceCategory>("android_auto")?.let {
|
||||
it.isVisible =
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && (BuildConfig.FLAVOR == "full" || isAutomotive)
|
||||
if (isAutomotive) {
|
||||
it.title = getString(commonR.string.android_automotive)
|
||||
}
|
||||
}
|
||||
|
||||
findPreference<Preference>("auto_favorites")?.let { pref ->
|
||||
if (isAutomotive) {
|
||||
pref.title = getString(commonR.string.android_automotive_favorites)
|
||||
}
|
||||
pref.setOnPreferenceClickListener {
|
||||
parentFragmentManager.commit {
|
||||
replace(R.id.content, ManageAndroidAutoSettingsFragment::class.java, null)
|
||||
addToBackStack(getString(commonR.string.basic_sensor_name_android_auto))
|
||||
}
|
||||
return@setOnPreferenceClickListener true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeSystemFromThemesIfNeeded() {
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
package io.homeassistant.companion.android.settings.vehicle
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.google.accompanist.themeadapter.material.MdcTheme
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.common.data.servers.ServerManager
|
||||
import io.homeassistant.companion.android.settings.vehicle.views.AndroidAutoFavoritesSettings
|
||||
import javax.inject.Inject
|
||||
import io.homeassistant.companion.android.common.R as commonR
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ManageAndroidAutoSettingsFragment : Fragment() {
|
||||
|
||||
@Inject
|
||||
lateinit var serverManager: ServerManager
|
||||
|
||||
val viewModel: ManageAndroidAutoViewModel by viewModels()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun onPrepareOptionsMenu(menu: Menu) {
|
||||
super.onPrepareOptionsMenu(menu)
|
||||
|
||||
menu.findItem(R.id.get_help)?.let {
|
||||
it.isVisible = true
|
||||
it.intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://companion.home-assistant.io/docs/android-auto"))
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
return ComposeView(requireContext()).apply {
|
||||
setContent {
|
||||
MdcTheme {
|
||||
AndroidAutoFavoritesSettings(
|
||||
androidAutoViewModel = viewModel,
|
||||
serversList = serverManager.defaultServers,
|
||||
defaultServer = serverManager.getServer()?.id ?: 0
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
activity?.title =
|
||||
if (requireContext().packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
|
||||
getString(commonR.string.android_automotive_favorites)
|
||||
} else {
|
||||
getString(commonR.string.aa_favorites)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
package io.homeassistant.companion.android.settings.vehicle
|
||||
|
||||
import android.app.Application
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
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.prefs.PrefsRepository
|
||||
import io.homeassistant.companion.android.common.data.servers.ServerManager
|
||||
import io.homeassistant.companion.android.vehicle.MainVehicleScreen
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.launch
|
||||
import org.burnoutcrew.reorderable.ItemPosition
|
||||
import javax.inject.Inject
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@HiltViewModel
|
||||
class ManageAndroidAutoViewModel @Inject constructor(
|
||||
private val serverManager: ServerManager,
|
||||
private val prefsRepository: PrefsRepository,
|
||||
application: Application
|
||||
) : AndroidViewModel(application) {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "AAViewModel"
|
||||
}
|
||||
val favoritesList = mutableStateListOf<String>()
|
||||
|
||||
var sortedEntities by mutableStateOf<List<Entity<*>>>(emptyList())
|
||||
private set
|
||||
val entities = mutableMapOf<Int, List<Entity<*>>>()
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
favoritesList.addAll(prefsRepository.getAutoFavorites())
|
||||
serverManager.defaultServers.map {
|
||||
async {
|
||||
entities[it.id] = try {
|
||||
serverManager.integrationRepository(it.id).getEntities().orEmpty()
|
||||
.filter { it.domain in MainVehicleScreen.SUPPORTED_DOMAINS }
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Couldn't load entities for server", e)
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
}.awaitAll()
|
||||
loadEntities(serverManager.getServer()?.id ?: 0)
|
||||
}
|
||||
}
|
||||
|
||||
fun onMove(fromItem: ItemPosition, toItem: ItemPosition) {
|
||||
favoritesList.apply {
|
||||
add(
|
||||
favoritesList.indexOfFirst { it == toItem.key },
|
||||
removeAt(favoritesList.indexOfFirst { it == fromItem.key })
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun canDragOver(position: ItemPosition) = favoritesList.any { it == position.key }
|
||||
|
||||
fun saveFavorites() {
|
||||
viewModelScope.launch {
|
||||
prefsRepository.setAutoFavorites(favoritesList.toList())
|
||||
}
|
||||
}
|
||||
|
||||
fun loadEntities(serverId: Int) {
|
||||
sortedEntities = entities[serverId] ?: emptyList()
|
||||
}
|
||||
|
||||
fun onEntitySelected(checked: Boolean, entityId: String, serverId: Int) {
|
||||
if (checked) {
|
||||
favoritesList.add("$serverId-$entityId")
|
||||
} else {
|
||||
favoritesList.remove("$serverId-$entityId")
|
||||
}
|
||||
viewModelScope.launch { prefsRepository.setAutoFavorites(favoritesList.toList()) }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
package io.homeassistant.companion.android.settings.vehicle.views
|
||||
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
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.friendlyName
|
||||
import io.homeassistant.companion.android.database.server.Server
|
||||
import io.homeassistant.companion.android.settings.vehicle.ManageAndroidAutoViewModel
|
||||
import io.homeassistant.companion.android.util.compose.FavoriteEntityRow
|
||||
import io.homeassistant.companion.android.util.compose.ServerExposedDropdownMenu
|
||||
import io.homeassistant.companion.android.util.compose.SingleEntityPicker
|
||||
import io.homeassistant.companion.android.vehicle.MainVehicleScreen
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.burnoutcrew.reorderable.ReorderableItem
|
||||
import org.burnoutcrew.reorderable.rememberReorderableLazyListState
|
||||
import org.burnoutcrew.reorderable.reorderable
|
||||
import io.homeassistant.companion.android.common.R as commonR
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@Composable
|
||||
fun AndroidAutoFavoritesSettings(
|
||||
androidAutoViewModel: ManageAndroidAutoViewModel,
|
||||
serversList: List<Server>,
|
||||
defaultServer: Int
|
||||
) {
|
||||
val reorderState = rememberReorderableLazyListState(
|
||||
onMove = { from, to -> androidAutoViewModel.onMove(from, to) },
|
||||
canDragOver = { draggedOver, _ -> androidAutoViewModel.canDragOver(draggedOver) },
|
||||
onDragEnd = { _, _ -> androidAutoViewModel.saveFavorites() }
|
||||
)
|
||||
|
||||
var selectedServer by remember { mutableStateOf(defaultServer) }
|
||||
|
||||
val favoriteEntities = androidAutoViewModel.favoritesList.toList()
|
||||
var validEntities by remember { mutableStateOf<List<Entity<*>>>(emptyList()) }
|
||||
LaunchedEffect(favoriteEntities.size, androidAutoViewModel.sortedEntities.size, selectedServer) {
|
||||
validEntities = withContext(Dispatchers.IO) {
|
||||
androidAutoViewModel.sortedEntities
|
||||
.filter {
|
||||
!favoriteEntities.contains("$selectedServer-${it.entityId}") &&
|
||||
(it.domain in MainVehicleScreen.SUPPORTED_DOMAINS)
|
||||
}
|
||||
.toList()
|
||||
}
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
state = reorderState.listState,
|
||||
contentPadding = PaddingValues(vertical = 16.dp),
|
||||
modifier = Modifier
|
||||
.reorderable(reorderState)
|
||||
) {
|
||||
item {
|
||||
Text(
|
||||
text = stringResource(commonR.string.aa_set_favorites),
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.padding(horizontal = 16.dp).padding(bottom = 16.dp)
|
||||
)
|
||||
}
|
||||
|
||||
if (serversList.size > 1) {
|
||||
item {
|
||||
ServerExposedDropdownMenu(
|
||||
servers = serversList,
|
||||
current = selectedServer,
|
||||
onSelected = {
|
||||
androidAutoViewModel.loadEntities(it)
|
||||
selectedServer = it
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp).padding(bottom = 16.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
item {
|
||||
SingleEntityPicker(
|
||||
entities = validEntities,
|
||||
currentEntity = null,
|
||||
onEntityCleared = { /* Nothing */ },
|
||||
onEntitySelected = {
|
||||
androidAutoViewModel.onEntitySelected(true, it, selectedServer)
|
||||
return@SingleEntityPicker false // Clear input
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 16.dp).padding(bottom = 16.dp),
|
||||
label = { Text(stringResource(commonR.string.add_favorite)) }
|
||||
)
|
||||
}
|
||||
if (favoriteEntities.isNotEmpty() && androidAutoViewModel.sortedEntities.isNotEmpty()) {
|
||||
items(favoriteEntities.size, { favoriteEntities[it] }) { index ->
|
||||
val favoriteEntity =
|
||||
favoriteEntities[index].split("-")
|
||||
androidAutoViewModel.sortedEntities.firstOrNull { it.entityId == favoriteEntity[1] && favoriteEntity[0].toInt() == selectedServer }?.let {
|
||||
ReorderableItem(
|
||||
reorderableState = reorderState,
|
||||
key = favoriteEntities[index]
|
||||
) { isDragging ->
|
||||
FavoriteEntityRow(
|
||||
entityName = it.friendlyName,
|
||||
entityId = it.entityId,
|
||||
onClick = {
|
||||
androidAutoViewModel.onEntitySelected(
|
||||
false,
|
||||
it.entityId,
|
||||
selectedServer
|
||||
)
|
||||
},
|
||||
checked = true,
|
||||
draggable = true,
|
||||
isDragging = isDragging,
|
||||
reorderableState = reorderState
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package io.homeassistant.companion.android.util.compose
|
||||
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.ContentAlpha
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.LocalContentAlpha
|
||||
import androidx.compose.material.LocalContentColor
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Clear
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.mikepenz.iconics.compose.Image
|
||||
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
|
||||
import io.homeassistant.companion.android.common.R
|
||||
import org.burnoutcrew.reorderable.ReorderableLazyListState
|
||||
import org.burnoutcrew.reorderable.detectReorderAfterLongPress
|
||||
|
||||
@Composable
|
||||
fun FavoriteEntityRow(
|
||||
entityName: String,
|
||||
entityId: String,
|
||||
onClick: () -> Unit,
|
||||
checked: Boolean,
|
||||
draggable: Boolean = false,
|
||||
isDragging: Boolean = false,
|
||||
reorderableState: ReorderableLazyListState? = null
|
||||
) {
|
||||
val surfaceElevation = animateDpAsState(targetValue = if (isDragging) 8.dp else 0.dp)
|
||||
var rowModifier = Modifier.fillMaxWidth().heightIn(min = 72.dp)
|
||||
if (draggable && reorderableState != null) {
|
||||
rowModifier = rowModifier.then(Modifier.detectReorderAfterLongPress(reorderableState))
|
||||
}
|
||||
Surface(
|
||||
elevation = surfaceElevation.value
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = rowModifier
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.weight(1f).padding(start = 16.dp)
|
||||
) {
|
||||
Text(text = entityName, style = MaterialTheme.typography.body1)
|
||||
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
|
||||
Text(text = entityId, style = MaterialTheme.typography.body2)
|
||||
}
|
||||
}
|
||||
IconButton(onClick = onClick) {
|
||||
Icon(
|
||||
imageVector = if (checked) Icons.Default.Clear else Icons.Default.Add,
|
||||
contentDescription = stringResource(if (checked) R.string.delete else R.string.add_favorite)
|
||||
)
|
||||
}
|
||||
if (draggable) {
|
||||
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
|
||||
Image(
|
||||
asset = CommunityMaterial.Icon.cmd_drag_horizontal_variant,
|
||||
contentDescription = stringResource(R.string.hold_to_reorder),
|
||||
colorFilter = ColorFilter.tint(LocalContentColor.current),
|
||||
modifier = Modifier
|
||||
.size(width = 40.dp, height = 24.dp)
|
||||
.padding(end = 16.dp)
|
||||
.alpha(LocalContentAlpha.current)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,6 +17,7 @@ import androidx.lifecycle.lifecycleScope
|
|||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.common.data.integration.Entity
|
||||
import io.homeassistant.companion.android.common.data.prefs.PrefsRepository
|
||||
import io.homeassistant.companion.android.common.data.servers.ServerManager
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
|
@ -41,6 +42,9 @@ class HaCarAppService : CarAppService() {
|
|||
@Inject
|
||||
lateinit var serverManager: ServerManager
|
||||
|
||||
@Inject
|
||||
lateinit var prefsRepository: PrefsRepository
|
||||
|
||||
private val serverId = MutableStateFlow(0)
|
||||
private val allEntities = MutableStateFlow<Map<String, Entity<*>>>(emptyMap())
|
||||
private var allEntitiesJob: Job? = null
|
||||
|
@ -81,7 +85,8 @@ class HaCarAppService : CarAppService() {
|
|||
carContext,
|
||||
serverManager,
|
||||
serverIdFlow,
|
||||
entityFlow
|
||||
entityFlow,
|
||||
prefsRepository
|
||||
) { loadEntities(lifecycleScope, it) }
|
||||
)
|
||||
|
||||
|
@ -101,7 +106,8 @@ class HaCarAppService : CarAppService() {
|
|||
carContext,
|
||||
serverManager,
|
||||
serverIdFlow,
|
||||
entityFlow
|
||||
entityFlow,
|
||||
prefsRepository
|
||||
) { loadEntities(lifecycleScope, it) }
|
||||
)
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ import io.homeassistant.companion.android.common.data.authentication.SessionStat
|
|||
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
|
||||
|
@ -49,6 +50,7 @@ class MainVehicleScreen(
|
|||
val serverManager: ServerManager,
|
||||
private val serverId: StateFlow<Int>,
|
||||
private val allEntities: Flow<Map<String, Entity<*>>>,
|
||||
private val prefsRepository: PrefsRepository,
|
||||
private val onChangeServer: (Int) -> Unit
|
||||
) : Screen(carContext) {
|
||||
|
||||
|
@ -66,7 +68,7 @@ class MainVehicleScreen(
|
|||
"script" to commonR.string.scripts,
|
||||
"switch" to commonR.string.switches
|
||||
)
|
||||
private val SUPPORTED_DOMAINS = SUPPORTED_DOMAINS_WITH_STRING.keys
|
||||
val SUPPORTED_DOMAINS = SUPPORTED_DOMAINS_WITH_STRING.keys
|
||||
|
||||
private val MAP_DOMAINS = listOf(
|
||||
"device_tracker",
|
||||
|
@ -76,6 +78,7 @@ class MainVehicleScreen(
|
|||
)
|
||||
}
|
||||
|
||||
private var favoritesList = emptyList<String>()
|
||||
private var isLoggedIn: Boolean? = null
|
||||
private val domains = mutableSetOf<String>()
|
||||
private var car: Car? = null
|
||||
|
@ -92,6 +95,7 @@ class MainVehicleScreen(
|
|||
init {
|
||||
lifecycleScope.launch {
|
||||
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
favoritesList = prefsRepository.getAutoFavorites()
|
||||
isLoggedIn = serverManager.isRegistered() &&
|
||||
serverManager.authenticationRepository()
|
||||
.getSessionState() == SessionState.CONNECTED
|
||||
|
@ -134,6 +138,33 @@ class MainVehicleScreen(
|
|||
|
||||
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()
|
||||
)
|
||||
}
|
||||
domains.forEach { domain ->
|
||||
val friendlyDomain =
|
||||
SUPPORTED_DOMAINS_WITH_STRING[domain]?.let { carContext.getString(it) }
|
||||
|
|
5
app/src/main/res/drawable/ic_car.xml
Executable file
5
app/src/main/res/drawable/ic_car.xml
Executable file
|
@ -0,0 +1,5 @@
|
|||
<vector android:height="24dp"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@color/colorAccent" android:pathData="M18.92,6.01C18.72,5.42 18.16,5 17.5,5h-11c-0.66,0 -1.21,0.42 -1.42,1.01L3,12v8c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-1h12v1c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-8l-2.08,-5.99zM6.5,16c-0.83,0 -1.5,-0.67 -1.5,-1.5S5.67,13 6.5,13s1.5,0.67 1.5,1.5S7.33,16 6.5,16zM17.5,16c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM5,11l1.5,-4.5h11L19,11L5,11z"/>
|
||||
</vector>
|
|
@ -123,6 +123,15 @@
|
|||
android:icon="@drawable/ic_notifications"
|
||||
android:summary="@string/rate_limit_summary"/>
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory
|
||||
android:title="@string/basic_sensor_name_android_auto"
|
||||
android:key="android_auto">
|
||||
<Preference
|
||||
android:key="auto_favorites"
|
||||
android:icon="@drawable/ic_car"
|
||||
android:title="@string/aa_favorites"
|
||||
android:summary="@string/aa_favorites_summary" />
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory
|
||||
android:key="device_controls"
|
||||
android:title="@string/controls_setting_category"
|
||||
|
|
|
@ -66,4 +66,8 @@ interface PrefsRepository {
|
|||
suspend fun getIgnoredSuggestions(): List<String>
|
||||
|
||||
suspend fun setIgnoredSuggestions(ignored: List<String>)
|
||||
|
||||
suspend fun getAutoFavorites(): List<String>
|
||||
|
||||
suspend fun setAutoFavorites(favorites: List<String>)
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ class PrefsRepositoryImpl @Inject constructor(
|
|||
private const val PREF_KEY_ALIAS = "key-alias"
|
||||
private const val PREF_CRASH_REPORTING_DISABLED = "crash_reporting"
|
||||
private const val PREF_IGNORED_SUGGESTIONS = "ignored_suggestions"
|
||||
private const val PREF_AUTO_FAVORITES = "auto_favorites"
|
||||
}
|
||||
|
||||
init {
|
||||
|
@ -197,4 +198,12 @@ class PrefsRepositoryImpl @Inject constructor(
|
|||
override suspend fun setIgnoredSuggestions(ignored: List<String>) {
|
||||
localStorage.putStringSet(PREF_IGNORED_SUGGESTIONS, ignored.toSet())
|
||||
}
|
||||
|
||||
override suspend fun getAutoFavorites(): List<String> {
|
||||
return localStorage.getString(PREF_AUTO_FAVORITES)?.removeSurrounding("[", "]")?.split(", ") ?: emptyList()
|
||||
}
|
||||
|
||||
override suspend fun setAutoFavorites(favorites: List<String>) {
|
||||
localStorage.putString(PREF_AUTO_FAVORITES, favorites.toString())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1121,4 +1121,9 @@
|
|||
<string name="sensor_description_car_fuel_type">List of available fuel types for the connected car</string>
|
||||
<string name="basic_sensor_name_car_ev_connector_type">Car EV Connector Type</string>
|
||||
<string name="sensor_description_car_ev_connector_type">List of available EV connectors for the connected car</string>
|
||||
<string name="aa_set_favorites">Select your favorite entities to appear in the Favorites category in the Home Assistant driving interface. You can also drag and drop to change the order in which they appear. Keep in mind that the amount of entities shown will vary from vehicle to vehicle.</string>
|
||||
<string name="aa_favorites">Android Auto Favorites</string>
|
||||
<string name="aa_favorites_summary">Select your favorite entities to be shown in the app while viewing the Home Assistant driving interface</string>
|
||||
<string name="android_automotive">Android Automotive</string>
|
||||
<string name="android_automotive_favorites">Driving Favorites</string>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in a new issue