From a87ecde8a478cd8a2d944b40453f97952753e59f Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 23 Nov 2022 15:39:03 +0100 Subject: [PATCH] RoomList: avoid recomposition --- .../x/features/roomlist/RoomListScreen.kt | 20 +++++-- .../x/features/roomlist/RoomListViewModel.kt | 6 +-- .../roomlist/components/RoomSummaryRow.kt | 53 ++++++++++++------- .../roomlist/model/RoomListRoomSummary.kt | 19 ++----- .../model/RoomListRoomSummaryPlaceholders.kt | 28 ++++++++++ .../model/RoomListSummaryPlaceholders.kt | 13 ----- .../x/features/roomlist/model/stubbed.kt | 2 +- 7 files changed, 83 insertions(+), 58 deletions(-) create mode 100644 features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/RoomListRoomSummaryPlaceholders.kt delete mode 100644 features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/RoomListSummaryPlaceholders.kt diff --git a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListScreen.kt b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListScreen.kt index 845dcb7ea6..3880339503 100644 --- a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListScreen.kt +++ b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListScreen.kt @@ -17,8 +17,10 @@ import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Velocity import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Success 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.components.ProgressDialog 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.RoomSummaryRow 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.RoomListViewState @@ -89,12 +91,18 @@ fun RoomListContent( firstItemIndex until firstItemIndex + size } } - if (!lazyListState.isScrollInProgress) { - onScrollOver(visibleRange) - } val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(appBarState) 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( modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { @@ -109,7 +117,9 @@ fun RoomListContent( content = { padding -> Column(modifier = Modifier.padding(padding)) { LazyColumn( - modifier = Modifier.weight(1f), + modifier = Modifier + .weight(1f) + .nestedScroll(nestedScrollConnection), state = lazyListState, ) { items(roomSummaries) { room -> diff --git a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListViewModel.kt b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListViewModel.kt index bd411e04c9..e8d50887c4 100644 --- a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListViewModel.kt +++ b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListViewModel.kt @@ -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.features.roomlist.model.MatrixUser 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.createFakePlaceHolders import io.element.android.x.matrix.MatrixClient import io.element.android.x.matrix.MatrixInstance 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 (it is Success && it().isEmpty() && filter.isEmpty()) -> { // Show fake placeholders to avoid having empty screen - Loading(createFakePlaceHolders()) + Loading(RoomListRoomSummaryPlaceholders.createFakeList(size = 16)) } else -> { it @@ -129,7 +129,7 @@ class RoomListViewModel( ): List { return roomSummaries.parallelMap { roomSummary -> when (roomSummary) { - is RoomSummary.Empty -> RoomListRoomSummary.placeholder(roomSummary.identifier) + is RoomSummary.Empty -> RoomListRoomSummaryPlaceholders.create(roomSummary.identifier) is RoomSummary.Filled -> { val avatarData = loadAvatarData( roomSummary.details.name, diff --git a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/components/RoomSummaryRow.kt b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/components/RoomSummaryRow.kt index b1ef6f2ccf..32857dcd33 100644 --- a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/components/RoomSummaryRow.kt +++ b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/components/RoomSummaryRow.kt @@ -6,20 +6,26 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.GenericShape import androidx.compose.material.ripple.rememberRipple import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.geometry.Size 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.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.sp import com.google.accompanist.placeholder.material.placeholder @@ -49,18 +55,15 @@ internal fun RoomSummaryRow( .heightIn(min = minHeight) .then(clickModifier) ) { - DefaultRoomSummaryRow(modifier = modifier, room = room) + DefaultRoomSummaryRow(room = room) } } @Composable internal fun DefaultRoomSummaryRow( - modifier: Modifier = Modifier, room: RoomListRoomSummary, ) { - val placeholderShape = PlaceholderShape() - Row( modifier = Modifier .fillMaxWidth() @@ -81,7 +84,7 @@ internal fun DefaultRoomSummaryRow( // Name Text( modifier = Modifier - .placeholder(room.isPlaceholder, shape = placeholderShape), + .placeholder(room.isPlaceholder, shape = TextPlaceholderShape), fontSize = 16.sp, fontWeight = FontWeight.SemiBold, text = room.name, @@ -90,7 +93,7 @@ internal fun DefaultRoomSummaryRow( ) // Last Message Text( - modifier = Modifier.placeholder(room.isPlaceholder, shape = placeholderShape), + modifier = Modifier.placeholder(room.isPlaceholder, shape = TextPlaceholderShape), text = room.lastMessage?.toString().orEmpty(), color = MaterialTheme.colorScheme.secondary, fontSize = 14.sp, @@ -104,12 +107,12 @@ internal fun DefaultRoomSummaryRow( .alignByBaseline(), ) { Text( - modifier = Modifier.placeholder(room.isPlaceholder, shape = placeholderShape), + modifier = Modifier.placeholder(room.isPlaceholder, shape = TextPlaceholderShape), fontSize = 12.sp, text = room.timestamp ?: "", color = MaterialTheme.colorScheme.secondary, ) - Spacer(modifier.size(4.dp)) + Spacer(Modifier.size(4.dp)) val unreadIndicatorColor = if (room.hasUnread) MaterialTheme.colorScheme.primary else Color.Transparent Box( @@ -123,15 +126,25 @@ internal fun DefaultRoomSummaryRow( } } -@Composable -fun PlaceholderShape(): GenericShape { - return GenericShape { size, _ -> - val rect = Rect( - 0f, - size.height / 4, - size.width, - size.height - size.height / 4 - ) - addRect(rect) +val TextPlaceholderShape = PercentRectangleSizeShape(0.5f) + +class PercentRectangleSizeShape(private val percent: Float) : Shape { + override fun createOutline( + size: Size, + layoutDirection: LayoutDirection, + density: Density + ): Outline { + val halfPercent = percent / 2f + 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) } -} \ No newline at end of file +} diff --git a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/RoomListRoomSummary.kt b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/RoomListRoomSummary.kt index 551f06fb6c..98e2200df6 100644 --- a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/RoomListRoomSummary.kt +++ b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/RoomListRoomSummary.kt @@ -1,8 +1,10 @@ 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.matrix.core.RoomId +@Stable data class RoomListRoomSummary( val id: String, val roomId: RoomId = RoomId(id), @@ -11,19 +13,4 @@ data class RoomListRoomSummary( val timestamp: String? = null, val lastMessage: CharSequence? = null, val avatarData: AvatarData = AvatarData(), - 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") - ) - } - } -} \ No newline at end of file + val isPlaceholder: Boolean = false,) diff --git a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/RoomListRoomSummaryPlaceholders.kt b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/RoomListRoomSummaryPlaceholders.kt new file mode 100644 index 0000000000..1be8c07b2d --- /dev/null +++ b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/RoomListRoomSummaryPlaceholders.kt @@ -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 { + return mutableListOf().apply { + for (i in 0..size) { + add(create("\$fakeRoom$i")) + } + } + } + +} + diff --git a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/RoomListSummaryPlaceholders.kt b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/RoomListSummaryPlaceholders.kt deleted file mode 100644 index aafb6bcada..0000000000 --- a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/RoomListSummaryPlaceholders.kt +++ /dev/null @@ -1,13 +0,0 @@ -package io.element.android.x.features.roomlist.model - - -/** - * Create a list of 16 RoomListRoomSummary placeholders - */ -fun createFakePlaceHolders(): List { - return mutableListOf().apply { - for (i in 0..15) { - add(RoomListRoomSummary.placeholder("\$fakeRoom$i")) - } - } -} diff --git a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/stubbed.kt b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/stubbed.kt index 55a68ab18e..4c8844b13b 100644 --- a/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/stubbed.kt +++ b/features/roomlist/src/main/java/io/element/android/x/features/roomlist/model/stubbed.kt @@ -20,6 +20,6 @@ internal fun stubbedRoomSummaries(): List { avatarData = AvatarData("Z"), id = "roomId2" ), - RoomListRoomSummary.placeholder("roomId2") + RoomListRoomSummaryPlaceholders.create("roomId2") ) } \ No newline at end of file