mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-10-31 08:15:02 +00:00
Iterate on unread counts again (#7869)
* Fix timestamp color to match the designs. * Don't limit the call and unread row height to 16dp: the font size is not fixed and it might display components in a broken state * Ensure the alignment works * Remove `SemanticColors.unreadIndicator` and use `.iconAccentPrimary` and `.textActionAccent` instead * Simplify and document `UnreadIndicatorAtom` * Use `fontBodySmMedium` for the font size in the unread indicator * Update screenshots --------- Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
committed by
GitHub
parent
367cea0213
commit
1564de9b6e
+4
-9
@@ -60,7 +60,6 @@ import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.roomListRoomMessage
|
||||
import io.element.android.libraries.designsystem.theme.roomListRoomMessageDate
|
||||
import io.element.android.libraries.designsystem.theme.roomListRoomName
|
||||
import io.element.android.libraries.designsystem.theme.unreadIndicator
|
||||
import io.element.android.libraries.matrix.api.notification.CallIntent
|
||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||
import io.element.android.libraries.matrix.api.user.DisplayedStatus
|
||||
@@ -244,7 +243,7 @@ private fun NameAndTimestampRow(
|
||||
text = timestamp ?: "",
|
||||
style = ElementTheme.typography.fontBodySmMedium,
|
||||
color = if (isHighlighted) {
|
||||
ElementTheme.colors.unreadIndicator
|
||||
ElementTheme.colors.textActionAccent
|
||||
} else {
|
||||
ElementTheme.colors.roomListRoomMessageDate
|
||||
},
|
||||
@@ -347,13 +346,12 @@ private fun MessagePreviewAndIndicatorRow(
|
||||
// Call and unread
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.height(16.dp)
|
||||
// Used to force this line to be read aloud earlier than the latest event when using Talkback
|
||||
.zIndex(-1f),
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
val tint = if (room.isHighlighted) ElementTheme.colors.unreadIndicator else ElementTheme.colors.iconQuaternary
|
||||
val tint = if (room.isHighlighted) ElementTheme.colors.textActionAccent else ElementTheme.colors.iconQuaternary
|
||||
if (room.hasRoomCall) {
|
||||
OnGoingCallIcon(
|
||||
color = tint,
|
||||
@@ -422,10 +420,7 @@ private fun InviteNameAndIndicatorRow(
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
if (!isInviteSeen) {
|
||||
UnreadIndicatorAtom(
|
||||
color = ElementTheme.colors.unreadIndicator,
|
||||
count = 0,
|
||||
)
|
||||
UnreadIndicatorAtom(count = 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -459,7 +454,7 @@ private fun MentionIndicatorAtom() {
|
||||
modifier = Modifier.size(16.dp),
|
||||
contentDescription = stringResource(CommonStrings.a11y_notifications_new_mentions),
|
||||
imageVector = CompoundIcons.Mention(),
|
||||
tint = ElementTheme.colors.unreadIndicator,
|
||||
tint = ElementTheme.colors.iconAccentPrimary,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+3
-2
@@ -264,13 +264,14 @@ private fun ThreadListItemRow(
|
||||
)
|
||||
}
|
||||
|
||||
if (hasUnreadNotifications) {
|
||||
UnreadIndicatorAtom(
|
||||
size = 14.dp,
|
||||
isVisible = hasUnreadNotifications,
|
||||
dotIndicatorSize = 14.dp,
|
||||
count = 0,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
|
||||
-1
@@ -597,7 +597,6 @@ private fun JumpToPositionButton(
|
||||
modifier = Modifier
|
||||
.align(dotAlignment)
|
||||
.offset { IntOffset(x = 0, y = dotYOffset.roundToPx()) },
|
||||
color = ElementTheme.colors.iconSuccessPrimary,
|
||||
border = BorderStroke(2.dp, ElementTheme.colors.bgCanvasDefault),
|
||||
count = 0,
|
||||
)
|
||||
|
||||
+40
-24
@@ -8,26 +8,32 @@
|
||||
|
||||
package io.element.android.libraries.designsystem.atomic.atoms
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement.spacedBy
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.plus
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.sizeIn
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.rememberTextMeasurer
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.coerceAtLeast
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.compound.tokens.generated.SemanticColors
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.text.toDp
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
|
||||
private const val MAX_COUNT = 99
|
||||
@@ -54,7 +60,7 @@ fun CounterAtom(
|
||||
modifier: Modifier = Modifier,
|
||||
containerColor: Color? = null,
|
||||
contentColor: Color? = null,
|
||||
contentPadding: PaddingValues = PaddingValues.Zero,
|
||||
contentPadding: PaddingValues = PaddingValues(vertical = 0.5.dp),
|
||||
textStyle: TextStyle = CounterAtomDefaults.textStyle,
|
||||
dotSize: Dp = 12.0.dp,
|
||||
isCritical: Boolean = false,
|
||||
@@ -65,32 +71,43 @@ fun CounterAtom(
|
||||
else -> MAX_COUNT_STRING
|
||||
}
|
||||
|
||||
val minSingleDigitSize = if (countAsText?.length == 1) {
|
||||
val density = LocalDensity.current
|
||||
val textMeasurer = rememberTextMeasurer()
|
||||
// Measure the maximum count string size
|
||||
val textLayoutResult = textMeasurer.measure(
|
||||
text = countAsText,
|
||||
val minTextSize = remember(density.density, textStyle) {
|
||||
val result = textMeasurer.measure(
|
||||
text = MAX_COUNT_STRING,
|
||||
style = textStyle
|
||||
)
|
||||
val textSize = textLayoutResult.size
|
||||
maxOf(textSize.width, textSize.height)
|
||||
with(density) {
|
||||
minOf(result.size.width, result.size.height).toDp()
|
||||
}
|
||||
}
|
||||
val sizeModifier = if (countAsText?.length == 1) {
|
||||
Modifier.size(minTextSize)
|
||||
} else {
|
||||
0
|
||||
Modifier.sizeIn(minHeight = minTextSize)
|
||||
}
|
||||
|
||||
androidx.compose.material3.Badge(
|
||||
modifier = modifier.then(
|
||||
if (countAsText == null) Modifier.sizeIn(minWidth = dotSize, minHeight = dotSize) else Modifier
|
||||
),
|
||||
containerColor = containerColor ?: if (isCritical) ElementTheme.colors.iconCriticalPrimary else ElementTheme.colors.iconAccentPrimary,
|
||||
contentColor = contentColor ?: ElementTheme.colors.textOnSolidPrimary,
|
||||
content = countAsText?.let {
|
||||
@Composable {
|
||||
val extraPadding = if (countAsText?.length == 1) {
|
||||
contentPadding
|
||||
} else {
|
||||
contentPadding + PaddingValues(horizontal = 4.dp)
|
||||
}
|
||||
val backgroundColor = containerColor ?: if (isCritical) ElementTheme.colors.iconCriticalPrimary else ElementTheme.colors.iconAccentPrimary
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.clip(RoundedCornerShape(100))
|
||||
.background(backgroundColor)
|
||||
.then(
|
||||
if (countAsText == null) Modifier.size(dotSize) else Modifier
|
||||
)
|
||||
) {
|
||||
countAsText?.let {
|
||||
Text(
|
||||
modifier = Modifier.sizeIn(
|
||||
minWidth = (minSingleDigitSize.toDp() - 8.dp).coerceAtLeast(0.dp),
|
||||
minHeight = minSingleDigitSize.toDp()
|
||||
).padding(contentPadding),
|
||||
modifier = sizeModifier
|
||||
.align(Alignment.Center)
|
||||
.padding(extraPadding),
|
||||
text = countAsText,
|
||||
style = textStyle,
|
||||
color = contentColor ?: ElementTheme.colors.textOnSolidPrimary,
|
||||
@@ -98,7 +115,6 @@ fun CounterAtom(
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
object CounterAtomDefaults {
|
||||
@@ -112,7 +128,7 @@ internal fun CounterAtomPreview() = ElementPreview {
|
||||
Row(horizontalArrangement = spacedBy(2.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
CounterAtom(count = 0)
|
||||
CounterAtom(count = 4)
|
||||
CounterAtom(count = 99)
|
||||
CounterAtom(count = 10)
|
||||
CounterAtom(count = 100)
|
||||
CounterAtom(count = 4, isCritical = true)
|
||||
}
|
||||
|
||||
+18
-12
@@ -13,7 +13,6 @@ import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -27,36 +26,43 @@ import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.unreadIndicator
|
||||
|
||||
/**
|
||||
* A small indicator to show the number of unread messages or notifications.
|
||||
*
|
||||
* @param count The number of unread messages or notifications. If null, the indicator will be hidden, if 0, a notification dot will be shown.
|
||||
* @param modifier The modifier to be applied to the indicator.
|
||||
* @param dotIndicatorSize The size of the dot indicator.
|
||||
* @param color The background color of the indicator.
|
||||
* @param contentDescription The content description for accessibility.
|
||||
* @param border The border to be applied to the indicator.
|
||||
* @param contentPadding The padding inside the indicator.
|
||||
*/
|
||||
@Composable
|
||||
fun UnreadIndicatorAtom(
|
||||
count: Long?,
|
||||
modifier: Modifier = Modifier,
|
||||
size: Dp = 12.dp,
|
||||
count: Long? = null,
|
||||
color: Color = ElementTheme.colors.unreadIndicator,
|
||||
isVisible: Boolean = true,
|
||||
dotIndicatorSize: Dp = 12.dp,
|
||||
color: Color = ElementTheme.colors.iconAccentPrimary,
|
||||
contentDescription: String? = null,
|
||||
border: BorderStroke? = null,
|
||||
contentPadding: PaddingValues = PaddingValues.Zero,
|
||||
) {
|
||||
when {
|
||||
!isVisible -> Spacer(modifier = modifier.size(size))
|
||||
else -> count?.let {
|
||||
count?.let {
|
||||
CounterAtom(
|
||||
count = count.toInt(),
|
||||
modifier = modifier
|
||||
.semantics {
|
||||
contentDescription?.let { this.contentDescription = it }
|
||||
}
|
||||
.then(if (border != null) Modifier.border(border, RoundedCornerShape(percent = 50)) else Modifier),
|
||||
.then(if (count == 0L) Modifier.size(dotIndicatorSize) else Modifier)
|
||||
.then(if (border != null) Modifier.border(border, RoundedCornerShape(percent = 100)) else Modifier),
|
||||
containerColor = color,
|
||||
contentColor = ElementTheme.colors.bgCanvasDefault,
|
||||
textStyle = ElementTheme.typography.fontBodySmMedium,
|
||||
contentPadding = contentPadding,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@@ -66,7 +72,7 @@ internal fun UnreadIndicatorAtomPreview() = ElementPreview {
|
||||
UnreadIndicatorAtom(count = null)
|
||||
UnreadIndicatorAtom(count = 0)
|
||||
UnreadIndicatorAtom(count = 1)
|
||||
UnreadIndicatorAtom(count = 10)
|
||||
UnreadIndicatorAtom(count = 20)
|
||||
UnreadIndicatorAtom(count = 99)
|
||||
UnreadIndicatorAtom(count = 999)
|
||||
}
|
||||
|
||||
-4
@@ -32,9 +32,6 @@ val SemanticColors.roomListRoomMessage
|
||||
val SemanticColors.roomListRoomMessageDate
|
||||
get() = textSecondary
|
||||
|
||||
val SemanticColors.unreadIndicator
|
||||
get() = iconAccentTertiary
|
||||
|
||||
val SemanticColors.placeholderBackground
|
||||
get() = bgSubtleSecondary
|
||||
|
||||
@@ -83,7 +80,6 @@ internal fun ColorAliasesPreview() = ElementPreview {
|
||||
"roomListRoomName" to ElementTheme.colors.roomListRoomName,
|
||||
"roomListRoomMessage" to ElementTheme.colors.roomListRoomMessage,
|
||||
"roomListRoomMessageDate" to ElementTheme.colors.roomListRoomMessageDate,
|
||||
"unreadIndicator" to ElementTheme.colors.unreadIndicator,
|
||||
"placeholderBackground" to ElementTheme.colors.placeholderBackground,
|
||||
"messageFromMeBackground" to ElementTheme.colors.messageFromMeBackground,
|
||||
"messageFromOtherBackground" to ElementTheme.colors.messageFromOtherBackground,
|
||||
|
||||
+3
-5
@@ -36,7 +36,7 @@ import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.CounterAtom
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.UnreadIndicatorAtom
|
||||
import io.element.android.libraries.designsystem.components.tooltip.PlainTooltip
|
||||
import io.element.android.libraries.designsystem.components.tooltip.TooltipBox
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
@@ -140,12 +140,10 @@ fun HorizontalFloatingToolbarItem(
|
||||
BadgedBox(
|
||||
badge = {
|
||||
counter?.let {
|
||||
CounterAtom(
|
||||
count = it,
|
||||
UnreadIndicatorAtom(
|
||||
count = it.toLong(),
|
||||
// Tweak the offset so it's centered on the icon
|
||||
modifier = Modifier.offset(x = (-16).dp, y = 10.dp),
|
||||
contentPadding = PaddingValues(vertical = 0.5.dp),
|
||||
textStyle = ElementTheme.typography.fontBodySmMedium,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-5
@@ -44,7 +44,6 @@ import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.unreadIndicator
|
||||
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
|
||||
import io.element.android.libraries.matrix.api.spaces.SpaceRoomVisibility
|
||||
@@ -168,10 +167,7 @@ private fun NameAndIndicatorRow(
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
if (showIndicator) {
|
||||
UnreadIndicatorAtom(
|
||||
color = ElementTheme.colors.unreadIndicator,
|
||||
count = 0,
|
||||
)
|
||||
UnreadIndicatorAtom(count = 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LFS
BIN
Binary file not shown.
LFS
BIN
Binary file not shown.
LFS
BIN
Binary file not shown.
LFS
BIN
Binary file not shown.
LFS
BIN
Binary file not shown.
LFS
BIN
Binary file not shown.
LFS
BIN
Binary file not shown.
LFS
BIN
Binary file not shown.
LFS
BIN
Binary file not shown.
LFS
BIN
Binary file not shown.
tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_10_en.png
LFS
BIN
Binary file not shown.
tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_13_en.png
LFS
BIN
Binary file not shown.
tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_15_en.png
LFS
BIN
Binary file not shown.
tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_16_en.png
LFS
BIN
Binary file not shown.
tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_25_en.png
LFS
BIN
Binary file not shown.
tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_26_en.png
LFS
BIN
Binary file not shown.
tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_27_en.png
LFS
BIN
Binary file not shown.
tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_28_en.png
LFS
BIN
Binary file not shown.
tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_31_en.png
LFS
BIN
Binary file not shown.
tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_32_en.png
LFS
BIN
Binary file not shown.
tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_33_en.png
LFS
BIN
Binary file not shown.
tests/uitests/src/test/snapshots/images/features.home.impl.components_RoomSummaryRow_Night_34_en.png
LFS
BIN
Binary file not shown.
LFS
BIN
Binary file not shown.
LFS
BIN
Binary file not shown.
tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.atoms_CounterAtom_Day_0_en.png
LFS
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
LFS
BIN
Binary file not shown.
Reference in New Issue
Block a user