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:
Joris Pelgröm 2022-06-09 15:54:53 +02:00 committed by GitHub
parent ffaf9fa11a
commit 0f0edcf4a9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 117 additions and 86 deletions

View file

@ -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")
}

View file

@ -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 }

View file

@ -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) {

View file

@ -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>