From fb52412c0e9d10c32873b6908e0222141a7625d3 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 90121b6b283100eae260545c33a67b018dd32c1e 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 5bf55e76baabf4100c468dc22f169de3401ab120 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 374f3dad3a32614634a4f41650c6d2abd9e887c9 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 8c402066a1c0d6c1bd609826e576c8df9cc2a701 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 7c7019ae1886d6f34bbc9df7bb034d06b0c22810 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 dc9390c43c97b483abc396b6412efa61416d9a29 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 8c33531d741f616ac6977effe3d96632194f0607 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 5a47ef3d3729da8f4020a2cd909b329ac505ec7a 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 2e5478e105d35f666fef644c6cb98b3f6f993024 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 8ab7068820399e49996fab6bdb4b58468e1d7564 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