Skip to content

Commit 54be31c

Browse files
authored
Merge pull request #4108 from element-hq/feature/fga/design_counter
design : CounterAtom
2 parents 531cea1 + 179a9d1 commit 54be31c

File tree

10 files changed

+111
-1
lines changed

10 files changed

+111
-1
lines changed

features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ open class RoomDetailsStateProvider : PreviewParameterProvider<RoomDetailsState>
4848
aRoomDetailsState(isPublic = false),
4949
aRoomDetailsState(heroes = aMatrixUserList()),
5050
aRoomDetailsState(pinnedMessagesCount = 3),
51+
aRoomDetailsState(knockRequestsCount = null, canShowKnockRequests = true),
52+
aRoomDetailsState(knockRequestsCount = 4, canShowKnockRequests = true),
5153
// Add other state here
5254
)
5355
}

features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ private fun KnockRequestsItem(knockRequestsCount: Int?, onKnockRequestsClick: ()
251251
trailingContent = if (knockRequestsCount == null || knockRequestsCount == 0) {
252252
null
253253
} else {
254-
ListItemContent.Text(knockRequestsCount.toString())
254+
ListItemContent.Counter(knockRequestsCount)
255255
},
256256
onClick = onKnockRequestsClick,
257257
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright 2025 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only
5+
* Please see LICENSE in the repository root for full details.
6+
*/
7+
8+
package io.element.android.libraries.designsystem.atomic.atoms
9+
10+
import androidx.compose.foundation.background
11+
import androidx.compose.foundation.layout.Arrangement.spacedBy
12+
import androidx.compose.foundation.layout.Box
13+
import androidx.compose.foundation.layout.Column
14+
import androidx.compose.foundation.layout.size
15+
import androidx.compose.foundation.shape.CircleShape
16+
import androidx.compose.runtime.Composable
17+
import androidx.compose.ui.Alignment
18+
import androidx.compose.ui.Modifier
19+
import androidx.compose.ui.draw.clip
20+
import androidx.compose.ui.graphics.Color
21+
import androidx.compose.ui.text.rememberTextMeasurer
22+
import androidx.compose.ui.unit.dp
23+
import io.element.android.compound.theme.ElementTheme
24+
import io.element.android.libraries.designsystem.preview.ElementPreview
25+
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
26+
import io.element.android.libraries.designsystem.text.toDp
27+
import io.element.android.libraries.designsystem.theme.components.Text
28+
29+
private const val MAX_COUNT = 99
30+
private const val MAX_COUNT_STRING = "+$MAX_COUNT"
31+
32+
/**
33+
* A counter atom that displays a number in a circle.
34+
* Figma link : https://www.figma.com/design/G1xy0HDZKJf5TCRFmKb5d5/Compound-Android-Components?node-id=2805-2649&m=dev
35+
*
36+
* @param count The number to display. If the number is greater than [MAX_COUNT], the counter will display [MAX_COUNT_STRING].
37+
* If the number is less than 1, the counter will not be displayed.
38+
* @param modifier The modifier to apply to this layout.
39+
*/
40+
@Composable
41+
fun CounterAtom(
42+
count: Int,
43+
modifier: Modifier = Modifier,
44+
) {
45+
if (count < 1) return
46+
val countAsText = when (count) {
47+
in 0..MAX_COUNT -> count.toString()
48+
else -> MAX_COUNT_STRING
49+
}
50+
val textStyle = ElementTheme.typography.fontBodyMdMedium
51+
val textMeasurer = rememberTextMeasurer()
52+
// Measure the maximum count string size
53+
val textLayoutResult = textMeasurer.measure(
54+
text = MAX_COUNT_STRING,
55+
style = textStyle
56+
)
57+
val textSize = textLayoutResult.size
58+
val squareSize = maxOf(textSize.width, textSize.height)
59+
Box(
60+
modifier = modifier
61+
.size(squareSize.toDp() + 1.dp)
62+
.clip(CircleShape)
63+
.background(ElementTheme.colors.iconSuccessPrimary)
64+
) {
65+
Text(
66+
modifier = Modifier.align(Alignment.Center),
67+
text = countAsText,
68+
style = textStyle,
69+
color = Color.White,
70+
)
71+
}
72+
}
73+
74+
@PreviewsDayNight
75+
@Composable
76+
internal fun CounterAtomPreview() = ElementPreview {
77+
Column(verticalArrangement = spacedBy(2.dp)) {
78+
CounterAtom(count = 0)
79+
CounterAtom(count = 4)
80+
CounterAtom(count = 99)
81+
CounterAtom(count = 100)
82+
}
83+
}

libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/list/ListItemContent.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import androidx.compose.ui.Modifier
1717
import androidx.compose.ui.text.style.TextOverflow
1818
import androidx.compose.ui.unit.DpSize
1919
import androidx.compose.ui.unit.dp
20+
import io.element.android.libraries.designsystem.atomic.atoms.CounterAtom
2021
import io.element.android.libraries.designsystem.atomic.atoms.RedIndicatorAtom
2122
import io.element.android.libraries.designsystem.theme.components.IconSource
2223
import io.element.android.libraries.designsystem.theme.components.ListItem
@@ -91,6 +92,9 @@ sealed interface ListItemContent {
9192
/** Displays a badge. */
9293
data object Badge : ListItemContent
9394

95+
/** Displays a counter. */
96+
data class Counter(val count: Int) : ListItemContent
97+
9498
@Composable
9599
fun View() {
96100
when (this) {
@@ -125,6 +129,9 @@ sealed interface ListItemContent {
125129
) {
126130
RedIndicatorAtom()
127131
}
132+
is Counter -> {
133+
CounterAtom(count = count)
134+
}
128135
is Custom -> content()
129136
}
130137
}
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 3 additions & 0 deletions
Loading
Loading

0 commit comments

Comments
 (0)