mirror of
https://github.com/home-assistant/android
synced 2024-07-22 10:54:12 +00:00
Use new entity picker for more settings (#3662)
This commit is contained in:
parent
bc103636db
commit
cd62f17fac
|
@ -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)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
) {
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue