From 065b2ac349755440f2ee6f2d6cc6eb85cd67589d Mon Sep 17 00:00:00 2001 From: rapterjet2004 Date: Wed, 4 Feb 2026 10:45:14 -0600 Subject: [PATCH 01/11] Simplifying pinned messages Signed-off-by: rapterjet2004 --- .../java/com/nextcloud/talk/ui/PinnedMessage.kt | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/ui/PinnedMessage.kt b/app/src/main/java/com/nextcloud/talk/ui/PinnedMessage.kt index a35f4f91598..3a15c8ab298 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/PinnedMessage.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/PinnedMessage.kt @@ -13,6 +13,8 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding @@ -71,7 +73,7 @@ fun PinnedMessageView( val pinnedBy = stringResource(R.string.pinned_by) message.actorDisplayName = remember(message.pinnedActorDisplayName) { - "${message.actorDisplayName}\n$pinnedBy ${message.pinnedActorDisplayName}" + "$pinnedBy ${message.pinnedActorDisplayName}" } val scrollState = rememberScrollState() @@ -97,10 +99,6 @@ fun PinnedMessageView( ConversationUtils.isParticipantOwnerOrModerator(currentConversation!!) } - val adapter = remember { - ComposeChatAdapter() - } - Column( verticalArrangement = Arrangement.spacedBy((-SPACE_16).dp), modifier = Modifier @@ -117,7 +115,14 @@ fun PinnedMessageView( } ) { - adapter.GetComposableForMessage(message) + Column { + message.actorDisplayName?.let { + Text(it) + } + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Start) { + Text(message.text) + } + } } var expanded by remember { mutableStateOf(false) } From 7bc7e80269daa7b48f2c0b382a875bab5270a1c5 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Thu, 5 Feb 2026 10:21:28 +0100 Subject: [PATCH 02/11] use 3dot button inside box / add shadow / avoid ripple Signed-off-by: Marcel Hibbe --- .../com/nextcloud/talk/ui/PinnedMessage.kt | 108 +++++++----------- 1 file changed, 43 insertions(+), 65 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/ui/PinnedMessage.kt b/app/src/main/java/com/nextcloud/talk/ui/PinnedMessage.kt index 3a15c8ab298..12665249f84 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/PinnedMessage.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/PinnedMessage.kt @@ -10,23 +10,20 @@ package com.nextcloud.talk.ui import android.text.format.DateFormat import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn -import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Menu -import androidx.compose.material3.Divider +import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Text @@ -35,12 +32,12 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.colorResource -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.nextcloud.talk.R @@ -72,8 +69,8 @@ fun PinnedMessageView( val pinnedBy = stringResource(R.string.pinned_by) - message.actorDisplayName = remember(message.pinnedActorDisplayName) { - "$pinnedBy ${message.pinnedActorDisplayName}" + val pinnedHeadline = remember(message.pinnedActorDisplayName) { + "${message.actorDisplayName} ($pinnedBy ${message.pinnedActorDisplayName})" } val scrollState = rememberScrollState() @@ -99,34 +96,31 @@ fun PinnedMessageView( ConversationUtils.isParticipantOwnerOrModerator(currentConversation!!) } - Column( - verticalArrangement = Arrangement.spacedBy((-SPACE_16).dp), - modifier = Modifier - ) { - Box( - modifier = Modifier - .shadow(ELEVATION.dp, shape = RoundedCornerShape(CORNER_RADIUS.dp)) - .background(incomingBubbleColor, RoundedCornerShape(CORNER_RADIUS.dp)) - .padding(SPACE_16.dp) - .heightIn(max = MAX_HEIGHT.dp) - .verticalScroll(scrollState) - .clickable { - scrollToMessageWithIdWithOffset(message.id) - } + val interactionSource = remember { MutableInteractionSource() } - ) { - Column { - message.actorDisplayName?.let { - Text(it) - } - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Start) { - Text(message.text) - } + Box( + modifier = Modifier + .fillMaxWidth() + .padding(4.dp) + .shadow( + elevation = 2.dp, + shape = RoundedCornerShape(CORNER_RADIUS.dp), + clip = false + ) + .background( + incomingBubbleColor, + RoundedCornerShape(CORNER_RADIUS.dp) + ) + .padding(SPACE_16.dp) + .heightIn(max = MAX_HEIGHT.dp) + .clickable( + interactionSource = interactionSource, + indication = null + ) { + scrollToMessageWithIdWithOffset(message.id) } - } - + ) { var expanded by remember { mutableStateOf(false) } - val pinnedUntilStr = stringResource(R.string.pinned_until) val untilUnpin = stringResource(R.string.until_unpin) val pinnedText = remember(message.pinnedUntil) { @@ -147,15 +141,23 @@ fun PinnedMessageView( } ?: untilUnpin } + Column( + modifier = Modifier + .verticalScroll(scrollState) + .padding(end = 40.dp) + ) { + Text(pinnedHeadline) + Text(message.text) + } + Box( modifier = Modifier - .offset(SPACE_16.dp, 0.dp) - .background(outgoingBubbleColor, RoundedCornerShape(CORNER_RADIUS.dp)) + .align(Alignment.TopEnd) ) { IconButton(onClick = { expanded = true }) { Icon( - imageVector = Icons.Default.Menu, // Or use a Pin icon here - contentDescription = "Pinned Message Options", + imageVector = Icons.Default.MoreVert, + contentDescription = "Message options", tint = highEmphasisColor ) } @@ -172,22 +174,14 @@ fun PinnedMessageView( color = highEmphasisColor ) }, - onClick = { /* No-op or toggle expansion */ }, - enabled = false // Visually distinct as information, not action + onClick = {}, + enabled = false ) - Divider() + HorizontalDivider() DropdownMenuItem( text = { Text("Go to message", color = highEmphasisColor) }, - leadingIcon = { - Icon( - painter = painterResource(R.drawable.baseline_chat_bubble_outline_24), - contentDescription = null, - modifier = Modifier.size(ICON_SIZE.dp), - tint = highEmphasisColor - ) - }, onClick = { expanded = false scrollToMessageWithIdWithOffset(message.id) @@ -196,14 +190,6 @@ fun PinnedMessageView( DropdownMenuItem( text = { Text("Dismiss", color = highEmphasisColor) }, - leadingIcon = { - Icon( - painter = painterResource(R.drawable.ic_eye_off), - contentDescription = null, - modifier = Modifier.size(ICON_SIZE.dp), - tint = highEmphasisColor - ) - }, onClick = { expanded = false hidePinnedMessage(message) @@ -213,14 +199,6 @@ fun PinnedMessageView( if (canPin) { DropdownMenuItem( text = { Text("Unpin", color = highEmphasisColor) }, - leadingIcon = { - Icon( - painter = painterResource(R.drawable.keep_off_24px), - contentDescription = null, - modifier = Modifier.size(ICON_SIZE.dp), - tint = highEmphasisColor - ) - }, onClick = { expanded = false unPinMessage(message) From 91d65916a2de156bf03b31345fe867d82f86890d Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Thu, 5 Feb 2026 10:30:07 +0100 Subject: [PATCH 03/11] add styling to pinned message headline Signed-off-by: Marcel Hibbe --- .../java/com/nextcloud/talk/ui/PinnedMessage.kt | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/ui/PinnedMessage.kt b/app/src/main/java/com/nextcloud/talk/ui/PinnedMessage.kt index 12665249f84..0376a798d76 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/PinnedMessage.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/PinnedMessage.kt @@ -13,7 +13,9 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState @@ -26,6 +28,7 @@ import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -51,8 +54,7 @@ import java.time.format.DateTimeFormatter const val SPACE_16 = 16 const val CORNER_RADIUS = 16 -const val ICON_SIZE = 24 -const val ELEVATION = 4 +val ELEVATION = 2.dp const val MAX_HEIGHT = 100 @Suppress("LongMethod", "LongParameterList") @@ -103,7 +105,7 @@ fun PinnedMessageView( .fillMaxWidth() .padding(4.dp) .shadow( - elevation = 2.dp, + elevation = ELEVATION, shape = RoundedCornerShape(CORNER_RADIUS.dp), clip = false ) @@ -146,7 +148,12 @@ fun PinnedMessageView( .verticalScroll(scrollState) .padding(end = 40.dp) ) { - Text(pinnedHeadline) + Text( + text = pinnedHeadline, + color = MaterialTheme.colorScheme.onSurfaceVariant, + style = MaterialTheme.typography.labelMedium + ) + Spacer(modifier = Modifier.height(4.dp)) Text(message.text) } From 92926854f9686a9d513aa93b7c6fe9b2fb444d18 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Thu, 5 Feb 2026 10:39:54 +0100 Subject: [PATCH 04/11] fix hardcoded strings Signed-off-by: Marcel Hibbe --- app/src/main/java/com/nextcloud/talk/ui/PinnedMessage.kt | 8 ++++---- app/src/main/res/values/strings.xml | 5 ++++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/ui/PinnedMessage.kt b/app/src/main/java/com/nextcloud/talk/ui/PinnedMessage.kt index 0376a798d76..8c5d559bc7e 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/PinnedMessage.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/PinnedMessage.kt @@ -164,7 +164,7 @@ fun PinnedMessageView( IconButton(onClick = { expanded = true }) { Icon( imageVector = Icons.Default.MoreVert, - contentDescription = "Message options", + contentDescription = stringResource(R.string.pinned_message_options), tint = highEmphasisColor ) } @@ -188,7 +188,7 @@ fun PinnedMessageView( HorizontalDivider() DropdownMenuItem( - text = { Text("Go to message", color = highEmphasisColor) }, + text = { Text(stringResource(R.string.pinned_go_to_message), color = highEmphasisColor) }, onClick = { expanded = false scrollToMessageWithIdWithOffset(message.id) @@ -196,7 +196,7 @@ fun PinnedMessageView( ) DropdownMenuItem( - text = { Text("Dismiss", color = highEmphasisColor) }, + text = { Text(stringResource(R.string.pinned_dismiss), color = highEmphasisColor) }, onClick = { expanded = false hidePinnedMessage(message) @@ -205,7 +205,7 @@ fun PinnedMessageView( if (canPin) { DropdownMenuItem( - text = { Text("Unpin", color = highEmphasisColor) }, + text = { Text(stringResource(R.string.unpin_message), color = highEmphasisColor) }, onClick = { expanded = false unPinMessage(message) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 52c6b7eca92..adb6443273a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -914,9 +914,12 @@ How to translate with transifex: Pin indefinitely Pinned indefinitely Pinned until - Pinned by + pinned by Pinned Until unpin + Go to message + Dismiss + Message options Send later Schedule message From bd084cb81850ddaceaf4bee51e8350e08177f128 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Thu, 5 Feb 2026 10:57:45 +0100 Subject: [PATCH 05/11] use Dispatchers.IO for getIndividualMessageFromServer Signed-off-by: Marcel Hibbe --- .../java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt index 32fa780281a..41c0dd1b570 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt @@ -55,6 +55,7 @@ import io.reactivex.Observer import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -62,6 +63,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import java.io.File @@ -887,7 +889,7 @@ class ChatViewModel @Inject constructor( } else { emit(null) } - } + }.flowOn(Dispatchers.IO) suspend fun getNumberOfThreadReplies(threadId: Long): Int = chatRepository.getNumberOfThreadReplies(threadId) From 7b98a642375744ba9c051ff2c162617c3aab3f2a Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Thu, 5 Feb 2026 11:49:31 +0100 Subject: [PATCH 06/11] hopefully fix ANR issues by using flatMapLatest to cancel previous pinned message requests getConversationFlow emits too often, causing multiple overlapping calls to getIndividualMessageFromServer (i guess this caused the ANR issues). Using flatMapLatest ensures that only the latest conversation triggers a network call and cancels previous requests. This should hopefully avoid the ANR issues. TODO: improve architecture -> reduce unnecessary getConversationFlow triggers -> better MVVM (too many things are inside Activity) I wont do further changes now to avoid too many changes for v23RC2 Signed-off-by: Marcel Hibbe --- .../com/nextcloud/talk/chat/ChatActivity.kt | 65 ++++++++++--------- 1 file changed, 34 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index 62fc0823f42..305a4f8b226 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -232,7 +232,11 @@ import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking @@ -652,55 +656,54 @@ class ChatActivity : this.lifecycle.removeObserver(chatViewModel) } + @OptIn(ExperimentalCoroutinesApi::class) @SuppressLint("NotifyDataSetChanged", "SetTextI18n", "ResourceAsColor") @Suppress("LongMethod") private fun initObservers() { Log.d(TAG, "initObservers Called") - - this.lifecycleScope.launch { + lifecycleScope.launch { chatViewModel.getConversationFlow - .collect { conversationModel -> + .onEach { conversationModel -> currentConversation = conversationModel - chatViewModel.updateConversation( - currentConversation!! - ) - + chatViewModel.updateConversation(conversationModel) logConversationInfos("GetRoomSuccessState") if (adapter == null) { initAdapter() binding.messagesListView.setAdapter(adapter) - layoutManager = binding.messagesListView.layoutManager as LinearLayoutManager? + layoutManager = binding.messagesListView.layoutManager as? LinearLayoutManager } - chatViewModel.getCapabilities(conversationUser!!, roomToken, currentConversation!!) - + chatViewModel.getCapabilities(conversationUser!!, roomToken, conversationModel) + } + .flatMapLatest { conversationModel -> if (conversationModel.lastPinnedId != null && conversationModel.lastPinnedId != 0L && conversationModel.lastPinnedId != conversationModel.hiddenPinnedId ) { - chatViewModel - .getIndividualMessageFromServer( - credentials!!, - conversationUser?.baseUrl!!, - roomToken, - conversationModel.lastPinnedId.toString() + chatViewModel.getIndividualMessageFromServer( + credentials!!, + conversationUser?.baseUrl!!, + roomToken, + conversationModel.lastPinnedId.toString() + ) + } else { + flowOf(null) + } + } + .collectLatest { message -> + if (message != null) { + binding.pinnedMessageContainer.visibility = View.VISIBLE + binding.pinnedMessageComposeView.setContent { + PinnedMessageView( + message, + viewThemeUtils, + currentConversation, + scrollToMessageWithIdWithOffset = ::scrollToMessageWithIdWithOffset, + hidePinnedMessage = ::hidePinnedMessage, + unPinMessage = ::unPinMessage ) - .collect { message -> - message?.let { - binding.pinnedMessageContainer.visibility = View.VISIBLE - binding.pinnedMessageComposeView.setContent { - PinnedMessageView( - message, - viewThemeUtils, - currentConversation, - scrollToMessageWithIdWithOffset = ::scrollToMessageWithIdWithOffset, - hidePinnedMessage = ::hidePinnedMessage, - unPinMessage = ::unPinMessage - ) - } - } - } + } } else { binding.pinnedMessageContainer.visibility = View.GONE } From 28c946db1d1048271f013faf5df114d7e7ae0788 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Thu, 5 Feb 2026 12:35:30 +0100 Subject: [PATCH 07/11] differentiate "pinned by" messages Signed-off-by: Marcel Hibbe --- .../com/nextcloud/talk/ui/PinnedMessage.kt | 20 +++++++++++++++---- app/src/main/res/values/strings.xml | 3 ++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/ui/PinnedMessage.kt b/app/src/main/java/com/nextcloud/talk/ui/PinnedMessage.kt index 8c5d559bc7e..97beec7fa65 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/PinnedMessage.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/PinnedMessage.kt @@ -69,11 +69,23 @@ fun PinnedMessageView( ) { message.incoming = true - val pinnedBy = stringResource(R.string.pinned_by) - - val pinnedHeadline = remember(message.pinnedActorDisplayName) { - "${message.actorDisplayName} ($pinnedBy ${message.pinnedActorDisplayName})" + val pinnedHeadline = if (message.pinnedActorId != message.actorId) { + if (message.pinnedActorId == currentConversation?.actorId) { + stringResource( + R.string.pinned_by_you, + message.actorDisplayName.orEmpty() + ) + } else { + stringResource( + R.string.pinned_by_author, + message.actorDisplayName.orEmpty(), + message.pinnedActorDisplayName.orEmpty() + ) + } + } else { + "${message.actorDisplayName}" } + val scrollState = rememberScrollState() val context = LocalContext.current diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index adb6443273a..bca74f7d95e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -914,7 +914,8 @@ How to translate with transifex: Pin indefinitely Pinned indefinitely Pinned until - pinned by + %1$s (pinned by you) + %1$s (pinned by %2$s) Pinned Until unpin Go to message From 7ce40ced6c68ff334b4bddc33b9d82db75215cae Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Thu, 5 Feb 2026 14:55:36 +0100 Subject: [PATCH 08/11] test: Add previews Signed-off-by: Andy Scherzinger --- .../com/nextcloud/talk/ui/PinnedMessage.kt | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/app/src/main/java/com/nextcloud/talk/ui/PinnedMessage.kt b/app/src/main/java/com/nextcloud/talk/ui/PinnedMessage.kt index 97beec7fa65..1225ad48e35 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/PinnedMessage.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/PinnedMessage.kt @@ -42,12 +42,18 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.nextcloud.talk.R import com.nextcloud.talk.chat.data.model.ChatMessage +import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.models.domain.ConversationModel +import com.nextcloud.talk.models.json.conversations.Conversation +import com.nextcloud.talk.models.json.conversations.ConversationEnums +import com.nextcloud.talk.models.json.participants.Participant import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.utils.ConversationUtils +import com.nextcloud.talk.utils.preview.ComposePreviewUtils import java.time.Instant import java.time.ZoneId import java.time.format.DateTimeFormatter @@ -228,3 +234,61 @@ fun PinnedMessageView( } } } + +@Preview(name = "Dark Mode", uiMode = android.content.res.Configuration.UI_MODE_NIGHT_YES) +@Composable +fun PinnedMessagePreviewDark() { + PinnedMessagePreview() +} + +@Preview(name = "Long Content") +@Composable +fun PinnedMessageLongContentPreview() { + PinnedMessagePreview( + messageContent = "This is a **very long** _pinned_ ??\ncontent that should demonstrate how the " + + "scrollable box behaves when there is more text than what can fit in the maximum height of the pinned " + + "message view. It should show a scrollbar or at least allow vertical scrolling to see the rest of " + + "the message. Adding even more text here to ensure it exceeds 100dp." + ) +} + +@Preview(name = "Light Mode") +@Composable +fun PinnedMessagePreview( + messageContent: String = "This is a **pinned** message _content_" +) { + val context = LocalContext.current + val previewUtils = ComposePreviewUtils.getInstance(context) + val viewThemeUtils = previewUtils.viewThemeUtils + val colorScheme = viewThemeUtils.getColorScheme(context) + + val user = User(id = 1L, userId = "user_id") + val conversation = Conversation( + token = "token", + participantType = Participant.ParticipantType.OWNER, + type = ConversationEnums.ConversationType.ROOM_GROUP_CALL + ) + val currentConversation = ConversationModel.mapToConversationModel(conversation, user) + + val message = ChatMessage().apply { + jsonMessageId = 1 + actorDisplayName = "Author One" + pinnedActorDisplayName = "User Two" + message = messageContent + timestamp = System.currentTimeMillis() / 1000 + pinnedAt = System.currentTimeMillis() / 1000 + } + + MaterialTheme(colorScheme = colorScheme) { + Box(modifier = Modifier.padding(16.dp)) { + PinnedMessageView( + message = message, + viewThemeUtils = viewThemeUtils, + currentConversation = currentConversation, + scrollToMessageWithIdWithOffset = {}, + hidePinnedMessage = {}, + unPinMessage = {} + ) + } + } +} From 6dac318939ceba927b92c6a1440f16c3e27ef737 Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Thu, 5 Feb 2026 14:58:35 +0100 Subject: [PATCH 09/11] style(dark): Fix message color Signed-off-by: Andy Scherzinger --- app/src/main/java/com/nextcloud/talk/ui/PinnedMessage.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/nextcloud/talk/ui/PinnedMessage.kt b/app/src/main/java/com/nextcloud/talk/ui/PinnedMessage.kt index 1225ad48e35..76df23c8b46 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/PinnedMessage.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/PinnedMessage.kt @@ -172,7 +172,10 @@ fun PinnedMessageView( style = MaterialTheme.typography.labelMedium ) Spacer(modifier = Modifier.height(4.dp)) - Text(message.text) + Text( + text = message.text, + color = MaterialTheme.colorScheme.onSurface + ) } Box( From e6f394b15313a620694439aa24323c31b3260174 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Thu, 5 Feb 2026 16:16:40 +0100 Subject: [PATCH 10/11] resolve ktlint warning Signed-off-by: Marcel Hibbe --- app/src/main/java/com/nextcloud/talk/ui/PinnedMessage.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/ui/PinnedMessage.kt b/app/src/main/java/com/nextcloud/talk/ui/PinnedMessage.kt index 76df23c8b46..ea24e079d50 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/PinnedMessage.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/PinnedMessage.kt @@ -255,11 +255,10 @@ fun PinnedMessageLongContentPreview() { ) } +@Suppress("MagicNumber") @Preview(name = "Light Mode") @Composable -fun PinnedMessagePreview( - messageContent: String = "This is a **pinned** message _content_" -) { +fun PinnedMessagePreview(messageContent: String = "This is a **pinned** message _content_") { val context = LocalContext.current val previewUtils = ComposePreviewUtils.getInstance(context) val viewThemeUtils = previewUtils.viewThemeUtils From 91fd0ef4c01f18c82088a307e7418829d61871e8 Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Thu, 5 Feb 2026 17:02:58 +0100 Subject: [PATCH 11/11] style: Optimize top/end spacing of pinned message and update colors Signed-off-by: Andy Scherzinger --- .../com/nextcloud/talk/ui/PinnedMessage.kt | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/ui/PinnedMessage.kt b/app/src/main/java/com/nextcloud/talk/ui/PinnedMessage.kt index ea24e079d50..a39930a9d71 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/PinnedMessage.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/PinnedMessage.kt @@ -59,6 +59,7 @@ import java.time.ZoneId import java.time.format.DateTimeFormatter const val SPACE_16 = 16 +const val SPACE_0 = 0 const val CORNER_RADIUS = 16 val ELEVATION = 2.dp const val MAX_HEIGHT = 100 @@ -131,7 +132,7 @@ fun PinnedMessageView( incomingBubbleColor, RoundedCornerShape(CORNER_RADIUS.dp) ) - .padding(SPACE_16.dp) + .padding(SPACE_16.dp, SPACE_0.dp, SPACE_0.dp, SPACE_16.dp) .heightIn(max = MAX_HEIGHT.dp) .clickable( interactionSource = interactionSource, @@ -164,23 +165,24 @@ fun PinnedMessageView( Column( modifier = Modifier .verticalScroll(scrollState) - .padding(end = 40.dp) + .padding(top = SPACE_16.dp, end = 40.dp) ) { Text( text = pinnedHeadline, - color = MaterialTheme.colorScheme.onSurfaceVariant, + color = colorScheme.onSurfaceVariant, style = MaterialTheme.typography.labelMedium ) Spacer(modifier = Modifier.height(4.dp)) Text( text = message.text, - color = MaterialTheme.colorScheme.onSurface + color = colorScheme.onSurface ) } Box( modifier = Modifier .align(Alignment.TopEnd) + .padding(top = 2.dp) ) { IconButton(onClick = { expanded = true }) { Icon( @@ -238,12 +240,6 @@ fun PinnedMessageView( } } -@Preview(name = "Dark Mode", uiMode = android.content.res.Configuration.UI_MODE_NIGHT_YES) -@Composable -fun PinnedMessagePreviewDark() { - PinnedMessagePreview() -} - @Preview(name = "Long Content") @Composable fun PinnedMessageLongContentPreview() { @@ -255,6 +251,16 @@ fun PinnedMessageLongContentPreview() { ) } +@Preview( + name = "Dark Mode / R-t-L", + uiMode = android.content.res.Configuration.UI_MODE_NIGHT_YES, + locale = "ar" +) +@Composable +fun PinnedMessagePreviewDarkRtl() { + PinnedMessagePreview() +} + @Suppress("MagicNumber") @Preview(name = "Light Mode") @Composable