@@ -28,7 +28,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape
2828import androidx.compose.material.icons.Icons
2929import androidx.compose.material.icons.filled.KeyboardArrowDown
3030import androidx.compose.material3.Icon
31- import androidx.compose.material3.MaterialTheme
3231import androidx.compose.material3.MaterialTheme.colorScheme
3332import androidx.compose.material3.Surface
3433import androidx.compose.material3.Text
@@ -41,6 +40,7 @@ import androidx.compose.runtime.mutableIntStateOf
4140import androidx.compose.runtime.mutableStateOf
4241import androidx.compose.runtime.remember
4342import androidx.compose.runtime.rememberCoroutineScope
43+ import androidx.compose.runtime.rememberUpdatedState
4444import androidx.compose.runtime.setValue
4545import androidx.compose.runtime.snapshotFlow
4646import androidx.compose.ui.Alignment
@@ -52,7 +52,6 @@ import androidx.compose.ui.platform.LocalResources
5252import androidx.compose.ui.unit.dp
5353import androidx.compose.ui.unit.sp
5454import com.nextcloud.talk.R
55- import com.nextcloud.talk.chat.UnreadMessagesPopup
5655import com.nextcloud.talk.chat.data.model.ChatMessage
5756import com.nextcloud.talk.chat.viewmodels.ChatViewModel
5857import com.nextcloud.talk.data.user.model.User
@@ -74,7 +73,9 @@ private val AUTHOR_TEXT_SIZE = 12.sp
7473fun GetNewChatView (
7574 chatItems : List <ChatViewModel .ChatItem >,
7675 conversationThreadId : Long? = null,
77- onLoadMore : (() -> Unit? )?
76+ onLoadMore : (() -> Unit? )? ,
77+ advanceLocalLastReadMessageIfNeeded : ((Int ) -> Unit? )? ,
78+ updateRemoteLastReadMessageIfNeeded : (() -> Unit? )?
7879) {
7980 val listState = rememberLazyListState()
8081 val showUnreadPopup = remember { mutableStateOf(false ) }
@@ -104,6 +105,8 @@ fun GetNewChatView(
104105 // Show floating scroll-to-newest button when not at newest
105106 val showScrollToNewest by remember { derivedStateOf { ! isAtNewest } }
106107
108+ val latestChatItems by rememberUpdatedState(chatItems)
109+
107110 // Track newest message and show unread popup
108111 LaunchedEffect (chatItems) {
109112 if (chatItems.isEmpty()) return @LaunchedEffect
@@ -165,8 +168,7 @@ fun GetNewChatView(
165168 val stickyDateHeaderText by remember(listState, chatItems) {
166169 derivedStateOf {
167170 chatItems.getOrNull(
168- listState.layoutInfo.visibleItemsInfo
169- .lastOrNull()?.index ? : 0
171+ listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index ? : 0
170172 )?.let { item ->
171173 when (item) {
172174 is ChatViewModel .ChatItem .MessageItem ->
@@ -185,6 +187,7 @@ fun GetNewChatView(
185187 // Only listen to scroll if user is away from newest messages. This ensures the stickyHeader is not shown on
186188 // every new received message when being at the bottom of the list (because this triggers a scroll).
187189 if (! isNearNewest) {
190+ updateRemoteLastReadMessageIfNeeded?.invoke()
188191 snapshotFlow { listState.isScrollInProgress }
189192 .collectLatest { scrolling ->
190193 if (scrolling) {
@@ -199,6 +202,20 @@ fun GetNewChatView(
199202 }
200203 }
201204
205+ LaunchedEffect (isAtNewest) {
206+ if (! isAtNewest) return @LaunchedEffect
207+
208+ latestChatItems
209+ .getOrNull(listState.firstVisibleItemIndex)
210+ ?.let { item ->
211+ // It might not always be a chat message. Not calling advanceLocalLastReadMessageIfNeeded should not
212+ // matter. This should be triggered often enough so it's okay when it's true the next times.
213+ if (item is ChatViewModel .ChatItem .MessageItem ) {
214+ advanceLocalLastReadMessageIfNeeded?.invoke(item.message.jsonMessageId)
215+ }
216+ }
217+ }
218+
202219 val stickyDateHeaderAlpha by animateFloatAsState(
203220 targetValue = if (stickyDateHeader && stickyDateHeaderText.isNotEmpty()) 1f else 0f ,
204221 animationSpec = tween(durationMillis = if (stickyDateHeader) 500 else 1000 ),
0 commit comments