mirror of
https://github.com/home-assistant/android
synced 2024-10-15 04:22:54 +00:00
Bump reorderable and fix/improve design (#2587)
* Bump reorderable and fix breaking changes * Only include reorderable with full version * Improve "Wear Favorite Entities" screen design - Fix too small touch targets for dragging - Fix no background resulting in overlapping text when dragging - Update design to standard Material design - Re-use Composable for selected and unselected favorite entities
This commit is contained in:
parent
ffaf9fa11a
commit
0f0edcf4a9
|
@ -196,7 +196,7 @@ dependencies {
|
|||
implementation("com.mikepenz:iconics-core:5.3.3")
|
||||
implementation("com.mikepenz:iconics-compose:5.3.3")
|
||||
implementation("com.mikepenz:community-material-typeface:6.4.95.0-kotlin@aar")
|
||||
implementation("org.burnoutcrew.composereorderable:reorderable:0.7.4")
|
||||
"fullImplementation"("org.burnoutcrew.composereorderable:reorderable:0.9.0")
|
||||
implementation("com.github.AppDevNext:ChangeLog:3.4")
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,6 @@ import io.homeassistant.companion.android.common.data.integration.Entity
|
|||
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
|
||||
import kotlinx.coroutines.launch
|
||||
import org.burnoutcrew.reorderable.ItemPosition
|
||||
import org.burnoutcrew.reorderable.move
|
||||
import javax.inject.Inject
|
||||
import io.homeassistant.companion.android.common.R as commonR
|
||||
|
||||
|
@ -134,10 +133,12 @@ class SettingsWearViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
fun onMove(fromItem: ItemPosition, toItem: ItemPosition) {
|
||||
favoriteEntityIds.move(
|
||||
favoriteEntityIds.indexOfFirst { it == fromItem.key },
|
||||
favoriteEntityIds.indexOfFirst { it == toItem.key }
|
||||
)
|
||||
favoriteEntityIds.apply {
|
||||
add(
|
||||
favoriteEntityIds.indexOfFirst { it == toItem.key },
|
||||
removeAt(favoriteEntityIds.indexOfFirst { it == fromItem.key })
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun canDragOver(position: ItemPosition) = favoriteEntityIds.any { it == position.key }
|
||||
|
|
|
@ -1,32 +1,41 @@
|
|||
package io.homeassistant.companion.android.settings.wear.views
|
||||
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.gestures.Orientation
|
||||
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.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.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.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
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 androidx.compose.ui.unit.sp
|
||||
import com.mikepenz.iconics.IconicsDrawable
|
||||
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.settings.wear.SettingsWearViewModel
|
||||
import org.burnoutcrew.reorderable.ReorderableItem
|
||||
import org.burnoutcrew.reorderable.ReorderableLazyListState
|
||||
import org.burnoutcrew.reorderable.detectReorderAfterLongPress
|
||||
import org.burnoutcrew.reorderable.draggedItem
|
||||
import org.burnoutcrew.reorderable.rememberReorderState
|
||||
import org.burnoutcrew.reorderable.rememberReorderableLazyListState
|
||||
import org.burnoutcrew.reorderable.reorderable
|
||||
import io.homeassistant.companion.android.common.R as commonR
|
||||
|
||||
|
@ -35,7 +44,13 @@ fun LoadWearFavoritesSettings(
|
|||
settingsWearViewModel: SettingsWearViewModel,
|
||||
onBackClicked: () -> Unit
|
||||
) {
|
||||
val reorderState = rememberReorderState()
|
||||
val reorderState = rememberReorderableLazyListState(
|
||||
onMove = { from, to -> settingsWearViewModel.onMove(from, to) },
|
||||
canDragOver = { settingsWearViewModel.canDragOver(it) },
|
||||
onDragEnd = { _, _ ->
|
||||
settingsWearViewModel.sendHomeFavorites(settingsWearViewModel.favoriteEntityIds.toList())
|
||||
}
|
||||
)
|
||||
|
||||
val validEntities = settingsWearViewModel.entities.filter { it.key.split(".")[0] in settingsWearViewModel.supportedDomains }.values.sortedBy { it.entityId }.toList()
|
||||
val favoriteEntities = settingsWearViewModel.favoriteEntityIds
|
||||
|
@ -47,104 +62,63 @@ fun LoadWearFavoritesSettings(
|
|||
docsLink = WEAR_DOCS_LINK
|
||||
)
|
||||
}
|
||||
) {
|
||||
) { contentPadding ->
|
||||
LazyColumn(
|
||||
state = reorderState.listState,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
contentPadding = PaddingValues(all = 16.dp),
|
||||
modifier = Modifier.reorderable(
|
||||
reorderState,
|
||||
{ from, to -> settingsWearViewModel.onMove(from, to) },
|
||||
canDragOver = { settingsWearViewModel.canDragOver(it) },
|
||||
onDragEnd = { _, _ ->
|
||||
settingsWearViewModel.sendHomeFavorites(settingsWearViewModel.favoriteEntityIds.toList())
|
||||
}
|
||||
)
|
||||
contentPadding = PaddingValues(vertical = 16.dp),
|
||||
modifier = Modifier
|
||||
.padding(contentPadding)
|
||||
.reorderable(reorderState)
|
||||
) {
|
||||
item {
|
||||
Text(
|
||||
text = stringResource(commonR.string.wear_set_favorites),
|
||||
fontWeight = FontWeight.Bold
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.padding(horizontal = 16.dp).padding(bottom = 16.dp)
|
||||
)
|
||||
}
|
||||
items(favoriteEntities.size, { favoriteEntities[it] }) { index ->
|
||||
val favoriteEntityID = favoriteEntities[index].replace("[", "").replace("]", "")
|
||||
for (entity in validEntities)
|
||||
for (entity in validEntities) {
|
||||
if (entity.entityId == favoriteEntityID) {
|
||||
val favoriteAttributes = entity.attributes as Map<*, *>
|
||||
val favoriteFriendlyName = favoriteAttributes["friendly_name"].toString()
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(12.dp)
|
||||
.clickable {
|
||||
ReorderableItem(
|
||||
reorderableState = reorderState,
|
||||
key = favoriteEntities[index]
|
||||
) { isDragging ->
|
||||
WearFavoriteEntityRow(
|
||||
entityName = favoriteAttributes["friendly_name"].toString(),
|
||||
entityDomain = favoriteEntityID.split('.')[0],
|
||||
onClick = {
|
||||
settingsWearViewModel.onEntitySelected(
|
||||
false,
|
||||
favoriteEntities[index]
|
||||
)
|
||||
}
|
||||
.draggedItem(
|
||||
reorderState.offsetByKey(favoriteEntities[index]),
|
||||
Orientation.Vertical
|
||||
)
|
||||
.detectReorderAfterLongPress(reorderState)
|
||||
) {
|
||||
val iconBitmap =
|
||||
IconicsDrawable(LocalContext.current, "cmd-drag_vertical").toBitmap()
|
||||
.asImageBitmap()
|
||||
Icon(iconBitmap, "", modifier = Modifier.padding(top = 13.dp))
|
||||
Checkbox(
|
||||
checked = favoriteEntities.contains(favoriteEntities[index]),
|
||||
onCheckedChange = {
|
||||
settingsWearViewModel.onEntitySelected(it, favoriteEntities[index])
|
||||
},
|
||||
modifier = Modifier.padding(end = 5.dp)
|
||||
checked = favoriteEntities.contains(favoriteEntities[index]),
|
||||
draggable = true,
|
||||
isDragging = isDragging,
|
||||
reorderableState = reorderState
|
||||
)
|
||||
Column {
|
||||
Text(
|
||||
text = favoriteFriendlyName,
|
||||
modifier = Modifier.padding(top = 10.dp)
|
||||
)
|
||||
Text(
|
||||
text = getDomainString(favoriteEntityID.split('.')[0]),
|
||||
fontSize = 11.sp
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
item {
|
||||
Divider()
|
||||
}
|
||||
if (!validEntities.isNullOrEmpty()) {
|
||||
items(validEntities.size) { index ->
|
||||
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)) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(15.dp)
|
||||
.clickable {
|
||||
settingsWearViewModel.onEntitySelected(true, item.entityId)
|
||||
}
|
||||
) {
|
||||
Checkbox(
|
||||
checked = false,
|
||||
onCheckedChange = {
|
||||
settingsWearViewModel.onEntitySelected(it, item.entityId)
|
||||
},
|
||||
modifier = Modifier.padding(end = 5.dp)
|
||||
)
|
||||
Column {
|
||||
Text(
|
||||
text = itemAttributes["friendly_name"].toString(),
|
||||
modifier = Modifier.padding(top = 10.dp)
|
||||
)
|
||||
Text(
|
||||
text = getDomainString(item.domain),
|
||||
fontSize = 11.sp
|
||||
)
|
||||
}
|
||||
}
|
||||
WearFavoriteEntityRow(
|
||||
entityName = itemAttributes["friendly_name"].toString(),
|
||||
entityDomain = item.domain,
|
||||
onClick = { settingsWearViewModel.onEntitySelected(true, item.entityId) },
|
||||
checked = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -152,6 +126,61 @@ fun LoadWearFavoritesSettings(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun WearFavoriteEntityRow(
|
||||
entityName: String,
|
||||
entityDomain: 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
|
||||
.clickable { onClick() }
|
||||
.fillMaxWidth()
|
||||
.padding(all = 16.dp)
|
||||
if (draggable && reorderableState != null) {
|
||||
rowModifier = rowModifier.then(Modifier.detectReorderAfterLongPress(reorderableState))
|
||||
}
|
||||
Surface(
|
||||
elevation = surfaceElevation.value
|
||||
) {
|
||||
Row(
|
||||
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)
|
||||
) {
|
||||
Text(text = entityName, style = MaterialTheme.typography.body1)
|
||||
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
|
||||
Text(text = getDomainString(entityDomain), style = MaterialTheme.typography.body2)
|
||||
}
|
||||
}
|
||||
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(start = 16.dp)
|
||||
.alpha(LocalContentAlpha.current)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun getDomainString(domain: String): String {
|
||||
return when (domain) {
|
||||
|
|
|
@ -212,6 +212,7 @@
|
|||
<string name="high_accuracy_mode_channel_name">High accuracy location</string>
|
||||
<string name="high_accuracy_mode_notification_title">High accuracy (GPS) mode enabled</string>
|
||||
<string name="history">History</string>
|
||||
<string name="hold_to_reorder">Tap and hold to reorder</string>
|
||||
<string name="home_assistant_not_discover">Unable to find your\nHome Assistant instance</string>
|
||||
<string name="icon">Icon</string>
|
||||
<string name="input_booleans">Input Booleans</string>
|
||||
|
@ -720,7 +721,7 @@
|
|||
<string name="wear_os_category">Wear OS</string>
|
||||
<string name="wear_os_settings_summary">Manage Wear OS App</string>
|
||||
<string name="wear_os_settings_title">Wear OS Settings</string>
|
||||
<string name="wear_set_favorites">Select your favorite entities to appear at the top of the wear home screen. You can also drag and drop to change the order in which they appear.</string>
|
||||
<string name="wear_set_favorites">Select your favorite entities to appear at the top of the Wear home screen. You can also drag and drop to change the order in which they appear.</string>
|
||||
<string name="wear_settings">Wear Device Settings</string>
|
||||
<string name="websocket_listening">Connected to Home Assistant</string>
|
||||
<string name="webview_error_AUTH_SCHEME">Unsupported authentication scheme (not basic or digest), please check network settings.</string>
|
||||
|
|
Loading…
Reference in a new issue