Skip to content

Commit 11b2d9e

Browse files
committed
pinned messages
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
1 parent 4222fc3 commit 11b2d9e

2 files changed

Lines changed: 85 additions & 2 deletions

File tree

app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,11 @@ import androidx.activity.viewModels
6161
import androidx.appcompat.app.AlertDialog
6262
import androidx.appcompat.view.ContextThemeWrapper
6363
import androidx.cardview.widget.CardView
64+
import androidx.compose.foundation.lazy.LazyListState
65+
import androidx.compose.foundation.lazy.rememberLazyListState
6466
import androidx.compose.material3.MaterialTheme
6567
import androidx.compose.runtime.CompositionLocalProvider
68+
import androidx.compose.runtime.SideEffect
6669
import androidx.compose.runtime.getValue
6770
import androidx.compose.runtime.mutableStateOf
6871
import androidx.compose.runtime.setValue
@@ -149,6 +152,7 @@ import com.nextcloud.talk.signaling.SignalingMessageReceiver
149152
import com.nextcloud.talk.signaling.SignalingMessageSender
150153
import com.nextcloud.talk.threadsoverview.ThreadsOverviewActivity
151154
import com.nextcloud.talk.translate.ui.TranslateActivity
155+
import com.nextcloud.talk.ui.PinnedMessageView
152156
import com.nextcloud.talk.ui.PlaybackSpeed
153157
import com.nextcloud.talk.ui.StatusDrawable
154158
import com.nextcloud.talk.ui.chat.ChatView
@@ -510,6 +514,7 @@ class ChatActivity :
510514
if (useJetpackCompose) {
511515
setChatListContent()
512516
}
517+
setPinnedMessageContent()
513518

514519
lifecycleScope.launch {
515520
currentUserProvider.getCurrentUser()
@@ -557,13 +562,50 @@ class ChatActivity :
557562
onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
558563
}
559564

565+
private fun setPinnedMessageContent() {
566+
binding.pinnedMessageComposeView.setContent {
567+
val uiState by chatViewModel.uiState.collectAsStateWithLifecycle()
568+
val pinnedMessage = uiState.pinnedMessage
569+
binding.pinnedMessageContainer.visibility = if (pinnedMessage != null) View.VISIBLE else View.GONE
570+
if (pinnedMessage != null) {
571+
PinnedMessageView(
572+
message = pinnedMessage,
573+
viewThemeUtils = viewThemeUtils,
574+
currentConversation = uiState.conversation,
575+
scrollToMessageWithIdWithOffset = { messageId ->
576+
scrollToMessageById(messageId.toLong())
577+
},
578+
hidePinnedMessage = ::hidePinnedMessage,
579+
unPinMessage = ::unPinMessage
580+
)
581+
}
582+
}
583+
}
584+
585+
private var chatListState: LazyListState? = null
586+
587+
private fun scrollToMessageById(messageId: Long) {
588+
val items = chatViewModel.uiState.value.items
589+
val targetIndex = items.indexOfFirst { item ->
590+
(item as? ChatViewModel.ChatItem.MessageItem)?.uiMessage?.id == messageId.toInt()
591+
}
592+
if (targetIndex >= 0) {
593+
lifecycleScope.launch {
594+
chatListState?.scrollToItem(targetIndex)
595+
}
596+
}
597+
}
598+
560599
private fun setChatListContent() {
561600
binding.messagesListViewCompose.setContent {
562601
val uiState by chatViewModel.uiState.collectAsStateWithLifecycle()
563602
currentConversation = uiState.conversation
564603

565604
binding.messagesListViewCompose.visibility = View.VISIBLE
566605

606+
val listState = rememberLazyListState()
607+
SideEffect { chatListState = listState }
608+
567609
CompositionLocalProvider(
568610
LocalViewThemeUtils provides viewThemeUtils,
569611
LocalMessageUtils provides messageUtils,
@@ -588,7 +630,8 @@ class ChatActivity :
588630
onReactionClick = { messageId, emoji -> handleReactionClick(messageId, emoji) },
589631
onReactionLongClick = { messageId -> openReactionsDialog(messageId) },
590632
onOpenThreadClick = { messageId -> openThread(messageId.toLong()) },
591-
onLoadQuotedMessageClick = { messageId -> onLoadQuotedMessage(messageId) }
633+
onLoadQuotedMessageClick = { messageId -> onLoadQuotedMessage(messageId) },
634+
listState = listState
592635
)
593636
}
594637
}

app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
7575
import kotlinx.coroutines.flow.SharingStarted
7676
import kotlinx.coroutines.flow.StateFlow
7777
import kotlinx.coroutines.flow.asSharedFlow
78+
import kotlinx.coroutines.flow.catch
7879
import kotlinx.coroutines.flow.combine
7980
import kotlinx.coroutines.flow.distinctUntilChanged
8081
import kotlinx.coroutines.flow.distinctUntilChangedBy
@@ -344,7 +345,8 @@ class ChatViewModel @AssistedInject constructor(
344345

345346
// Adding the whole conversation is just an intermediate solution as it is used in the activity.
346347
// For the future, only necessary vars from conversation should be in the ui state
347-
val conversation: ConversationModel? = null
348+
val conversation: ConversationModel? = null,
349+
val pinnedMessage: ChatMessage? = null
348350
)
349351

350352
private val _uiState = MutableStateFlow(ChatUiState())
@@ -435,6 +437,7 @@ class ChatViewModel @AssistedInject constructor(
435437
observeConversation()
436438
observeMessages()
437439
observeMediaPlayerProgressForCompose()
440+
observePinnedMessage()
438441
}
439442

440443
private fun observeMediaPlayerProgressForCompose() {
@@ -529,6 +532,43 @@ class ChatViewModel @AssistedInject constructor(
529532
.launchIn(viewModelScope)
530533
}
531534

535+
private fun observePinnedMessage() {
536+
nonNullUserFlow
537+
.flatMapLatest { user ->
538+
conversationRepository.observeConversation(requireNotNull(user.id), chatRoomToken)
539+
.mapNotNull { result ->
540+
(result as? OfflineFirstConversationsRepository.ConversationResult.Found)?.conversation
541+
}
542+
.distinctUntilChangedBy { it.lastPinnedId to it.hiddenPinnedId }
543+
.flatMapLatest { conversation ->
544+
val pinnedId = conversation.lastPinnedId
545+
if (pinnedId != null && pinnedId != 0L && pinnedId != conversation.hiddenPinnedId) {
546+
val bundle = Bundle().apply {
547+
putString(
548+
BundleKeys.KEY_CHAT_URL,
549+
ApiUtils.getUrlForChat(1, user.baseUrl, chatRoomToken)
550+
)
551+
putString(
552+
BundleKeys.KEY_CREDENTIALS,
553+
ApiUtils.getCredentials(user.username, user.token)
554+
)
555+
putString(BundleKeys.KEY_ROOM_TOKEN, chatRoomToken)
556+
}
557+
chatRepository.getMessage(pinnedId, bundle)
558+
.map { it as ChatMessage? }
559+
.catch { emit(null) }
560+
} else {
561+
flowOf(null)
562+
}
563+
}
564+
}
565+
.onEach { pinnedMessage ->
566+
_uiState.update { it.copy(pinnedMessage = pinnedMessage) }
567+
}
568+
.catch { Log.e(TAG, "Error observing pinned message", it) }
569+
.launchIn(viewModelScope)
570+
}
571+
532572
// val lastCommonReadMessageId = getLastCommonReadFlow.first()
533573

534574
// ------------------------------

0 commit comments

Comments
 (0)