ui: show unicode characters in address as unicode markers (#7698)

* ui: show unicode characters in address as unicode markers

* defence in depth for links in messages (they are non-clickable from parser)

---------

Current code does not have this problem, as markdown parser rejects such links, and they are not clickable - it is needed for new markdown file viewer - this way unicode links in markdown show with unicode sequences instead of look-alike letters.
This commit is contained in:
Evgeny authored and GitHub committed 2026-10-13 08:42:59 +01:00
1 parent 4b6ac88c6e
commit d673a6b021
5 files changed
+49 -10

No files matched your search

@@ -96,6 +96,7 @@ fun itemSegmentDisplayText(ft: FormattedText, ci: ChatItem, linkMode: SimplexLin
} else if (mention != null) mentionText(ft.format.memberName)
else ft.text
}
is Format.Uri -> displayedUri(ft.text)
is Format.HyperLink -> ft.format.showText ?: ft.text
is Format.SimplexLink -> {
val t = ft.format.showText
@@ -283,7 +284,7 @@ fun MarkdownText (
val s = ft.text
val link = if (s.startsWith("http://") || s.startsWith("https://")) s else "https://$s"
withAnnotation(tag = "WEB_URL", annotation = link) {
withStyle(ftStyle) { append(ft.text) }
withStyle(ftStyle) { append(displayedUri(ft.text)) }
}
}
is Format.HyperLink -> {
@@ -535,19 +536,22 @@ fun openBrowserAlert(uri: String, uriHandler: UriHandler) {
if (res == null) {
showInvalidLinkAlert(uri, err)
} else {
val message = if (uri.count() > 160) uri.substring(0, 159) + "…" else uri
val shownUri = displayedUri(uri)
val message = if (shownUri.count() > 160) shownUri.substring(0, 159) + "…" else shownUri
val sanitizedUri = res.second
if (sanitizedUri == null) {
AlertManager.shared.showAlertDialog(
generalGetString(MR.strings.privacy_chat_list_open_web_link_question),
message,
confirmText = generalGetString(MR.strings.open_verb),
onConfirm = { safeOpenUri(uri, uriHandler) }
onConfirm = { safeOpenUri(uri, uriHandler) },
parseHtml = false
)
} else {
AlertManager.shared.showAlertDialogButtonsColumn(
generalGetString(MR.strings.privacy_chat_list_open_web_link_question),
message,
parseHtml = false,
buttons = {
Column {
SectionItemView({
@@ -585,7 +589,7 @@ fun safeOpenUri(uri: String, uriHandler: UriHandler) {
}
fun showInvalidLinkAlert(uri: String, error: String? = null) {
val message = if (error.isNullOrEmpty()) { uri } else { error + "\n" + uri }
val message = if (error.isNullOrEmpty()) { displayedUri(uri) } else { error + "\n" + displayedUri(uri) }
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_parsing_uri_title), message)
}
@@ -617,13 +617,27 @@ fun UriHandler.openExternalLink(uri: String) {
} else {
AlertManager.shared.showAlertDialog(
title = generalGetString(MR.strings.open_external_link_title),
text = uri,
text = displayedUri(uri),
confirmText = generalGetString(MR.strings.open_verb),
onConfirm = { uriHandler.openUriCatching(uri) }
onConfirm = { uriHandler.openUriCatching(uri) },
parseHtml = false
)
}
}
val invisibleChar = Regex("\\p{Cf}")
private val nonAsciiChar = Regex("[^\\x00-\\x7F]")
fun unicodeMarker(c: String): String = "[U+" + Integer.toHexString(c.codePointAt(0)).uppercase().padStart(4, '0') + "]"
fun displayedUri(uri: String): String {
val authorityStart = uri.indexOf("://").takeIf { it >= 0 }?.plus(3) ?: uri.length
val authorityEnd = uri.indexOfAny(charArrayOf('/', '?', '#'), authorityStart).takeIf { it >= 0 } ?: uri.length
val authority = uri.substring(authorityStart, authorityEnd).replace(nonAsciiChar) { unicodeMarker(it.value) }
return uri.replaceRange(authorityStart, authorityEnd, authority).replace(invisibleChar) { unicodeMarker(it.value) }
}
fun IntSize.Companion.Saver(): Saver<IntSize, *> = Saver(
save = { it.width to it.height },
restore = { IntSize(it.first, it.second) }