RoomList: simple implementation of setRange
This commit is contained in:
parent
6759240687
commit
f9acecf9eb
5 changed files with 194 additions and 14 deletions
|
|
@ -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 = {}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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…
Add table
Add a link
Reference in a new issue