RoomList: avoid recomposition
This commit is contained in:
parent
51367a50ea
commit
c246561aaf
7 changed files with 83 additions and 58 deletions
|
|
@ -17,8 +17,10 @@ import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.Velocity
|
||||||
import com.airbnb.mvrx.Loading
|
import com.airbnb.mvrx.Loading
|
||||||
import com.airbnb.mvrx.Success
|
import com.airbnb.mvrx.Success
|
||||||
import com.airbnb.mvrx.compose.collectAsState
|
import com.airbnb.mvrx.compose.collectAsState
|
||||||
|
|
@ -27,8 +29,8 @@ import io.element.android.x.core.compose.LogCompositions
|
||||||
import io.element.android.x.designsystem.ElementXTheme
|
import io.element.android.x.designsystem.ElementXTheme
|
||||||
import io.element.android.x.designsystem.components.ProgressDialog
|
import io.element.android.x.designsystem.components.ProgressDialog
|
||||||
import io.element.android.x.designsystem.components.avatar.AvatarData
|
import io.element.android.x.designsystem.components.avatar.AvatarData
|
||||||
import io.element.android.x.features.roomlist.components.RoomSummaryRow
|
|
||||||
import io.element.android.x.features.roomlist.components.RoomListTopBar
|
import io.element.android.x.features.roomlist.components.RoomListTopBar
|
||||||
|
import io.element.android.x.features.roomlist.components.RoomSummaryRow
|
||||||
import io.element.android.x.features.roomlist.model.MatrixUser
|
import io.element.android.x.features.roomlist.model.MatrixUser
|
||||||
import io.element.android.x.features.roomlist.model.RoomListRoomSummary
|
import io.element.android.x.features.roomlist.model.RoomListRoomSummary
|
||||||
import io.element.android.x.features.roomlist.model.RoomListViewState
|
import io.element.android.x.features.roomlist.model.RoomListViewState
|
||||||
|
|
@ -89,12 +91,18 @@ fun RoomListContent(
|
||||||
firstItemIndex until firstItemIndex + size
|
firstItemIndex until firstItemIndex + size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!lazyListState.isScrollInProgress) {
|
|
||||||
onScrollOver(visibleRange)
|
|
||||||
}
|
|
||||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(appBarState)
|
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(appBarState)
|
||||||
LogCompositions(tag = "RoomListScreen", msg = "Content")
|
LogCompositions(tag = "RoomListScreen", msg = "Content")
|
||||||
|
|
||||||
|
val nestedScrollConnection = remember {
|
||||||
|
object : NestedScrollConnection {
|
||||||
|
override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
|
||||||
|
onScrollOver(visibleRange)
|
||||||
|
return super.onPostFling(consumed, available)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||||
topBar = {
|
topBar = {
|
||||||
|
|
@ -109,7 +117,9 @@ fun RoomListContent(
|
||||||
content = { padding ->
|
content = { padding ->
|
||||||
Column(modifier = Modifier.padding(padding)) {
|
Column(modifier = Modifier.padding(padding)) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.nestedScroll(nestedScrollConnection),
|
||||||
state = lazyListState,
|
state = lazyListState,
|
||||||
) {
|
) {
|
||||||
items(roomSummaries) { room ->
|
items(roomSummaries) { room ->
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ import io.element.android.x.designsystem.components.avatar.AvatarData
|
||||||
import io.element.android.x.designsystem.components.avatar.AvatarSize
|
import io.element.android.x.designsystem.components.avatar.AvatarSize
|
||||||
import io.element.android.x.features.roomlist.model.MatrixUser
|
import io.element.android.x.features.roomlist.model.MatrixUser
|
||||||
import io.element.android.x.features.roomlist.model.RoomListRoomSummary
|
import io.element.android.x.features.roomlist.model.RoomListRoomSummary
|
||||||
|
import io.element.android.x.features.roomlist.model.RoomListRoomSummaryPlaceholders
|
||||||
import io.element.android.x.features.roomlist.model.RoomListViewState
|
import io.element.android.x.features.roomlist.model.RoomListViewState
|
||||||
import io.element.android.x.features.roomlist.model.createFakePlaceHolders
|
|
||||||
import io.element.android.x.matrix.MatrixClient
|
import io.element.android.x.matrix.MatrixClient
|
||||||
import io.element.android.x.matrix.MatrixInstance
|
import io.element.android.x.matrix.MatrixInstance
|
||||||
import io.element.android.x.matrix.media.MediaResolver
|
import io.element.android.x.matrix.media.MediaResolver
|
||||||
|
|
@ -114,7 +114,7 @@ class RoomListViewModel(
|
||||||
// Note: this second case will prevent to handle correctly the empty case
|
// Note: this second case will prevent to handle correctly the empty case
|
||||||
(it is Success && it().isEmpty() && filter.isEmpty()) -> {
|
(it is Success && it().isEmpty() && filter.isEmpty()) -> {
|
||||||
// Show fake placeholders to avoid having empty screen
|
// Show fake placeholders to avoid having empty screen
|
||||||
Loading(createFakePlaceHolders())
|
Loading(RoomListRoomSummaryPlaceholders.createFakeList(size = 16))
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
it
|
it
|
||||||
|
|
@ -129,7 +129,7 @@ class RoomListViewModel(
|
||||||
): List<RoomListRoomSummary> {
|
): List<RoomListRoomSummary> {
|
||||||
return roomSummaries.parallelMap { roomSummary ->
|
return roomSummaries.parallelMap { roomSummary ->
|
||||||
when (roomSummary) {
|
when (roomSummary) {
|
||||||
is RoomSummary.Empty -> RoomListRoomSummary.placeholder(roomSummary.identifier)
|
is RoomSummary.Empty -> RoomListRoomSummaryPlaceholders.create(roomSummary.identifier)
|
||||||
is RoomSummary.Filled -> {
|
is RoomSummary.Filled -> {
|
||||||
val avatarData = loadAvatarData(
|
val avatarData = loadAvatarData(
|
||||||
roomSummary.details.name,
|
roomSummary.details.name,
|
||||||
|
|
|
||||||
|
|
@ -6,20 +6,26 @@ import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.GenericShape
|
|
||||||
import androidx.compose.material.ripple.rememberRipple
|
import androidx.compose.material.ripple.rememberRipple
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Alignment.Companion.CenterVertically
|
import androidx.compose.ui.Alignment.Companion.CenterVertically
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.geometry.Rect
|
import androidx.compose.ui.geometry.Rect
|
||||||
|
import androidx.compose.ui.geometry.Size
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.Outline
|
||||||
|
import androidx.compose.ui.graphics.Path
|
||||||
|
import androidx.compose.ui.graphics.Shape
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.Density
|
||||||
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.google.accompanist.placeholder.material.placeholder
|
import com.google.accompanist.placeholder.material.placeholder
|
||||||
|
|
@ -49,18 +55,15 @@ internal fun RoomSummaryRow(
|
||||||
.heightIn(min = minHeight)
|
.heightIn(min = minHeight)
|
||||||
.then(clickModifier)
|
.then(clickModifier)
|
||||||
) {
|
) {
|
||||||
DefaultRoomSummaryRow(modifier = modifier, room = room)
|
DefaultRoomSummaryRow(room = room)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun DefaultRoomSummaryRow(
|
internal fun DefaultRoomSummaryRow(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
room: RoomListRoomSummary,
|
room: RoomListRoomSummary,
|
||||||
) {
|
) {
|
||||||
val placeholderShape = PlaceholderShape()
|
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
|
@ -81,7 +84,7 @@ internal fun DefaultRoomSummaryRow(
|
||||||
// Name
|
// Name
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.placeholder(room.isPlaceholder, shape = placeholderShape),
|
.placeholder(room.isPlaceholder, shape = TextPlaceholderShape),
|
||||||
fontSize = 16.sp,
|
fontSize = 16.sp,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.SemiBold,
|
||||||
text = room.name,
|
text = room.name,
|
||||||
|
|
@ -90,7 +93,7 @@ internal fun DefaultRoomSummaryRow(
|
||||||
)
|
)
|
||||||
// Last Message
|
// Last Message
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.placeholder(room.isPlaceholder, shape = placeholderShape),
|
modifier = Modifier.placeholder(room.isPlaceholder, shape = TextPlaceholderShape),
|
||||||
text = room.lastMessage?.toString().orEmpty(),
|
text = room.lastMessage?.toString().orEmpty(),
|
||||||
color = MaterialTheme.colorScheme.secondary,
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
|
|
@ -104,12 +107,12 @@ internal fun DefaultRoomSummaryRow(
|
||||||
.alignByBaseline(),
|
.alignByBaseline(),
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.placeholder(room.isPlaceholder, shape = placeholderShape),
|
modifier = Modifier.placeholder(room.isPlaceholder, shape = TextPlaceholderShape),
|
||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
text = room.timestamp ?: "",
|
text = room.timestamp ?: "",
|
||||||
color = MaterialTheme.colorScheme.secondary,
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
)
|
)
|
||||||
Spacer(modifier.size(4.dp))
|
Spacer(Modifier.size(4.dp))
|
||||||
val unreadIndicatorColor =
|
val unreadIndicatorColor =
|
||||||
if (room.hasUnread) MaterialTheme.colorScheme.primary else Color.Transparent
|
if (room.hasUnread) MaterialTheme.colorScheme.primary else Color.Transparent
|
||||||
Box(
|
Box(
|
||||||
|
|
@ -123,15 +126,25 @@ internal fun DefaultRoomSummaryRow(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
val TextPlaceholderShape = PercentRectangleSizeShape(0.5f)
|
||||||
fun PlaceholderShape(): GenericShape {
|
|
||||||
return GenericShape { size, _ ->
|
class PercentRectangleSizeShape(private val percent: Float) : Shape {
|
||||||
val rect = Rect(
|
override fun createOutline(
|
||||||
0f,
|
size: Size,
|
||||||
size.height / 4,
|
layoutDirection: LayoutDirection,
|
||||||
size.width,
|
density: Density
|
||||||
size.height - size.height / 4
|
): Outline {
|
||||||
)
|
val halfPercent = percent / 2f
|
||||||
addRect(rect)
|
val path = Path().apply {
|
||||||
|
val rect = Rect(
|
||||||
|
0f,
|
||||||
|
size.height * halfPercent,
|
||||||
|
size.width,
|
||||||
|
size.height - (size.height * halfPercent)
|
||||||
|
)
|
||||||
|
addRect(rect)
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
return Outline.Generic(path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
package io.element.android.x.features.roomlist.model
|
package io.element.android.x.features.roomlist.model
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
import io.element.android.x.designsystem.components.avatar.AvatarData
|
import io.element.android.x.designsystem.components.avatar.AvatarData
|
||||||
import io.element.android.x.matrix.core.RoomId
|
import io.element.android.x.matrix.core.RoomId
|
||||||
|
|
||||||
|
@Stable
|
||||||
data class RoomListRoomSummary(
|
data class RoomListRoomSummary(
|
||||||
val id: String,
|
val id: String,
|
||||||
val roomId: RoomId = RoomId(id),
|
val roomId: RoomId = RoomId(id),
|
||||||
|
|
@ -11,19 +13,4 @@ data class RoomListRoomSummary(
|
||||||
val timestamp: String? = null,
|
val timestamp: String? = null,
|
||||||
val lastMessage: CharSequence? = null,
|
val lastMessage: CharSequence? = null,
|
||||||
val avatarData: AvatarData = AvatarData(),
|
val avatarData: AvatarData = AvatarData(),
|
||||||
val isPlaceholder: Boolean = false,
|
val isPlaceholder: Boolean = false,)
|
||||||
) {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun placeholder(id: String): RoomListRoomSummary {
|
|
||||||
return RoomListRoomSummary(
|
|
||||||
id = id,
|
|
||||||
isPlaceholder = true,
|
|
||||||
name = "Short name",
|
|
||||||
timestamp = "hh:mm",
|
|
||||||
lastMessage = "Last message for placeholder",
|
|
||||||
avatarData = AvatarData("S")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
package io.element.android.x.features.roomlist.model
|
||||||
|
|
||||||
|
import io.element.android.x.designsystem.components.avatar.AvatarData
|
||||||
|
|
||||||
|
|
||||||
|
object RoomListRoomSummaryPlaceholders {
|
||||||
|
|
||||||
|
fun create(id: String): RoomListRoomSummary {
|
||||||
|
return RoomListRoomSummary(
|
||||||
|
id = id,
|
||||||
|
isPlaceholder = true,
|
||||||
|
name = "Short name",
|
||||||
|
timestamp = "hh:mm",
|
||||||
|
lastMessage = "Last message for placeholder",
|
||||||
|
avatarData = AvatarData("S")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createFakeList(size: Int): List<RoomListRoomSummary> {
|
||||||
|
return mutableListOf<RoomListRoomSummary>().apply {
|
||||||
|
for (i in 0..size) {
|
||||||
|
add(create("\$fakeRoom$i"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
package io.element.android.x.features.roomlist.model
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a list of 16 RoomListRoomSummary placeholders
|
|
||||||
*/
|
|
||||||
fun createFakePlaceHolders(): List<RoomListRoomSummary> {
|
|
||||||
return mutableListOf<RoomListRoomSummary>().apply {
|
|
||||||
for (i in 0..15) {
|
|
||||||
add(RoomListRoomSummary.placeholder("\$fakeRoom$i"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -20,6 +20,6 @@ internal fun stubbedRoomSummaries(): List<RoomListRoomSummary> {
|
||||||
avatarData = AvatarData("Z"),
|
avatarData = AvatarData("Z"),
|
||||||
id = "roomId2"
|
id = "roomId2"
|
||||||
),
|
),
|
||||||
RoomListRoomSummary.placeholder("roomId2")
|
RoomListRoomSummaryPlaceholders.create("roomId2")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue