Use new entity picker for more settings (#3662)

This commit is contained in:
Joris Pelgröm 2023-07-17 14:08:51 +02:00 committed by GitHub
parent bc103636db
commit cd62f17fac
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 97 additions and 88 deletions

View file

@ -1,28 +1,35 @@
package io.homeassistant.companion.android.settings.wear.views
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.foundation.clickable
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.Checkbox
import androidx.compose.material.ContentAlpha
import androidx.compose.material.Divider
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
@ -32,12 +39,15 @@ 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.domain
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.getEntityDomainString
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
@ -59,8 +69,19 @@ fun LoadWearFavoritesSettings(
}
)
val validEntities = settingsWearViewModel.entities.filter { it.key.split(".")[0] in settingsWearViewModel.supportedDomains }.values.sortedBy { it.entityId }.toList()
val favoriteEntities = settingsWearViewModel.favoriteEntityIds
var validEntities by remember { mutableStateOf<List<Entity<*>>>(emptyList()) }
LaunchedEffect(favoriteEntities.size) {
validEntities = withContext(Dispatchers.IO) {
settingsWearViewModel.entities
.filter {
!favoriteEntities.contains(it.key) &&
it.key.split(".")[0] in settingsWearViewModel.supportedDomains
}
.values
.toList()
}
}
val scaffoldState = rememberScaffoldState()
LaunchedEffect("snackbar") {
@ -92,49 +113,42 @@ fun LoadWearFavoritesSettings(
Text(
text = stringResource(commonR.string.wear_set_favorites),
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(horizontal = 16.dp).padding(bottom = 16.dp)
modifier = Modifier.padding(horizontal = 16.dp)
)
}
item {
SingleEntityPicker(
entities = validEntities,
currentEntity = null,
onEntityCleared = { /* Nothing */ },
onEntitySelected = {
settingsWearViewModel.onEntitySelected(true, it)
return@SingleEntityPicker false // Clear input
},
modifier = Modifier.padding(all = 16.dp),
label = { Text(stringResource(commonR.string.add_favorite)) }
)
}
items(favoriteEntities.size, { favoriteEntities[it] }) { index ->
val favoriteEntityID = favoriteEntities[index].replace("[", "").replace("]", "")
for (entity in validEntities) {
if (entity.entityId == favoriteEntityID) {
val favoriteAttributes = entity.attributes as Map<*, *>
ReorderableItem(
reorderableState = reorderState,
key = favoriteEntities[index]
) { isDragging ->
WearFavoriteEntityRow(
entityName = favoriteAttributes["friendly_name"].toString(),
entityDomain = favoriteEntityID.split('.')[0],
onClick = {
settingsWearViewModel.onEntitySelected(
false,
favoriteEntities[index]
)
},
checked = favoriteEntities.contains(favoriteEntities[index]),
draggable = true,
isDragging = isDragging,
reorderableState = reorderState
)
}
}
}
}
item {
Divider()
}
if (validEntities.isNotEmpty()) {
items(validEntities.size, key = { "unchecked.${validEntities[it].entityId}" }) { index ->
val item = validEntities[index]
val itemAttributes = item.attributes as Map<*, *>
if (!favoriteEntities.contains(item.entityId)) {
settingsWearViewModel.entities[favoriteEntityID]?.let {
ReorderableItem(
reorderableState = reorderState,
key = favoriteEntities[index]
) { isDragging ->
WearFavoriteEntityRow(
entityName = itemAttributes["friendly_name"].toString(),
entityDomain = item.domain,
onClick = { settingsWearViewModel.onEntitySelected(true, item.entityId) },
checked = false
entityName = it.friendlyName,
entityId = favoriteEntityID,
onClick = {
settingsWearViewModel.onEntitySelected(
false,
favoriteEntities[index]
)
},
checked = favoriteEntities.contains(favoriteEntities[index]),
draggable = true,
isDragging = isDragging,
reorderableState = reorderState
)
}
}
@ -146,7 +160,7 @@ fun LoadWearFavoritesSettings(
@Composable
fun WearFavoriteEntityRow(
entityName: String,
entityDomain: String,
entityId: String,
onClick: () -> Unit,
checked: Boolean,
draggable: Boolean = false,
@ -154,10 +168,7 @@ fun WearFavoriteEntityRow(
reorderableState: ReorderableLazyListState? = null
) {
val surfaceElevation = animateDpAsState(targetValue = if (isDragging) 8.dp else 0.dp)
var rowModifier = Modifier
.clickable { onClick() }
.fillMaxWidth()
.padding(all = 16.dp)
var rowModifier = Modifier.fillMaxWidth().heightIn(min = 72.dp)
if (draggable && reorderableState != null) {
rowModifier = rowModifier.then(Modifier.detectReorderAfterLongPress(reorderableState))
}
@ -168,19 +179,20 @@ fun WearFavoriteEntityRow(
verticalAlignment = Alignment.CenterVertically,
modifier = rowModifier
) {
Checkbox(
checked = checked,
modifier = Modifier.padding(end = 16.dp),
onCheckedChange = null // Handled by parent Row clickable modifier
)
Column(
modifier = Modifier.weight(1f)
modifier = Modifier.weight(1f).padding(start = 16.dp)
) {
Text(text = entityName, style = MaterialTheme.typography.body1)
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
Text(text = getEntityDomainString(entityDomain), style = MaterialTheme.typography.body2)
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(
@ -189,7 +201,7 @@ fun WearFavoriteEntityRow(
colorFilter = ColorFilter.tint(LocalContentColor.current),
modifier = Modifier
.size(width = 40.dp, height = 24.dp)
.padding(start = 16.dp)
.padding(end = 16.dp)
.alpha(LocalContentAlpha.current)
)
}

View file

@ -133,7 +133,10 @@ fun ManageTilesView(
entities = viewModel.sortedEntities,
currentEntity = viewModel.selectedEntityId,
onEntityCleared = { viewModel.selectEntityId("") },
onEntitySelected = viewModel::selectEntityId,
onEntitySelected = {
viewModel.selectEntityId(it)
return@SingleEntityPicker true
},
modifier = Modifier
.padding(10.dp)
.fillMaxWidth(),

View file

@ -7,6 +7,7 @@ import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
@ -37,6 +38,7 @@ import io.homeassistant.companion.android.common.R
import io.homeassistant.companion.android.settings.shortcuts.ManageShortcutsSettingsFragment
import io.homeassistant.companion.android.settings.shortcuts.ManageShortcutsViewModel
import io.homeassistant.companion.android.util.compose.ServerDropdownButton
import io.homeassistant.companion.android.util.compose.SingleEntityPicker
@RequiresApi(Build.VERSION_CODES.N_MR1)
@Composable
@ -78,7 +80,6 @@ private fun CreateShortcutView(
showIconDialog: (tag: String) -> Unit
) {
val context = LocalContext.current
var expandedEntity by remember { mutableStateOf(false) }
var expandedPinnedShortcuts by remember { mutableStateOf(false) }
val index = i + 1
@ -144,7 +145,8 @@ private fun CreateShortcutView(
onValueChange = { viewModel.shortcuts[i].id.value = it },
label = {
Text(stringResource(id = R.string.shortcut_pinned_id))
}
},
modifier = Modifier.fillMaxWidth()
)
}
@ -185,7 +187,7 @@ private fun CreateShortcutView(
}
)
},
modifier = Modifier.padding(top = 16.dp)
modifier = Modifier.fillMaxWidth().padding(top = 16.dp)
)
TextField(
@ -200,7 +202,7 @@ private fun CreateShortcutView(
}
)
},
modifier = Modifier.padding(top = 16.dp, bottom = 16.dp)
modifier = Modifier.fillMaxWidth().padding(vertical = 16.dp)
)
if (viewModel.servers.size > 1 || viewModel.servers.none { it.id == shortcut.serverId.value }) {
@ -233,26 +235,17 @@ private fun CreateShortcutView(
modifier = Modifier.padding(top = 16.dp, bottom = 16.dp)
)
} else {
Text(
text = stringResource(id = R.string.entity_id),
fontSize = 15.sp
)
OutlinedButton(onClick = { expandedEntity = true }) {
Text(
text = viewModel.shortcuts[i].path.value
)
}
DropdownMenu(expanded = expandedEntity, onDismissRequest = { expandedEntity = false }) {
viewModel.entities[shortcut.serverId.value]?.forEach {
DropdownMenuItem(onClick = {
viewModel.shortcuts[i].path.value = "entityId:${it.entityId}"
expandedEntity = false
}) {
Text(text = it.entityId, fontSize = 15.sp)
}
SingleEntityPicker(
entities = viewModel.entities[shortcut.serverId.value].orEmpty(),
currentEntity = viewModel.shortcuts[i].path.value.split(":").getOrNull(1),
onEntityCleared = {
viewModel.shortcuts[i].path.value = ""
},
onEntitySelected = {
viewModel.shortcuts[i].path.value = "entityId:$it"
return@SingleEntityPicker true
}
}
)
}
for (item in viewModel.dynamicShortcuts) {
if (item.id == shortcutId) {
@ -326,7 +319,7 @@ private fun ShortcutRadioButtonRow(viewModel: ManageShortcutsViewModel, type: St
selected = viewModel.shortcuts[index].type.value == type,
onClick = { viewModel.shortcuts[index].type.value = type }
)
Text(stringResource(id = if (type == "lovelace") R.string.lovelace else R.string.entity_id))
Text(stringResource(id = if (type == "lovelace") R.string.lovelace else R.string.entity))
}
}

View file

@ -43,7 +43,7 @@ fun SingleEntityPicker(
entities: List<Entity<*>>,
currentEntity: String?,
onEntityCleared: () -> Unit,
onEntitySelected: (String) -> Unit,
onEntitySelected: (String) -> Boolean,
modifier: Modifier = Modifier,
label: @Composable (() -> Unit) = { Text(stringResource(commonR.string.select_entity_to_display)) }
) {
@ -74,14 +74,14 @@ fun SingleEntityPicker(
}
}
// The amount of items is limited because Compose ExposedDropdownMenu isn't lazy
listTooLarge = items.size > 150
listTooLarge = items.size > 75
items.sortedWith(
compareBy(
{ !it.friendlyName.startsWith(query, ignoreCase = true) },
{ !it.entityId.split(".")[1].startsWith(query.replace(" ", "_"), ignoreCase = true) },
{ it.friendlyName.lowercase() }
)
).take(150)
).take(75)
}
}
@ -120,10 +120,10 @@ fun SingleEntityPicker(
list.forEach {
DropdownMenuItem(
onClick = {
onEntitySelected(it.entityId)
inputValue = it.friendlyName
focusManager.clearFocus()
val setInput = onEntitySelected(it.entityId)
inputValue = if (setInput) it.friendlyName else ""
expanded = false
focusManager.clearFocus()
},
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp)
) {

View file

@ -11,6 +11,7 @@
<string name="activate">Activate</string>
<string name="activity_intent_error">Unable to send activity intent, please check command format</string>
<string name="add_device">Add device</string>
<string name="add_favorite">Add favorite</string>
<string name="add_service_data_field">Add Field</string>
<string name="add_shortcut">Add Shortcut</string>
<string name="add_ssid">Add</string>