diff --git a/app/src/full/java/io/homeassistant/companion/android/settings/wear/views/SettingsWearFavoritesView.kt b/app/src/full/java/io/homeassistant/companion/android/settings/wear/views/SettingsWearFavoritesView.kt index 4fcae86fc..c7b8da42c 100644 --- a/app/src/full/java/io/homeassistant/companion/android/settings/wear/views/SettingsWearFavoritesView.kt +++ b/app/src/full/java/io/homeassistant/companion/android/settings/wear/views/SettingsWearFavoritesView.kt @@ -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>>(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) ) } diff --git a/app/src/main/java/io/homeassistant/companion/android/settings/qs/views/ManageTilesView.kt b/app/src/main/java/io/homeassistant/companion/android/settings/qs/views/ManageTilesView.kt index 9cf0bd7bc..6744b3b7e 100755 --- a/app/src/main/java/io/homeassistant/companion/android/settings/qs/views/ManageTilesView.kt +++ b/app/src/main/java/io/homeassistant/companion/android/settings/qs/views/ManageTilesView.kt @@ -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(), diff --git a/app/src/main/java/io/homeassistant/companion/android/settings/shortcuts/views/ManageShortcutsView.kt b/app/src/main/java/io/homeassistant/companion/android/settings/shortcuts/views/ManageShortcutsView.kt index 39007cdb8..bfa303d5d 100755 --- a/app/src/main/java/io/homeassistant/companion/android/settings/shortcuts/views/ManageShortcutsView.kt +++ b/app/src/main/java/io/homeassistant/companion/android/settings/shortcuts/views/ManageShortcutsView.kt @@ -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)) } } diff --git a/app/src/main/java/io/homeassistant/companion/android/util/compose/SingleEntityPicker.kt b/app/src/main/java/io/homeassistant/companion/android/util/compose/SingleEntityPicker.kt index fae8abba2..b0a174532 100644 --- a/app/src/main/java/io/homeassistant/companion/android/util/compose/SingleEntityPicker.kt +++ b/app/src/main/java/io/homeassistant/companion/android/util/compose/SingleEntityPicker.kt @@ -43,7 +43,7 @@ fun SingleEntityPicker( entities: List>, 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) ) { diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index 1dd737537..66c0109c3 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -11,6 +11,7 @@ Activate Unable to send activity intent, please check command format Add device + Add favorite Add Field Add Shortcut Add