From 7073f7dec0d723fe814fe0a3ac451a666ae7a544 Mon Sep 17 00:00:00 2001 From: Taksh Date: Sun, 23 Aug 2026 20:01:51 +0530 Subject: [PATCH] fix: stop nickname truncation cutting a character in half truncateNickname compared and cut with String.length and take, both of which count UTF-16 code units. A name whose 15th unit is the high surrogate of an emoji lost its low surrogate, leaving a lone U+D83D that renders as a tofu box wherever the name is shown - the peer list, the geohash people list, and every inline mention. Step back to the last grapheme boundary at or before the limit instead, so a truncated name is always whole. The limit itself is unchanged, so an ASCII name truncates exactly as before. --- .../java/com/bitchat/android/ui/ChatUIConstants.kt | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/bitchat/android/ui/ChatUIConstants.kt b/app/src/main/java/com/bitchat/android/ui/ChatUIConstants.kt index f00d650e..1a15a497 100644 --- a/app/src/main/java/com/bitchat/android/ui/ChatUIConstants.kt +++ b/app/src/main/java/com/bitchat/android/ui/ChatUIConstants.kt @@ -1,9 +1,19 @@ package com.bitchat.android.ui +import java.text.BreakIterator + /** * UI constants/utilities for nickname rendering. */ fun truncateNickname(name: String, maxLen: Int = com.bitchat.android.util.AppConstants.UI.MAX_NICKNAME_LENGTH): String { - return if (name.length <= maxLen) name else name.take(maxLen) -} + if (name.length <= maxLen) return name + // The limit counts UTF-16 code units, so cutting at it can land inside a + // surrogate pair and leave a lone surrogate that renders as a tofu box, or + // split a ZWJ sequence into its parts. Step back to the last grapheme + // boundary at or before the limit so what is shown is always whole. + val boundaries = BreakIterator.getCharacterInstance() + boundaries.setText(name) + val end = boundaries.preceding(maxLen + 1) + return if (end == BreakIterator.DONE) "" else name.substring(0, end) +}