When an emoji is used as the 'initial' for an avatar, use the whole emoji (#4277)
* When an emoji is used as the 'initial' for an avatar, use the whole emoji Use `BreakIterator.getCharacterInstance()` for a simpler solution.
This commit is contained in:
committed by
GitHub
parent
047e659719
commit
717a15bea5
@@ -33,6 +33,7 @@ android {
|
||||
implementation(libs.vanniktech.blurhash)
|
||||
implementation(projects.features.enterprise.api)
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.core)
|
||||
implementation(projects.libraries.preferences.api)
|
||||
implementation(projects.libraries.testtags)
|
||||
implementation(projects.libraries.uiStrings)
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
package io.element.android.libraries.designsystem.components.avatar
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import io.element.android.libraries.core.data.tryOrNull
|
||||
import java.text.BreakIterator
|
||||
|
||||
@Immutable
|
||||
data class AvatarData(
|
||||
@@ -27,24 +29,36 @@ data class AvatarData(
|
||||
startIndex++
|
||||
}
|
||||
|
||||
var length = 1
|
||||
var first = dn[startIndex]
|
||||
var next = dn[startIndex]
|
||||
|
||||
// LEFT-TO-RIGHT MARK
|
||||
if (dn.length >= 2 && 0x200e == first.code) {
|
||||
if (dn.length >= 2 && 0x200e == next.code) {
|
||||
startIndex++
|
||||
first = dn[startIndex]
|
||||
next = dn[startIndex]
|
||||
}
|
||||
|
||||
// check if it’s the start of a surrogate pair
|
||||
if (first.code in 0xD800..0xDBFF && dn.length > startIndex + 1) {
|
||||
val second = dn[startIndex + 1]
|
||||
if (second.code in 0xDC00..0xDFFF) {
|
||||
length++
|
||||
while (next.isWhitespace()) {
|
||||
if (dn.length > startIndex + 1) {
|
||||
startIndex++
|
||||
next = dn[startIndex]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
dn.substring(startIndex, startIndex + length)
|
||||
val fullCharacterIterator = BreakIterator.getCharacterInstance()
|
||||
fullCharacterIterator.setText(dn)
|
||||
val glyphBoundary = tryOrNull { fullCharacterIterator.following(startIndex) }
|
||||
?.takeIf { it in startIndex..dn.length }
|
||||
|
||||
when {
|
||||
// Use the found boundary
|
||||
glyphBoundary != null -> dn.substring(startIndex, glyphBoundary)
|
||||
// If no boundary was found, default to the next char if possible
|
||||
startIndex + 1 < dn.length -> dn.substring(startIndex, startIndex + 1)
|
||||
// Return a fallback character otherwise
|
||||
else -> "#"
|
||||
}
|
||||
}
|
||||
.uppercase()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.designsystem.components.avatar
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Test
|
||||
|
||||
class AvatarDataTest {
|
||||
@Test
|
||||
fun `initial with text should get the first char, uppercased`() {
|
||||
val data = AvatarData("id", "test", null, AvatarSize.InviteSender)
|
||||
assertThat(data.initial).isEqualTo("T")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `initial with leading whitespace should get the first non-whitespace char, uppercased`() {
|
||||
val data = AvatarData("id", " test", null, AvatarSize.InviteSender)
|
||||
assertThat(data.initial).isEqualTo("T")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `initial with long emoji should get the full emoji`() {
|
||||
val data = AvatarData("id", "\uD83C\uDFF3\uFE0F\u200D\uD83C\uDF08 Test", null, AvatarSize.InviteSender)
|
||||
assertThat(data.initial).isEqualTo("\uD83C\uDFF3\uFE0F\u200D\uD83C\uDF08")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `initial with short emoji should get the emoji`() {
|
||||
val data = AvatarData("id", "✂ Test", null, AvatarSize.InviteSender)
|
||||
assertThat(data.initial).isEqualTo("✂")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `initial with a single letter should take that letter`() {
|
||||
val data = AvatarData("id", "T", null, AvatarSize.InviteSender)
|
||||
assertThat(data.initial).isEqualTo("T")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user