Skip to content

Commit

Permalink
Add favorites selection for Android Auto
Browse files Browse the repository at this point in the history
  • Loading branch information
dshokouhi committed Jul 18, 2023
1 parent 00c2ef6 commit 916d71a
Show file tree
Hide file tree
Showing 13 changed files with 450 additions and 81 deletions.
Original file line number Diff line number Diff line change
@@ -1,56 +1,33 @@
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
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
Expand Down Expand Up @@ -136,7 +113,7 @@ fun LoadWearFavoritesSettings(
reorderableState = reorderState,
key = favoriteEntities[index]
) { isDragging ->
WearFavoriteEntityRow(
FavoriteEntityRow(
entityName = it.friendlyName,
entityId = favoriteEntityID,
onClick = {
Expand All @@ -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)
)
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -324,6 +325,18 @@ class SettingsFragment(
}
return@setOnPreferenceClickListener true
}

findPreference<PreferenceCategory>("android_auto")?.let {
it.isVisible = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
}

findPreference<Preference>("auto_favorites")?.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() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package io.homeassistant.companion.android.settings.vehicle

import android.content.Intent
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 = getString(commonR.string.aa_favorites)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
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 entitiesLoaded by mutableStateOf(false)
private set
var sortedEntities by mutableStateOf<List<Entity<*>>>(emptyList())
private set
val entities = mutableMapOf<Int, List<Entity<*>>>()
init {
viewModelScope.launch {
favoritesList.addAll(prefsRepository.getAutoFavorites())
entitiesLoaded = true
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)
}
}

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

0 comments on commit 916d71a

Please sign in to comment.