From 85632e8e7644b5440467e5d2217fcd4df7955ae4 Mon Sep 17 00:00:00 2001 From: Infi Date: Sun, 19 Oct 2025 02:58:29 +0200 Subject: [PATCH] feat: point out unsupported messages Signed-off-by: Infi --- .../stoat/composables/chat/SystemMessage.kt | 3 +- .../composables/chat/UnsupportedMessage.kt | 74 +++++++++++++++++++ .../chat/views/channel/ChannelScreen.kt | 9 +++ app/src/main/res/values/strings.xml | 3 + 4 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/chat/stoat/composables/chat/UnsupportedMessage.kt diff --git a/app/src/main/java/chat/stoat/composables/chat/SystemMessage.kt b/app/src/main/java/chat/stoat/composables/chat/SystemMessage.kt index 1a866fd3..601f5e61 100644 --- a/app/src/main/java/chat/stoat/composables/chat/SystemMessage.kt +++ b/app/src/main/java/chat/stoat/composables/chat/SystemMessage.kt @@ -14,7 +14,6 @@ import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialShapes import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.material3.toShape import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider @@ -58,7 +57,7 @@ fun SystemMessage(message: Message) { SystemMessageType.entries.firstOrNull { it.type == message.system.type } if (systemMessageType == null) { - Text(text = message.system.toString()) + UnsupportedMessage(context = message.system.type) return } diff --git a/app/src/main/java/chat/stoat/composables/chat/UnsupportedMessage.kt b/app/src/main/java/chat/stoat/composables/chat/UnsupportedMessage.kt new file mode 100644 index 00000000..6702f5c7 --- /dev/null +++ b/app/src/main/java/chat/stoat/composables/chat/UnsupportedMessage.kt @@ -0,0 +1,74 @@ +package chat.stoat.composables.chat + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.Icon +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.MaterialShapes +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.toShape +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import chat.stoat.R + +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun UnsupportedMessage(modifier: Modifier = Modifier, context: String? = null) { + CompositionLocalProvider( + LocalContentColor provides MaterialTheme.colorScheme.error.copy(alpha = 0.7f), + LocalTextStyle provides LocalTextStyle.current.copy( + fontWeight = FontWeight.Medium + ) + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .padding(horizontal = 10.dp, vertical = 16.dp) + .fillMaxWidth() + ) { + Box( + contentAlignment = Alignment.Center, + modifier = modifier + .clip(MaterialShapes.PixelCircle.toShape()) + .background( + color = MaterialTheme.colorScheme.errorContainer, + ) + .size(40.dp) + ) { + CompositionLocalProvider( + LocalContentColor provides MaterialTheme.colorScheme.onErrorContainer, + ) { + Icon( + painter = painterResource(R.drawable.icn_error_24dp), + contentDescription = null, + tint = LocalContentColor.current, + modifier = Modifier.size(24.dp) + ) + } + } + Spacer(modifier = Modifier.width(10.dp)) + Text( + if (context != null) stringResource( + R.string.message_not_supported_with_context, + context + ) else stringResource(R.string.message_not_supported) + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/chat/stoat/screens/chat/views/channel/ChannelScreen.kt b/app/src/main/java/chat/stoat/screens/chat/views/channel/ChannelScreen.kt index 0b4ed969..39b51641 100644 --- a/app/src/main/java/chat/stoat/screens/chat/views/channel/ChannelScreen.kt +++ b/app/src/main/java/chat/stoat/screens/chat/views/channel/ChannelScreen.kt @@ -125,6 +125,7 @@ import chat.stoat.composables.chat.DateDivider import chat.stoat.composables.chat.Message import chat.stoat.composables.chat.MessageField import chat.stoat.composables.chat.SystemMessage +import chat.stoat.composables.chat.UnsupportedMessage import chat.stoat.composables.emoji.EmojiPicker import chat.stoat.composables.generic.GroupIcon import chat.stoat.composables.generic.PresenceBadge @@ -726,6 +727,14 @@ fun ChannelScreen( } when (val item = viewModel.items[index]) { is ChannelScreenItem.RegularMessage -> { + if (item.message.content?.replace("\\s".toRegex(), "") + ?.contains(">>>>>>>") == true + ) { + // FIXME Dirty hack to prevent a crash caused by malicious messages. + UnsupportedMessage() + return@items + } + RegularMessage( item.message, viewModel.channel, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8a72e86e..172675f5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -255,6 +255,9 @@ Blocked message Failed to send, long press for options + Message cannot be displayed. + Message cannot be displayed (%1$s). + Tap to play video from YouTube Ownership changed