Truncate and ellipsize long reactions (#821)
* Truncate and ellipsize long reactions * Update screenshots --------- Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
parent
84b2415226
commit
565d943466
24 changed files with 144 additions and 11 deletions
1
changelog.d/821.bugfix
Normal file
1
changelog.d/821.bugfix
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Truncate and ellipsize long reactions
|
||||||
|
|
@ -44,6 +44,7 @@ import androidx.compose.ui.unit.sp
|
||||||
import io.element.android.features.messages.impl.R
|
import io.element.android.features.messages.impl.R
|
||||||
import io.element.android.features.messages.impl.timeline.model.AggregatedReaction
|
import io.element.android.features.messages.impl.timeline.model.AggregatedReaction
|
||||||
import io.element.android.features.messages.impl.timeline.model.AggregatedReactionProvider
|
import io.element.android.features.messages.impl.timeline.model.AggregatedReactionProvider
|
||||||
|
import io.element.android.features.messages.impl.timeline.model.aTimelineItemReactions
|
||||||
import io.element.android.libraries.designsystem.ElementTextStyles
|
import io.element.android.libraries.designsystem.ElementTextStyles
|
||||||
import io.element.android.libraries.designsystem.preview.DayNightPreviews
|
import io.element.android.libraries.designsystem.preview.DayNightPreviews
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
|
|
@ -132,7 +133,7 @@ private fun IconContent(
|
||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ReactionContent(
|
private fun ReactionContent(
|
||||||
reaction: AggregatedReaction,
|
reaction: AggregatedReaction,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) = Row(
|
) = Row(
|
||||||
|
|
@ -140,7 +141,7 @@ fun ReactionContent(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = reaction.key,
|
text = reaction.displayKey,
|
||||||
fontSize = 15.sp, lineHeight = reactionEmojiLineHeight
|
fontSize = 15.sp, lineHeight = reactionEmojiLineHeight
|
||||||
)
|
)
|
||||||
if (reaction.count > 1) {
|
if (reaction.count > 1) {
|
||||||
|
|
@ -148,7 +149,7 @@ fun ReactionContent(
|
||||||
Text(
|
Text(
|
||||||
text = reaction.count.toString(),
|
text = reaction.count.toString(),
|
||||||
color = if (reaction.isHighlighted) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.secondary,
|
color = if (reaction.isHighlighted) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.secondary,
|
||||||
fontSize = 14.sp
|
fontSize = 14.sp,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -174,6 +175,12 @@ internal fun MessagesReactionExtraButtonsPreview() = ElementPreview {
|
||||||
content = MessagesReactionsButtonContent.Text("12 more"),
|
content = MessagesReactionsButtonContent.Text("12 more"),
|
||||||
onClick = {}
|
onClick = {}
|
||||||
)
|
)
|
||||||
|
MessagesReactionButton(
|
||||||
|
content = MessagesReactionsButtonContent.Reaction(aTimelineItemReactions().reactions.first().copy(
|
||||||
|
key = "A very long reaction with many characters that should be truncated"
|
||||||
|
)),
|
||||||
|
onClick = {}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,18 @@
|
||||||
|
|
||||||
package io.element.android.features.messages.impl.timeline.model
|
package io.element.android.features.messages.impl.timeline.model
|
||||||
|
|
||||||
|
import io.element.android.libraries.core.extensions.ellipsize
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property key the reaction key (e.g. "👍")
|
* Length at which we ellipsize a reaction key for display
|
||||||
|
*
|
||||||
|
* Reactions can be free text, so we need to limit the length
|
||||||
|
* displayed on screen.
|
||||||
|
*/
|
||||||
|
private const val MAX_DISPLAY_CHARS = 16
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property key the full reaction key (e.g. "👍", "YES!")
|
||||||
* @property count the number of users who reacted with this key
|
* @property count the number of users who reacted with this key
|
||||||
* @property isHighlighted true if the reaction has (also) been sent by the current user.
|
* @property isHighlighted true if the reaction has (also) been sent by the current user.
|
||||||
*/
|
*/
|
||||||
|
|
@ -25,4 +35,14 @@ data class AggregatedReaction(
|
||||||
val key: String,
|
val key: String,
|
||||||
val count: Int,
|
val count: Int,
|
||||||
val isHighlighted: Boolean = false
|
val isHighlighted: Boolean = false
|
||||||
)
|
) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The key to be displayed on screen.
|
||||||
|
*
|
||||||
|
* See [MAX_DISPLAY_CHARS].
|
||||||
|
*/
|
||||||
|
val displayKey: String by lazy {
|
||||||
|
key.ellipsize(MAX_DISPLAY_CHARS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.messages.timeline.model
|
||||||
|
|
||||||
|
import io.element.android.features.messages.impl.timeline.model.AggregatedReaction
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class AggregatedReactionTest {
|
||||||
|
@Test
|
||||||
|
fun `reaction display key is shortened`() {
|
||||||
|
val reaction = AggregatedReaction(
|
||||||
|
key = "1234567890123456790",
|
||||||
|
count = 1,
|
||||||
|
isHighlighted = false
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals("1234567890123456…", reaction.displayKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -58,6 +58,21 @@ fun String?.insertBeforeLast(insert: String, delimiter: String = "."): String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Truncate and ellipsize text if it exceeds the given length.
|
||||||
|
*
|
||||||
|
* Throws if length is < 1.
|
||||||
|
*/
|
||||||
|
fun String.ellipsize(length: Int): String {
|
||||||
|
require(length > 1)
|
||||||
|
|
||||||
|
if (this.length <= length) {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
return "${this.take(length)}…"
|
||||||
|
}
|
||||||
|
|
||||||
inline fun <reified R> Any?.takeAs(): R? {
|
inline fun <reified R> Any?.takeAs(): R? {
|
||||||
return takeIf { it is R } as R?
|
return takeIf { it is R } as R?
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.core.extensions
|
||||||
|
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class BasicExtensionsTest {
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException::class)
|
||||||
|
fun `test ellipsize at 0`() {
|
||||||
|
"1234567890".ellipsize(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test ellipsize at 1`() {
|
||||||
|
assertEquals(
|
||||||
|
"1…",
|
||||||
|
"1234567890".ellipsize(1)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test ellipsize at 5`() {
|
||||||
|
val output = "1234567890".ellipsize(5)
|
||||||
|
assertEquals("12345…", output)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test ellipsize noop 1`() {
|
||||||
|
val input = "12345"
|
||||||
|
val output = input.ellipsize(5)
|
||||||
|
assertEquals(input, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test ellipsize noop 2`() {
|
||||||
|
val input = "123"
|
||||||
|
val output = input.ellipsize(5)
|
||||||
|
assertEquals(input, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:b024b20585c98eddb6032795e2a434bf675f7254d1f3a649ccccbe4fb2351c8f
|
|
||||||
size 7844
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:f1da445cf56c818ddcbc8ebb75fc0390122812a4a866ad14170ed2f652fac785
|
||||||
|
size 11163
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:cb3b2ccab39aa4cb7afde21d8a9b2c0d18f54d9f397e7020413281b3bfebaa2f
|
|
||||||
size 7634
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:8687ae17659becc872db2f643bc1fd777e396db28bfcad18ef8c849140018ce7
|
||||||
|
size 10661
|
||||||
Loading…
Add table
Add a link
Reference in a new issue