Faster entity selection Wear OS (#2804)

* Always collapse headers and add favorites section

* Remove unneeded entity update subscription

* Process comments

* Use shorthand for favorites loop in MainView
This commit is contained in:
leroyboerefijn 2022-08-30 02:24:35 +02:00 committed by GitHub
parent a995d9410b
commit d00c823651
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 77 additions and 22 deletions

View file

@ -109,6 +109,7 @@
<string name="choose_server">Choose server</string>
<string name="clear_favorites">Clear Favorites</string>
<string name="color_temp">Color temperature: %1$d</string>
<string name="collapse">Collapse</string>
<string name="complication_entity_invalid">Invalid entity</string>
<string name="complication_entity_state_content_description">Entity state</string>
<string name="complication_entity_state_label">Entity state</string>
@ -201,6 +202,7 @@
<string name="error_http_generic">There was an error loading Home Assistant. Please review the connection settings and try again.\n\nError Code: %d \nDescription: %s</string>
<string name="event_error">Failed to notify HA of picked option.</string>
<string name="exit">Exit</string>
<string name="expand">Expand</string>
<string name="failed_authentication">Could not authenticate</string>
<string name="failed_connection">Could not connect</string>
<string name="failed_registration">Could not register</string>

View file

@ -2,6 +2,7 @@ package io.homeassistant.companion.android.complications
import android.app.Application
import android.util.Log
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateMapOf
@ -20,12 +21,16 @@ import io.homeassistant.companion.android.common.data.websocket.WebSocketState
import io.homeassistant.companion.android.data.SimplifiedEntity
import io.homeassistant.companion.android.database.wear.EntityStateComplications
import io.homeassistant.companion.android.database.wear.EntityStateComplicationsDao
import io.homeassistant.companion.android.database.wear.FavoritesDao
import io.homeassistant.companion.android.database.wear.getAllFlow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class ComplicationConfigViewModel @Inject constructor(
application: Application,
private val favoritesDao: FavoritesDao,
private val integrationUseCase: IntegrationRepository,
private val webSocketUseCase: WebSocketRepository,
private val entityStateComplicationsDao: EntityStateComplicationsDao
@ -46,6 +51,7 @@ class ComplicationConfigViewModel @Inject constructor(
private set
var entitiesByDomainOrder = mutableStateListOf<String>()
private set
val favoriteEntityIds = favoritesDao.getAllFlow().collectAsState()
var loadingState by mutableStateOf(LoadingState.LOADING)
private set
@ -81,14 +87,6 @@ class ComplicationConfigViewModel @Inject constructor(
} else {
LoadingState.ERROR
}
// Listen for updates
viewModelScope.launch {
integrationUseCase.getEntityUpdates()?.collect {
entities[it.entityId] = it
updateEntityDomains()
}
}
} catch (e: Exception) {
Log.e(TAG, "Exception while loading entities", e)
loadingState = LoadingState.ERROR
@ -124,4 +122,18 @@ class ComplicationConfigViewModel @Inject constructor(
entityStateComplicationsDao.add(EntityStateComplications(id, entity.entityId))
}
}
/**
* Convert a Flow into a State object that updates until the view model is cleared.
*/
private fun <T> Flow<T>.collectAsState(
initial: T
): State<T> {
val state = mutableStateOf(initial)
viewModelScope.launch {
collect { state.value = it }
}
return state
}
private fun <T> Flow<List<T>>.collectAsState(): State<List<T>> = collectAsState(initial = emptyList())
}

View file

@ -60,6 +60,7 @@ fun LoadConfigView(
ChooseEntityView(
entitiesByDomainOrder = complicationConfigViewModel.entitiesByDomainOrder,
entitiesByDomain = complicationConfigViewModel.entitiesByDomain,
favoriteEntityIds = complicationConfigViewModel.favoriteEntityIds,
onNoneClicked = {},
onEntitySelected = { entity ->
complicationConfigViewModel.setEntity(entity)

View file

@ -171,6 +171,7 @@ fun LoadHomePage(
ChooseEntityView(
entitiesByDomainOrder = mainViewModel.entitiesByDomainOrder,
entitiesByDomain = mainViewModel.entitiesByDomain,
favoriteEntityIds = mainViewModel.favoriteEntityIds,
onNoneClicked = {
mainViewModel.clearTileShortcut(shortcutEntitySelectionIndex)
TileService.getUpdater(context).requestUpdate(ShortcutsTile::class.java)

View file

@ -111,21 +111,16 @@ fun MainView(
colors = ChipDefaults.secondaryChipColors()
)
} else {
var isValidEntity = false
for (entity in mainViewModel.entities) {
if (entity.value.entityId == favoriteEntityID) {
isValidEntity = true
mainViewModel.entities.values.toList()
.firstOrNull { it.entityId == favoriteEntityID }
?.let {
EntityUi(
mainViewModel.entities[favoriteEntityID]!!,
onEntityClicked,
isHapticEnabled,
isToastEnabled
) { entityId -> onEntityLongClicked(entityId) }
}
}
if (!isValidEntity) {
deleteFavorite(favoriteEntityID)
}
} ?: deleteFavorite(favoriteEntityID)
}
}
}

View file

@ -3,6 +3,11 @@ package io.homeassistant.companion.android.views
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.runtime.snapshots.SnapshotStateMap
import androidx.compose.ui.Modifier
@ -32,12 +37,14 @@ import io.homeassistant.companion.android.common.R as commonR
fun ChooseEntityView(
entitiesByDomainOrder: SnapshotStateList<String>,
entitiesByDomain: SnapshotStateMap<String, SnapshotStateList<Entity<*>>>,
favoriteEntityIds: State<List<String>>,
onNoneClicked: () -> Unit,
onEntitySelected: (entity: SimplifiedEntity) -> Unit,
allowNone: Boolean = true
) {
// Remember expanded state of each header
val expandedStates = rememberExpandedStates(entitiesByDomainOrder)
var expandedFavorites: Boolean by rememberSaveable { mutableStateOf(false) }
WearAppTheme {
ThemeLazyColumn {
@ -59,6 +66,30 @@ fun ChooseEntityView(
)
}
}
if (favoriteEntityIds.value.isNotEmpty()) {
item {
ExpandableListHeader(
string = stringResource(commonR.string.favorites),
expanded = expandedFavorites,
onExpandChanged = { expandedFavorites = it }
)
}
if (expandedFavorites) {
items(favoriteEntityIds.value.size) { index ->
val favoriteEntityID = favoriteEntityIds.value[index].split(",")[0]
entitiesByDomain.flatMap { (_, values) -> values }
.firstOrNull { it.entityId == favoriteEntityID }
?.let {
ChooseEntityChip(
entity = it,
onEntitySelected = onEntitySelected
)
}
}
}
}
for (domain in entitiesByDomainOrder) {
val entities = entitiesByDomain[domain]
if (!entities.isNullOrEmpty()) {

View file

@ -2,16 +2,24 @@ package io.homeassistant.companion.android.views
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshots.SnapshotStateMap
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.wear.compose.material.Icon
import androidx.wear.compose.material.LocalContentColor
import androidx.wear.compose.material.Text
import io.homeassistant.companion.android.common.R
import com.mikepenz.iconics.compose.Image
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
import org.checkerframework.checker.units.qual.K
import io.homeassistant.companion.android.common.R as commonR
/**
* Remember expanded state of each header
@ -23,7 +31,7 @@ fun <K> rememberExpandedStates(
return remember {
mutableStateMapOf<K, Boolean>().apply {
initialKeys.forEach { key ->
put(key, true)
put(key, false)
}
}
}
@ -40,9 +48,14 @@ fun ExpandableListHeader(
.clickable { onExpandChanged(!expanded) }
) {
Row {
val plusMinus = if (expanded) "-" else "+"
Text(
text = "$string\u2001$plusMinus"
text = string
)
Spacer(modifier = Modifier.width(4.dp))
Image(
asset = if (expanded) CommunityMaterial.Icon.cmd_chevron_up else CommunityMaterial.Icon.cmd_chevron_down,
contentDescription = stringResource(if (expanded) commonR.string.collapse else commonR.string.expand),
colorFilter = ColorFilter.tint(LocalContentColor.current)
)
}
}
@ -65,7 +78,7 @@ fun <K> ExpandableListHeader(
@Composable
private fun PreviewExpandableListHeader() {
ExpandableListHeader(
string = stringResource(R.string.other),
string = stringResource(commonR.string.other),
expanded = true,
onExpandChanged = {}
)