diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ContextItemView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ContextItemView.kt index 00e5ab894f..3f0b647932 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ContextItemView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ContextItemView.kt @@ -52,7 +52,8 @@ fun ContextItemView( ) MarkdownText( contextItem.text, contextItem.formattedText, - sender = contextItem.memberDisplayName, senderBold = true, maxLines = 3 + sender = contextItem.memberDisplayName, senderBold = true, maxLines = 3, + modifier = Modifier.fillMaxWidth(), ) } IconButton(onClick = cancelContextItem) { diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt index e456f05766..c7cb491322 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt @@ -18,17 +18,21 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp +import androidx.core.text.BidiFormatter import chat.simplex.app.R import chat.simplex.app.model.ChatItem import chat.simplex.app.ui.theme.HighOrLowlight import chat.simplex.app.ui.theme.SimpleXTheme import kotlinx.coroutines.delay +import kotlin.math.min @OptIn(ExperimentalComposeUiApi::class) @Composable @@ -48,7 +52,7 @@ fun SendMsgView( delay(50) keyboard?.show() } - + val isRtl = remember(cs.message) { BidiFormatter.getInstance().isRtl(cs.message.subSequence(0, min(50, cs.message.length))) } BasicTextField( value = cs.message, onValueChange = onMessageChange, @@ -69,14 +73,18 @@ fun SendMsgView( Modifier.background(MaterialTheme.colors.background), verticalAlignment = Alignment.Bottom ) { - Box( - Modifier - .weight(1f) - .padding(horizontal = 12.dp) - .padding(top = 5.dp) - .padding(bottom = 7.dp) + CompositionLocalProvider( + LocalLayoutDirection provides if (isRtl) LayoutDirection.Rtl else LocalLayoutDirection.current ) { - innerTextField() + Box( + Modifier + .weight(1f) + .padding(horizontal = 12.dp) + .padding(top = 5.dp) + .padding(bottom = 7.dp) + ) { + innerTextField() + } } val icon = if (cs.editing) Icons.Filled.Check else Icons.Outlined.ArrowUpward val color = if (cs.sendEnabled()) MaterialTheme.colors.primary else HighOrLowlight diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/TextItemView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/TextItemView.kt index 7216716031..08cc9cae36 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/TextItemView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/TextItemView.kt @@ -7,11 +7,12 @@ import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.platform.UriHandler +import androidx.compose.ui.platform.* import androidx.compose.ui.text.* import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.* +import androidx.core.text.BidiFormatter import chat.simplex.app.model.* import chat.simplex.app.views.helpers.detectGesture @@ -50,51 +51,69 @@ fun MarkdownText ( modifier: Modifier = Modifier, onLinkLongClick: (link: String) -> Unit = {} ) { - val reserve = if (edited) " " else " " - if (formattedText == null) { - val annotatedText = buildAnnotatedString { - appendSender(this, sender, senderBold) - append(text) - if (metaText != null) withStyle(reserveTimestampStyle) { append(reserve + metaText) } - } - Text(annotatedText, style = style, modifier = modifier, maxLines = maxLines, overflow = overflow) - } else { - var hasLinks = false - val annotatedText = buildAnnotatedString { - appendSender(this, sender, senderBold) - for (ft in formattedText) { - if (ft.format == null) append(ft.text) - else { - val link = ft.link - if (link != null) { - hasLinks = true - val ftStyle = ft.format.style - withAnnotation(tag = "URL", annotation = link) { - withStyle(ftStyle) { append(ft.text) } + val textLayoutDirection = remember (text) { + if (BidiFormatter.getInstance().isRtl(text.subSequence(0, kotlin.math.min(50, text.length)))) LayoutDirection.Rtl else LayoutDirection.Ltr + } + val reserve = when { + textLayoutDirection != LocalLayoutDirection.current && metaText != null -> "\n" + edited -> " " + else -> " " + } + CompositionLocalProvider( + // When we draw `sender` is has issues on LTR languages set globally with RTL text language + LocalLayoutDirection provides if (textLayoutDirection != LocalLayoutDirection.current && sender == null) + if (LocalLayoutDirection.current == LayoutDirection.Ltr) LayoutDirection.Rtl else LayoutDirection.Ltr + else + LocalLayoutDirection.current + ) { + if (formattedText == null) { + val annotatedText = buildAnnotatedString { + appendSender(this, sender, senderBold) + append(text) + if (metaText != null) withStyle(reserveTimestampStyle) { append(reserve + metaText) } + } + Text(annotatedText, style = style, modifier = modifier, maxLines = maxLines, overflow = overflow) + } else { + var hasLinks = false + val annotatedText = buildAnnotatedString { + appendSender(this, sender, senderBold) + for (ft in formattedText) { + if (ft.format == null) append(ft.text) + else { + val link = ft.link + if (link != null) { + hasLinks = true + val ftStyle = ft.format.style + withAnnotation(tag = "URL", annotation = link) { + withStyle(ftStyle) { append(ft.text) } + } + } else { + withStyle(ft.format.style) { append(ft.text) } } - } else { - withStyle(ft.format.style) { append(ft.text) } } } + // With RTL language set globally links looks bad sometimes, better to add a new line to bo sure everything looks good + /*if (metaText != null && hasLinks && LocalLayoutDirection.current == LayoutDirection.Rtl) + withStyle(reserveTimestampStyle) { append("\n" + metaText) } + else */if (metaText != null) withStyle(reserveTimestampStyle) { append(reserve + metaText) } + } + if (hasLinks && uriHandler != null) { + ClickableText(annotatedText, style = style, modifier = modifier, maxLines = maxLines, overflow = overflow, + onLongClick = { offset -> + annotatedText.getStringAnnotations(tag = "URL", start = offset, end = offset) + .firstOrNull()?.let { annotation -> onLinkLongClick(annotation.item) } + }, + onClick = { offset -> + annotatedText.getStringAnnotations(tag = "URL", start = offset, end = offset) + .firstOrNull()?.let { annotation -> uriHandler.openUri(annotation.item) } + }, + shouldConsumeEvent = { offset -> + annotatedText.getStringAnnotations(tag = "URL", start = offset, end = offset).any() + } + ) + } else { + Text(annotatedText, style = style, modifier = modifier, maxLines = maxLines, overflow = overflow) } - if (metaText != null) withStyle(reserveTimestampStyle) { append(reserve + metaText) } - } - if (hasLinks && uriHandler != null) { - ClickableText(annotatedText, style = style, modifier = modifier, maxLines = maxLines, overflow = overflow, - onLongClick = { offset -> - annotatedText.getStringAnnotations(tag = "URL", start = offset, end = offset) - .firstOrNull()?.let { annotation -> onLinkLongClick(annotation.item) } - }, - onClick = { offset -> - annotatedText.getStringAnnotations(tag = "URL", start = offset, end = offset) - .firstOrNull()?.let { annotation -> uriHandler.openUri(annotation.item) } - }, - shouldConsumeEvent = { offset -> - annotatedText.getStringAnnotations(tag = "URL", start = offset, end = offset).any() - } - ) - } else { - Text(annotatedText, style = style, modifier = modifier, maxLines = maxLines, overflow = overflow) } } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatPreviewView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatPreviewView.kt index accbffb2e1..ccde6d61ab 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatPreviewView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatPreviewView.kt @@ -17,8 +17,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp +import androidx.compose.ui.unit.* import chat.simplex.app.R import chat.simplex.app.model.* import chat.simplex.app.ui.theme.* @@ -83,11 +82,14 @@ fun ChatPreviewView(chat: Chat, chatModelIncognito: Boolean, currentUserProfileD val ci = chat.chatItems.lastOrNull() if (ci != null) { MarkdownText( - ci.text, ci.formattedText, ci.memberDisplayName, - metaText = ci.timestampText, + ci.text, + ci.formattedText, + sender = null, + metaText = null, maxLines = 2, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.body1.copy(color = if (isInDarkTheme()) MessagePreviewDark else MessagePreviewLight, lineHeight = 22.sp), + modifier = Modifier.fillMaxWidth(), ) } else { when (cInfo) {