Skip to content

Commit 45643d1

Browse files
committed
Initialize OudsBulletList component
1 parent 14a112c commit 45643d1

File tree

10 files changed

+395
-0
lines changed

10 files changed

+395
-0
lines changed

NOTICE.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ app/src/prod/res/drawable/ic_launcher_foreground.xml
6868
docs/assets/banner.png
6969
docs/assets/logo-icon.svg
7070

71+
theme-contract/src/main/res/drawable/ic_bullet_list_level0.xml
72+
theme-contract/src/main/res/drawable/ic_bullet_list_level1.xml
73+
theme-contract/src/main/res/drawable/ic_bullet_list_level2.xml
7174
theme-contract/src/main/res/drawable/ic_checkbox_indeterminate.xml
7275
theme-contract/src/main/res/drawable/ic_checkbox_selected.xml
7376
theme-contract/src/main/res/drawable/ic_chevron_left.xml
Lines changed: 352 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,352 @@
1+
/*
2+
* Software Name: OUDS Android
3+
* SPDX-FileCopyrightText: Copyright (c) Orange SA
4+
* SPDX-License-Identifier: MIT
5+
*
6+
* This software is distributed under the MIT license,
7+
* the text of which is available at https://opensource.org/license/MIT/
8+
* or see the "LICENSE" file for more details.
9+
*
10+
* Software description: Android library of reusable graphical components
11+
*/
12+
13+
package com.orange.ouds.core.component
14+
15+
import android.util.Log
16+
import androidx.annotation.DrawableRes
17+
import androidx.compose.foundation.isSystemInDarkTheme
18+
import androidx.compose.foundation.layout.Box
19+
import androidx.compose.foundation.layout.Column
20+
import androidx.compose.foundation.layout.Row
21+
import androidx.compose.foundation.layout.padding
22+
import androidx.compose.foundation.layout.size
23+
import androidx.compose.foundation.layout.width
24+
import androidx.compose.material3.Icon
25+
import androidx.compose.material3.Text
26+
import androidx.compose.runtime.Composable
27+
import androidx.compose.runtime.LaunchedEffect
28+
import androidx.compose.runtime.remember
29+
import androidx.compose.ui.Alignment
30+
import androidx.compose.ui.Modifier
31+
import androidx.compose.ui.res.painterResource
32+
import androidx.compose.ui.text.TextStyle
33+
import androidx.compose.ui.tooling.preview.PreviewLightDark
34+
import androidx.compose.ui.tooling.preview.PreviewParameter
35+
import androidx.compose.ui.unit.Dp
36+
import androidx.compose.ui.unit.dp
37+
import com.orange.ouds.core.theme.OudsTheme
38+
import com.orange.ouds.core.theme.value
39+
import com.orange.ouds.core.utilities.OudsPreview
40+
import com.orange.ouds.core.utilities.getPreviewTheme
41+
import com.orange.ouds.foundation.utilities.BasicPreviewParameterProvider
42+
import com.orange.ouds.theme.OudsThemeContract
43+
44+
@Composable
45+
fun OudsBulletList(
46+
modifier: Modifier = Modifier,
47+
type: OudsBulletListType = OudsBulletListDefaults.Type,
48+
textStyle: OudsBulletListTextStyle = OudsBulletListDefaults.TextStyle,
49+
bold: Boolean = true,
50+
builder: OudsBulletListBuilder.() -> Unit
51+
) {
52+
val items = remember(builder) {
53+
OudsBulletListBuilderImpl().apply(builder).build()
54+
}
55+
56+
Column(modifier = modifier) {
57+
items.forEachIndexed { index, item ->
58+
OudsBulletListItem(
59+
item = item,
60+
currentType = type,
61+
currentTextStyle = textStyle,
62+
currentHasBoldText = bold,
63+
index = index,
64+
level = OudsBulletListItemNestedLevel.Zero
65+
)
66+
}
67+
}
68+
}
69+
70+
interface OudsBulletListBuilder {
71+
fun item(
72+
label: String,
73+
type: OudsBulletListType? = null,
74+
textStyle: OudsBulletListTextStyle? = null,
75+
bold: Boolean? = null,
76+
builder: (OudsBulletListBuilder.() -> Unit)? = null
77+
)
78+
}
79+
80+
private class OudsBulletListBuilderImpl : OudsBulletListBuilder {
81+
private val items = mutableListOf<BulletListItem>()
82+
83+
override fun item(
84+
label: String,
85+
type: OudsBulletListType?,
86+
textStyle: OudsBulletListTextStyle?,
87+
bold: Boolean?,
88+
builder: (OudsBulletListBuilder.() -> Unit)?
89+
) {
90+
val children = builder?.let { OudsBulletListBuilderImpl().apply(it).build() } ?: emptyList()
91+
items.add(BulletListItem(label, type, textStyle, bold, children))
92+
}
93+
94+
fun build(): List<BulletListItem> = items
95+
}
96+
97+
@Composable
98+
private fun OudsBulletListItem(
99+
item: BulletListItem,
100+
currentType: OudsBulletListType,
101+
currentTextStyle: OudsBulletListTextStyle,
102+
currentHasBoldText: Boolean,
103+
index: Int,
104+
level: OudsBulletListItemNestedLevel,
105+
modifier: Modifier = Modifier
106+
) {
107+
with(OudsTheme.componentsTokens.bulletList) {
108+
109+
val typography: TextStyle
110+
val columnGap: Dp
111+
val verticalPadding: Dp
112+
val bulletSize: Dp
113+
val bulletContainerSize: Dp
114+
when (currentTextStyle) {
115+
OudsBulletListTextStyle.BodyLarge -> {
116+
typography = if (currentHasBoldText) OudsTheme.typography.body.strong.large else OudsTheme.typography.body.default.large
117+
columnGap = spaceColumnGapBodyLarge.value
118+
verticalPadding = spacePaddingBlockBodyLarge.value
119+
bulletSize = 20.dp
120+
bulletContainerSize = 24.dp
121+
}
122+
OudsBulletListTextStyle.BodyMedium -> {
123+
typography = if (currentHasBoldText) OudsTheme.typography.body.strong.medium else OudsTheme.typography.body.default.medium
124+
columnGap = spaceColumnGapBodyMedium.value
125+
verticalPadding = spacePaddingBlockBodyMedium.value
126+
bulletSize = 16.dp
127+
bulletContainerSize = 20.dp
128+
}
129+
}
130+
131+
val paddingStart = when (level) {
132+
OudsBulletListItemNestedLevel.Zero -> spacePaddingInlineLevel0
133+
OudsBulletListItemNestedLevel.One -> spacePaddingInlineLevel1
134+
OudsBulletListItemNestedLevel.Two -> spacePaddingInlineLevel2
135+
}.dp
136+
137+
Row(
138+
modifier = modifier
139+
.padding(start = paddingStart)
140+
.padding(vertical = verticalPadding),
141+
verticalAlignment = Alignment.Top
142+
) {
143+
Box(modifier = Modifier.size(bulletContainerSize), contentAlignment = Alignment.CenterEnd) {
144+
Bullet(type = currentType, level = level, index = index, typography = typography, size = bulletSize)
145+
}
146+
147+
Column {
148+
Text(
149+
modifier = Modifier.padding(start = columnGap),
150+
text = item.label,
151+
style = typography,
152+
color = OudsTheme.colorScheme.content.default
153+
)
154+
155+
if (item.children.isNotEmpty()) {
156+
val nextLevel = OudsBulletListItemNestedLevel.entries.getOrNull(level.ordinal + 1)
157+
if (nextLevel != null) {
158+
val nextType = item.subListType ?: currentType
159+
val nextTextStyle = item.subListTextStyle ?: currentTextStyle
160+
val nextBoldValue = item.subListHasBoldText ?: currentHasBoldText
161+
162+
item.children.forEachIndexed { childIndex, childItem ->
163+
OudsBulletListItem(
164+
item = childItem,
165+
currentType = nextType,
166+
currentTextStyle = nextTextStyle,
167+
currentHasBoldText = nextBoldValue,
168+
index = childIndex,
169+
level = nextLevel
170+
)
171+
}
172+
} else {
173+
LaunchedEffect(Unit) {
174+
Log.w("OudsBulletList", "Maximum list depth (3 levels) reached. Children of '${item.label}' will not be displayed.")
175+
}
176+
}
177+
}
178+
}
179+
}
180+
}
181+
}
182+
183+
private data class BulletListItem(
184+
val label: String,
185+
val subListType: OudsBulletListType?,
186+
val subListTextStyle: OudsBulletListTextStyle?,
187+
val subListHasBoldText: Boolean?,
188+
val children: List<BulletListItem> = emptyList()
189+
)
190+
191+
@Composable
192+
private fun Bullet(type: OudsBulletListType, level: OudsBulletListItemNestedLevel, index: Int, typography: TextStyle, size: Dp) {
193+
when (type) {
194+
OudsBulletListType.Unordered -> when (level) {
195+
OudsBulletListItemNestedLevel.Zero -> UnorderedBullet(iconRes = OudsTheme.drawableResources.bulletListLevel0, size = size)
196+
OudsBulletListItemNestedLevel.One -> UnorderedBullet(iconRes = OudsTheme.drawableResources.bulletListLevel1, size = size)
197+
OudsBulletListItemNestedLevel.Two -> UnorderedBullet(iconRes = OudsTheme.drawableResources.bulletListLevel2, size = size)
198+
}
199+
OudsBulletListType.Ordered -> when (level) {
200+
OudsBulletListItemNestedLevel.Zero -> OrderedBullet("${index + 1}.", textStyle = typography, size = size)
201+
OudsBulletListItemNestedLevel.One -> OrderedBullet("${('A' + index)}.", textStyle = typography, size = size)
202+
OudsBulletListItemNestedLevel.Two -> OrderedBullet("${('a' + index)}.", textStyle = typography, size = size)
203+
}
204+
OudsBulletListType.Bare -> Box(modifier = Modifier.size(20.dp))
205+
}
206+
}
207+
208+
@Composable
209+
private fun UnorderedBullet(@DrawableRes iconRes: Int, size: Dp) {
210+
Icon(
211+
modifier = Modifier.size(size),
212+
painter = painterResource(iconRes),
213+
tint = OudsTheme.colorScheme.content.default,
214+
contentDescription = null
215+
)
216+
}
217+
218+
@Composable
219+
private fun OrderedBullet(text: String, textStyle: TextStyle, size: Dp) {
220+
Box(modifier = Modifier.width(size), contentAlignment = Alignment.CenterEnd) {
221+
Text(text = text, style = textStyle, color = OudsTheme.colorScheme.content.default)
222+
}
223+
}
224+
225+
/**
226+
* Default values for [OudsBulletList].
227+
*/
228+
object OudsBulletListDefaults {
229+
230+
/**
231+
* Default type of an [OudsBulletList].
232+
*/
233+
val Type = OudsBulletListType.Bare
234+
235+
/**
236+
* Default text style of an [OudsBulletList].
237+
*/
238+
val TextStyle = OudsBulletListTextStyle.BodyLarge
239+
}
240+
241+
enum class OudsBulletListType {
242+
/**
243+
* Collects related items that don't need to be in a specific order or sequence.List items are typically marked with bullets, but it is also possible
244+
* to use a tick or any Solaris icon. //TODO list with Solaris icon
245+
*/
246+
Unordered,
247+
248+
/**
249+
* Collects related items with numeric order or sequence. Numbering starts at 1 with the first list item and increases by increments of 1 for each
250+
* successive ordered list item.
251+
*/
252+
Ordered,
253+
254+
/**
255+
* An unordered list without any bullet or alphanumeric sequence. It sill has left-padding, so list items will appear indented. This is the default and
256+
* is also known as undecorated "Unstyled" list.
257+
*/
258+
Bare
259+
}
260+
261+
enum class OudsBulletListTextStyle {
262+
/**
263+
* Make sure to use this reference if the text accompanying the list component is the body large text.
264+
* This variant is designed for more visual, engaging experiences.
265+
*/
266+
BodyLarge,
267+
268+
/**
269+
* Make sure to use this reference if the text accompanying the list component is the body medium text.
270+
* This variant is best suited for functional, task oriented experiences.
271+
*/
272+
BodyMedium
273+
}
274+
275+
private enum class OudsBulletListItemNestedLevel {
276+
/**
277+
* Level 0 list items define the main structure.
278+
* Unordered level 0 list items are marked with full squares.
279+
* Ordered level 0 list items are marked with numbers.
280+
*/
281+
Zero,
282+
283+
/**
284+
* Level 1 (nested) list items provide hierarchy or subcategories.
285+
* Unordered level 1 list items are marked with outlined squares.
286+
* Ordered level 1 list items are marked with uppercase letters.
287+
*/
288+
One,
289+
290+
/**
291+
* Level 2 (nested) list items provide hierarchy or subcategories.
292+
* Unordered level 1 list items are marked with dashes.
293+
* Ordered level 1 list items are marked with lowercase letters.
294+
*/
295+
Two
296+
}
297+
298+
@PreviewLightDark
299+
@Composable
300+
@Suppress("PreviewShouldNotBeCalledRecursively")
301+
private fun PreviewOudsBulletList(@PreviewParameter(OudsBulletListPreviewParameterProvider::class) parameter: OudsBulletListPreviewParameter) {
302+
PreviewOudsBulletList(theme = getPreviewTheme(), darkThemeEnabled = isSystemInDarkTheme(), parameter = parameter)
303+
}
304+
305+
@Composable
306+
private fun PreviewOudsBulletList(theme: OudsThemeContract, darkThemeEnabled: Boolean, parameter: OudsBulletListPreviewParameter) {
307+
OudsPreview(theme = theme, darkThemeEnabled = darkThemeEnabled) {
308+
with(parameter) {
309+
OudsBulletList(
310+
type = type,
311+
textStyle = textStyle
312+
) {
313+
item(label = "${type.name} first item")
314+
item(
315+
label = "${type.name} second item with an unordered sublist",
316+
type = OudsBulletListType.Unordered
317+
) {
318+
item(label = "Unordered subitem")
319+
item(
320+
label = "Unordered subitem with an ordered sublist",
321+
type = OudsBulletListType.Ordered,
322+
bold = false
323+
) {
324+
item(label = "Ordered subitem")
325+
item(label = "Ordered subitem")
326+
}
327+
}
328+
item(
329+
label = "${type.name} third item with a sublist that inherits from the parent type",
330+
) {
331+
item(label = "${type.name} subitem")
332+
item(label = "${type.name} subitem")
333+
}
334+
item(label = "Ordered fourth item")
335+
}
336+
}
337+
}
338+
}
339+
340+
internal data class OudsBulletListPreviewParameter(
341+
val type: OudsBulletListType = OudsBulletListDefaults.Type,
342+
val textStyle: OudsBulletListTextStyle = OudsBulletListDefaults.TextStyle
343+
)
344+
345+
internal class OudsBulletListPreviewParameterProvider : BasicPreviewParameterProvider<OudsBulletListPreviewParameter>(*previewParameterValues.toTypedArray())
346+
347+
private val previewParameterValues: List<OudsBulletListPreviewParameter>
348+
get() = listOf(
349+
OudsBulletListPreviewParameter(),
350+
OudsBulletListPreviewParameter(type = OudsBulletListType.Ordered, textStyle = OudsBulletListTextStyle.BodyMedium),
351+
OudsBulletListPreviewParameter(type = OudsBulletListType.Unordered),
352+
)

theme-contract/src/main/java/com/orange/ouds/theme/OudsDrawableResources.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ import com.orange.ouds.foundation.InternalOudsApi
1717

1818
@InternalOudsApi
1919
open class OudsDrawableResources {
20+
open val bulletListLevel0: Int
21+
@DrawableRes get() = R.drawable.ic_bullet_list_level0
22+
open val bulletListLevel1: Int
23+
@DrawableRes get() = R.drawable.ic_bullet_list_level1
24+
open val bulletListLevel2: Int
25+
@DrawableRes get() = R.drawable.ic_bullet_list_level2
2026
open val checkboxIndeterminate: Int
2127
@DrawableRes get() = R.drawable.ic_checkbox_indeterminate
2228
open val checkboxSelected: Int

theme-contract/src/main/java/com/orange/ouds/theme/tokens/components/OudsComponentsTokens.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ package com.orange.ouds.theme.tokens.components
1414

1515
interface OudsComponentsTokens {
1616
val badge: OudsBadgeTokens
17+
val bulletList: OudsBulletListTokens
1718
val button: OudsButtonTokens
1819
val buttonMonochrome: OudsButtonMonoTokens
1920
val checkbox: OudsCheckboxTokens
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
2+
android:width="20dp"
3+
android:height="20dp"
4+
android:viewportWidth="20"
5+
android:viewportHeight="20">
6+
<path
7+
android:pathData="M6.25,6.25h7.5v7.5h-7.5z"
8+
android:fillColor="#000000"/>
9+
</vector>

0 commit comments

Comments
 (0)