Selected users: if scrollable, make last one peek

If there are enough selected users that they can't all be displayed,
add extra padding in between the users to ensure that the last
visible one is half visible to provide some scroll affordance.
This commit is contained in:
Chris Smith 2023-06-01 10:17:28 +01:00
parent 9515435102
commit f0a3981df5

View file

@ -16,18 +16,26 @@
package io.element.android.libraries.matrix.ui.components package io.element.android.libraries.matrix.ui.components
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewDark
@ -35,6 +43,7 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import kotlin.math.floor
@Composable @Composable
fun SelectedUsersList( fun SelectedUsersList(
@ -56,16 +65,60 @@ fun SelectedUsersList(
} }
} }
val rowWidth by remember {
derivedStateOf {
lazyListState.layoutInfo.viewportSize.width - lazyListState.layoutInfo.beforeContentPadding
}
}
LazyRow( LazyRow(
state = lazyListState, state = lazyListState,
modifier = modifier, modifier = modifier
.fillMaxWidth(),
contentPadding = contentPadding, contentPadding = contentPadding,
horizontalArrangement = Arrangement.spacedBy(24.dp),
) { ) {
items(selectedUsers.toList()) { matrixUser -> itemsIndexed(selectedUsers.toList()) { index, matrixUser ->
SelectedUser( Layout(
matrixUser = matrixUser, content = {
onUserRemoved = onUserRemoved, SelectedUser(
matrixUser = matrixUser,
onUserRemoved = onUserRemoved,
)
},
measurePolicy = { measurables, constraints ->
// Measures out extra space between each user to ensure that the last visible user is exactly half visible, giving an affordance that
// the user can scroll to see more.
val placeable = measurables.first().measure(constraints)
val isLastItem = index == selectedUsers.lastIndex
val width = if (isLastItem) {
// Don't add any padding on the final item
placeable.width
} else {
val minimumSpacing = 24.dp.toPx()
val userWidth = placeable.width + minimumSpacing
val maxVisibleUsers = rowWidth / userWidth
if (maxVisibleUsers >= selectedUsers.size) {
// If we can fit all the users in, don't do anything fancy
(placeable.width + minimumSpacing).toInt()
} else {
// Round down the number of visible users to end with a state where one is half visible
val targetFraction = (placeable.width / 2) / userWidth
val targetUsers = floor(maxVisibleUsers - targetFraction) + targetFraction
// Work out how much extra spacing we need to reduce the number of users that much, then split it evenly amongst the visible users
val extraSpacing = (maxVisibleUsers - targetUsers) * userWidth
val extraSpacingPerUser = (extraSpacing) / floor(targetUsers)
(placeable.width + minimumSpacing + extraSpacingPerUser).toInt()
}
}
layout(width, placeable.height) {
placeable.place(0, 0)
}
}
) )
} }
} }
@ -81,7 +134,23 @@ internal fun SelectedUsersListDarkPreview() = ElementPreviewDark { ContentToPrev
@Composable @Composable
private fun ContentToPreview() { private fun ContentToPreview() {
SelectedUsersList( Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
selectedUsers = aMatrixUserList().take(6).toImmutableList(), // Two users that will be visible with no scrolling
) SelectedUsersList(
selectedUsers = aMatrixUserList().take(2).toImmutableList(),
modifier = Modifier
.width(200.dp)
.border(1.dp, Color.Red)
)
// Multiple users that don't fit, so will be spaced out per the measure policy
for (i in 0..5) {
SelectedUsersList(
selectedUsers = aMatrixUserList().take(6).toImmutableList(),
modifier = Modifier
.width((200 + (i * 20)).dp)
.border(1.dp, Color.Red)
)
}
}
} }