Browse Source

RoomList: simple implementation of setRange

feature/bma/flipper
ganfra 2 years ago
parent
commit
a75b906414
  1. 30
      features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListScreen.kt
  2. 7
      features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListViewModel.kt
  3. 137
      features/roomlist/src/main/java/io/element/android/x/features/roomlist/components/RoomSummaryRow.kt
  4. 13
      libraries/matrix/src/main/java/io/element/android/x/matrix/MatrixClient.kt
  5. 21
      libraries/matrix/src/main/java/io/element/android/x/matrix/room/RoomSummaryDataSource.kt

30
features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListScreen.kt

@ -13,7 +13,9 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
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
@ -25,7 +27,7 @@ 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.RoomItem 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.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
@ -56,6 +58,7 @@ fun RoomListScreen(
isLoginOut = logoutAction is Loading, isLoginOut = logoutAction is Loading,
filter = filter, filter = filter,
onFilterChanged = viewModel::filterRoom, onFilterChanged = viewModel::filterRoom,
onScrollOver = viewModel::updateVisibleRange
) )
} }
@ -67,12 +70,31 @@ fun RoomListContent(
filter: String, filter: String,
onFilterChanged: (String) -> Unit, onFilterChanged: (String) -> Unit,
onLogoutClicked: () -> Unit, onLogoutClicked: () -> Unit,
onScrollOver: (IntRange) -> Unit,
isLoginOut: Boolean, isLoginOut: Boolean,
) { ) {
fun onRoomClicked(room: RoomListRoomSummary) {
onRoomClicked(room.roomId)
}
val appBarState = rememberTopAppBarState() val appBarState = rememberTopAppBarState()
val lazyListState = rememberLazyListState() val lazyListState = rememberLazyListState()
val visibleRange by remember {
derivedStateOf {
val layoutInfo = lazyListState.layoutInfo
val firstItemIndex = layoutInfo.visibleItemsInfo.firstOrNull()?.index ?: 0
val size = layoutInfo.visibleItemsInfo.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")
Scaffold( Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = { topBar = {
@ -91,9 +113,7 @@ fun RoomListContent(
state = lazyListState, state = lazyListState,
) { ) {
items(roomSummaries) { room -> items(roomSummaries) { room ->
RoomItem(room = room) { RoomSummaryRow(room = room, onClick = ::onRoomClicked)
onRoomClicked(it)
}
} }
} }
} }
@ -120,6 +140,7 @@ private fun PreviewableRoomListContent() {
filter = "filter", filter = "filter",
onFilterChanged = {}, onFilterChanged = {},
isLoginOut = false, isLoginOut = false,
onScrollOver = {}
) )
} }
} }
@ -136,6 +157,7 @@ private fun PreviewableDarkRoomListContent() {
filter = "filter", filter = "filter",
onFilterChanged = {}, onFilterChanged = {},
isLoginOut = true, isLoginOut = true,
onScrollOver = {}
) )
} }
} }

7
features/roomlist/src/main/java/io/element/android/x/features/roomlist/RoomListViewModel.kt

@ -66,6 +66,13 @@ class RoomListViewModel(
} }
} }
fun updateVisibleRange(range: IntRange) {
viewModelScope.launch {
if (range.isEmpty()) return@launch
client.roomSummaryDataSource().setSlidingSyncRange(range)
}
}
private fun handleInit() { private fun handleInit() {
suspend { suspend {
val userAvatarUrl = client.loadUserAvatarURLString().getOrNull() val userAvatarUrl = client.loadUserAvatarURLString().getOrNull()

137
features/roomlist/src/main/java/io/element/android/x/features/roomlist/components/RoomSummaryRow.kt

@ -0,0 +1,137 @@
package io.element.android.x.features.roomlist.components
import Avatar
import androidx.compose.foundation.background
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.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.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.google.accompanist.placeholder.material.placeholder
import io.element.android.x.features.roomlist.model.RoomListRoomSummary
private val minHeight = 72.dp
@Composable
internal fun RoomSummaryRow(
modifier: Modifier = Modifier,
room: RoomListRoomSummary,
onClick: (RoomListRoomSummary) -> Unit
) {
val clickModifier = if (room.isPlaceholder) {
Modifier
} else {
Modifier.clickable(
onClick = { onClick(room) },
indication = rememberRipple(),
interactionSource = remember { MutableInteractionSource() }
)
}
Box(
modifier = modifier
.fillMaxWidth()
.heightIn(min = minHeight)
.then(clickModifier)
) {
DefaultRoomSummaryRow(modifier = modifier, room = room)
}
}
@Composable
internal fun DefaultRoomSummaryRow(
modifier: Modifier = Modifier,
room: RoomListRoomSummary,
) {
val placeholderShape = PlaceholderShape()
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.height(IntrinsicSize.Min),
verticalAlignment = CenterVertically
) {
Avatar(
room.avatarData,
modifier = Modifier.placeholder(room.isPlaceholder, shape = CircleShape)
)
Column(
modifier = Modifier
.padding(start = 12.dp, end = 4.dp, top = 12.dp, bottom = 12.dp)
.alignByBaseline()
.weight(1f)
) {
// Name
Text(
modifier = Modifier
.placeholder(room.isPlaceholder, shape = placeholderShape),
fontSize = 16.sp,
fontWeight = FontWeight.SemiBold,
text = room.name,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
// Last Message
Text(
modifier = Modifier.placeholder(room.isPlaceholder, shape = placeholderShape),
text = room.lastMessage?.toString().orEmpty(),
color = MaterialTheme.colorScheme.secondary,
fontSize = 14.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
// Timestamp and Unread
Column(
modifier = Modifier
.alignByBaseline(),
) {
Text(
modifier = Modifier.placeholder(room.isPlaceholder, shape = placeholderShape),
fontSize = 12.sp,
text = room.timestamp ?: "",
color = MaterialTheme.colorScheme.secondary,
)
Spacer(modifier.size(4.dp))
val unreadIndicatorColor =
if (room.hasUnread) MaterialTheme.colorScheme.primary else Color.Transparent
Box(
modifier = Modifier
.size(12.dp)
.clip(CircleShape)
.background(unreadIndicatorColor)
.align(Alignment.End),
)
}
}
}
@Composable
fun PlaceholderShape(): GenericShape {
return GenericShape { size, _ ->
val rect = Rect(
0f,
size.height / 4,
size.width,
size.height - size.height / 4
)
addRect(rect)
}
}

13
libraries/matrix/src/main/java/io/element/android/x/matrix/MatrixClient.kt

@ -44,19 +44,19 @@ class MatrixClient internal constructor(
.requiredState( .requiredState(
requiredState = listOf( requiredState = listOf(
RequiredState(key = "m.room.avatar", value = ""), RequiredState(key = "m.room.avatar", value = ""),
RequiredState(key = "m.room.name", value = ""),
RequiredState(key = "m.room.encryption", value = ""), RequiredState(key = "m.room.encryption", value = ""),
) )
) )
.name(name = "HomeScreenView") .name(name = "HomeScreenView")
.syncMode(mode = SlidingSyncMode.FULL_SYNC) .syncMode(mode = SlidingSyncMode.SELECTIVE)
.addRange(0u, 10u)
.build() .build()
private val slidingSync = client private val slidingSync = client
.slidingSync() .slidingSync()
.homeserver("https://slidingsync.lab.element.dev") .homeserver("https://slidingsync.lab.element.dev")
.withCommonExtensions() .withCommonExtensions()
.coldCache("ElementX") //.coldCache("ElementX")
.addView(slidingSyncView) .addView(slidingSyncView)
.build() .build()
@ -66,7 +66,8 @@ class MatrixClient internal constructor(
slidingSyncObserverProxy.updateSummaryFlow, slidingSyncObserverProxy.updateSummaryFlow,
slidingSync, slidingSync,
slidingSyncView, slidingSyncView,
dispatchers dispatchers,
::onRestartSync
) )
private var slidingSyncObserverToken: StoppableSpawn? = null private var slidingSyncObserverToken: StoppableSpawn? = null
@ -77,6 +78,10 @@ class MatrixClient internal constructor(
client.setDelegate(clientDelegate) client.setDelegate(clientDelegate)
} }
private fun onRestartSync(){
slidingSyncObserverToken = slidingSync.sync()
}
fun getRoom(roomId: String): MatrixRoom? { fun getRoom(roomId: String): MatrixRoom? {
val slidingSyncRoom = slidingSync.getRoom(roomId) ?: return null val slidingSyncRoom = slidingSync.getRoom(roomId) ?: return null
val room = slidingSyncRoom.fullRoom() ?: return null val room = slidingSyncRoom.fullRoom() ?: return null

21
libraries/matrix/src/main/java/io/element/android/x/matrix/room/RoomSummaryDataSource.kt

@ -12,6 +12,7 @@ import java.util.*
interface RoomSummaryDataSource { interface RoomSummaryDataSource {
fun roomSummaries(): Flow<List<RoomSummary>> fun roomSummaries(): Flow<List<RoomSummary>>
fun setSlidingSyncRange(range: IntRange)
} }
internal class RustRoomSummaryDataSource( internal class RustRoomSummaryDataSource(
@ -19,7 +20,8 @@ internal class RustRoomSummaryDataSource(
private val slidingSync: SlidingSync, private val slidingSync: SlidingSync,
private val slidingSyncView: SlidingSyncView, private val slidingSyncView: SlidingSyncView,
private val coroutineDispatchers: CoroutineDispatchers, private val coroutineDispatchers: CoroutineDispatchers,
private val roomSummaryDetailsFactory: RoomSummaryDetailsFactory = RoomSummaryDetailsFactory() private val onRestartSync: () -> Unit,
private val roomSummaryDetailsFactory: RoomSummaryDetailsFactory = RoomSummaryDetailsFactory(),
) : RoomSummaryDataSource, Closeable { ) : RoomSummaryDataSource, Closeable {
private val coroutineScope = CoroutineScope(SupervisorJob() + coroutineDispatchers.io) private val coroutineScope = CoroutineScope(SupervisorJob() + coroutineDispatchers.io)
@ -69,6 +71,12 @@ internal class RustRoomSummaryDataSource(
return roomSummaries.sample(50) return roomSummaries.sample(50)
} }
override fun setSlidingSyncRange(range: IntRange) {
Timber.v("setVisibleRange=$range")
slidingSyncView.setRange(range.first.toUInt(), range.last.toUInt())
onRestartSync()
}
private suspend fun didReceiveSyncUpdate(summary: UpdateSummary) { private suspend fun didReceiveSyncUpdate(summary: UpdateSummary) {
Timber.v("UpdateRooms with identifiers: ${summary.rooms}") Timber.v("UpdateRooms with identifiers: ${summary.rooms}")
if (state.value != SlidingSyncState.LIVE) { if (state.value != SlidingSyncState.LIVE) {
@ -140,11 +148,12 @@ internal class RustRoomSummaryDataSource(
) )
} }
private suspend fun updateRoomSummaries(block: MutableList<RoomSummary>.() -> Unit) = withContext(coroutineDispatchers.diffUpdateDispatcher){ private suspend fun updateRoomSummaries(block: MutableList<RoomSummary>.() -> Unit) =
val mutableRoomSummaries = roomSummaries.value.toMutableList() withContext(coroutineDispatchers.diffUpdateDispatcher) {
block(mutableRoomSummaries) val mutableRoomSummaries = roomSummaries.value.toMutableList()
roomSummaries.value = mutableRoomSummaries block(mutableRoomSummaries)
} roomSummaries.value = mutableRoomSummaries
}
fun SlidingSyncViewRoomsListDiff.isInvalidation(): Boolean { fun SlidingSyncViewRoomsListDiff.isInvalidation(): Boolean {
return when (this) { return when (this) {

Loading…
Cancel
Save