Skip to content

Commit 8afa331

Browse files
committed
Add rich text for alert message
1 parent 7a13971 commit 8afa331

6 files changed

Lines changed: 212 additions & 32 deletions

File tree

core/src/main/java/com/orange/ouds/core/component/OudsAlertMessage.kt

Lines changed: 106 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,13 @@ import androidx.compose.ui.platform.LocalConfiguration
4545
import androidx.compose.ui.res.painterResource
4646
import androidx.compose.ui.res.stringResource
4747
import androidx.compose.ui.semantics.semantics
48+
import androidx.compose.ui.text.AnnotatedString
4849
import androidx.compose.ui.tooling.preview.Preview
4950
import androidx.compose.ui.tooling.preview.PreviewParameter
5051
import androidx.compose.ui.unit.dp
5152
import com.orange.ouds.core.R
53+
import com.orange.ouds.core.component.common.text.OudsAnnotatedAlertMessageBulletListLabel
54+
import com.orange.ouds.core.component.common.text.OudsAnnotatedAlertMessageDescription
5255
import com.orange.ouds.core.component.content.OudsComponentContent
5356
import com.orange.ouds.core.theme.LocalDrawableResources
5457
import com.orange.ouds.core.theme.LocalThemeSettings
@@ -59,6 +62,7 @@ import com.orange.ouds.core.utilities.OudsPreview
5962
import com.orange.ouds.core.utilities.OudsPreviewDevice
6063
import com.orange.ouds.core.utilities.OudsPreviewableComponent
6164
import com.orange.ouds.core.utilities.getPreviewTheme
65+
import com.orange.ouds.foundation.extensions.orElse
6266
import com.orange.ouds.foundation.utilities.BasicPreviewParameterProvider
6367
import com.orange.ouds.theme.OudsThemeContract
6468

@@ -107,6 +111,90 @@ fun OudsAlertMessage(
107111
onClose: (() -> Unit)? = null,
108112
actionLink: OudsAlertMessageActionLink? = null,
109113
bulletList: List<String>? = null
114+
) {
115+
OudsAlertMessage(
116+
label = label,
117+
modifier = modifier,
118+
status = status,
119+
description = description,
120+
annotatedDescription = null,
121+
onClose = onClose,
122+
actionLink = actionLink,
123+
bulletList = bulletList,
124+
annotatedBulletList = null
125+
)
126+
}
127+
128+
/**
129+
* Alert message is a UI element that displays system feedback, status changes or required action; throughout detailed, prominent, persistent and actionable
130+
* communication. Alert message includes functional icon and semantic colour, and may include as well a close button and/or action link.
131+
* Alert Message does not disappear automatically and remains visible until dismissed or resolved by the user.
132+
*
133+
* > Design guidelines: [unified-design-system.orange.com](https://r.orange.fr/r/S-ouds-doc-alert-message)
134+
*
135+
* > Design version: 1.1.0
136+
*
137+
* @param label Label displayed in the alert message. Main message that should be short, clear, and readable at a glance.
138+
* @param modifier [Modifier] applied to the alert message.
139+
* @param status The status of the alert message. Its background color and its icon color are based on this status.
140+
* There are two types of statuses:
141+
* - Non-functional statuses ([OudsAlertMessageStatus.Neutral] or [OudsAlertMessageStatus.Accent]) used for informational or decorative alert messages. They
142+
* provide context or highlight content without implying a specific state, system event, or user action. These alerts are not tied to UX patterns such as
143+
* success, error, or warning, and may use contextual or brand-related icons to enhance recognition or storytelling.
144+
* - Functional statuses communicate specific system statuses, results, or user feedback: [OudsAlertMessageStatus.Positive], [OudsAlertMessageStatus.Warning],
145+
* [OudsAlertMessageStatus.Negative], [OudsAlertMessageStatus.Info].
146+
* Each variant conveys a clear semantic meaning and must always be paired with its dedicated functional icon to ensure clarity and accessibility.
147+
* Use functional alerts to inform user about state changes, confirmations, or issues that are directly connected to system logic or user actions. These
148+
* messages carry functional meaning and help guide user response or acknowledgment.
149+
* @param description Annotated supplementary text in an alert message. Use only when additional detail or guidance is needed beyond the label. It should remain
150+
* short, clear and scannable, helping the user to understand what happened and what he can do next.
151+
* @param onClose Callback invoked when the close button is clicked. If `null`, the close button is not displayed and the alert message remains visible until
152+
* the context changes (e.g., the issue is resolved, the screen is refreshed). Otherwise, the alert message is dismissable and includes a close button,
153+
* allowing the user to dismiss it when he has acknowledged the message.
154+
* Some alerts must remain visible to ensure user is aware of important information; others can be closed to reduce visual clutter.
155+
* @param actionLink An optional link to be displayed in the alert message. It can be used to trigger an action.
156+
* @param bulletList A list of annotated bullet points to be displayed in the alert message following the label or the optional [description].
157+
* Add this list when you need to highlight multiple points, such as service features, plan details, or next steps. Each bullet should be short and written
158+
* as a clear phrase or fragment — avoid long sentences or complex structures.
159+
*
160+
* @sample com.orange.ouds.core.component.samples.OudsAlertMessageSample
161+
*
162+
* @sample com.orange.ouds.core.component.samples.OudsAlertMessageFunctionalWithTopEndActionLinkSample
163+
*/
164+
@Composable
165+
fun OudsAlertMessage(
166+
label: String,
167+
modifier: Modifier = Modifier,
168+
status: OudsAlertMessageStatus = OudsAlertMessageDefaults.Status,
169+
description: OudsAnnotatedAlertMessageDescription,
170+
onClose: (() -> Unit)? = null,
171+
actionLink: OudsAlertMessageActionLink? = null,
172+
bulletList: List<OudsAnnotatedAlertMessageBulletListLabel>?
173+
) {
174+
OudsAlertMessage(
175+
label = label,
176+
modifier = modifier,
177+
status = status,
178+
description = null,
179+
annotatedDescription = description,
180+
onClose = onClose,
181+
actionLink = actionLink,
182+
bulletList = null,
183+
annotatedBulletList = bulletList
184+
)
185+
}
186+
187+
@Composable
188+
private fun OudsAlertMessage(
189+
label: String,
190+
modifier: Modifier = Modifier,
191+
status: OudsAlertMessageStatus = OudsAlertMessageDefaults.Status,
192+
description: String? = null,
193+
annotatedDescription: OudsAnnotatedAlertMessageDescription? = null,
194+
onClose: (() -> Unit)? = null,
195+
actionLink: OudsAlertMessageActionLink? = null,
196+
bulletList: List<String>? = null,
197+
annotatedBulletList: List<OudsAnnotatedAlertMessageBulletListLabel>? = null
110198
) {
111199
with(OudsTheme.componentsTokens.alert) {
112200
val scale = LocalConfiguration.current.fontScale
@@ -150,15 +238,15 @@ fun OudsAlertMessage(
150238
color = status.contentColor,
151239
style = OudsTheme.typography.label.moderate.large
152240
)
153-
description?.let {
154-
Text(
155-
modifier = Modifier.widthIn(max = OudsTheme.sizes.maxWidth.type.label.medium),
156-
text = description,
157-
color = status.contentColor,
158-
style = OudsTheme.typography.label.default.medium
159-
)
241+
val descriptionModifier = Modifier.widthIn(max = OudsTheme.sizes.maxWidth.type.label.medium)
242+
val descriptionText = annotatedDescription?.annotatedString.orElse { description }
243+
val descriptionColor = status.contentColor
244+
val descriptionStyle = OudsTheme.typography.label.default.medium
245+
when (descriptionText) {
246+
is AnnotatedString -> Text(modifier = descriptionModifier, text = descriptionText, color = descriptionColor, style = descriptionStyle)
247+
is String -> Text(modifier = descriptionModifier, text = descriptionText, color = descriptionColor, style = descriptionStyle)
160248
}
161-
bulletList?.let { list ->
249+
annotatedBulletList.orElse { bulletList }?.let { list ->
162250
Column(verticalArrangement = Arrangement.spacedBy(spaceRowGapBullet.value)) {
163251
list.forEach { label ->
164252
if (label.isNotBlank()) OudsAlertMessageBulletListItem(label = label, color = status.contentColor)
@@ -360,7 +448,7 @@ sealed class OudsAlertMessageStatus(internal val value: OudsAlertStatus, val ico
360448
}
361449

362450
@Composable
363-
private fun OudsAlertMessageBulletListItem(label: String, color: Color) {
451+
private fun OudsAlertMessageBulletListItem(label: CharSequence, color: Color) {
364452
val scale = LocalConfiguration.current.fontScale
365453
Row(
366454
modifier = Modifier
@@ -383,15 +471,15 @@ private fun OudsAlertMessageBulletListItem(label: String, color: Color) {
383471
tint = color
384472
)
385473
}
386-
Text(
387-
modifier = Modifier
388-
.fillMaxHeight()
389-
.wrapContentHeight() // Allows to center the text vertically when its height is smaller than the row height
390-
.widthIn(max = OudsTheme.sizes.maxWidth.type.label.medium),
391-
text = label,
392-
style = OudsTheme.typography.label.default.medium,
393-
color = color
394-
)
474+
val modifier = Modifier
475+
.fillMaxHeight()
476+
.wrapContentHeight() // Allows to center the text vertically when its height is smaller than the row height
477+
.widthIn(max = OudsTheme.sizes.maxWidth.type.label.medium)
478+
val style = OudsTheme.typography.label.default.medium
479+
when (label) {
480+
is AnnotatedString -> Text(modifier = modifier, text = label, color = color, style = style)
481+
is String -> Text(modifier = modifier, text = label, color = color, style = style)
482+
}
395483
}
396484
}
397485

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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.common.text
14+
15+
import androidx.compose.ui.text.AnnotatedString
16+
17+
class OudsAnnotatedAlertMessageBulletListLabel internal constructor(annotatedString: AnnotatedString) :
18+
OudsAnnotatedString<OudsAnnotatedAlertMessageBulletListLabel>(annotatedString) {
19+
20+
class Builder(capacity: Int = 16) :
21+
OudsAnnotatedString.Builder<OudsAnnotatedAlertMessageBulletListLabel>(capacity, OudsAnnotatedAlertMessageBulletListLabel::class.java),
22+
StrongBuilder, LinkBuilder {
23+
24+
constructor(text: String) : this() {
25+
append(text)
26+
}
27+
28+
constructor(text: OudsAnnotatedHelperText) : this() {
29+
append(text)
30+
}
31+
32+
override fun addStrong(start: Int, end: Int) = addStrongImpl(start, end)
33+
34+
override fun pushStrong(): Int = pushStrongImpl()
35+
36+
override fun addLink(url: OudsLinkAnnotation.Url, start: Int, end: Int) = addLinkImpl(url, start, end)
37+
38+
override fun addLink(clickable: OudsLinkAnnotation.Clickable, start: Int, end: Int) = addLinkImpl(clickable, start, end)
39+
40+
override fun pushLink(link: OudsLinkAnnotation): Int = pushLinkImpl(link)
41+
}
42+
}
43+
44+
fun buildOudsAnnotatedAlertMessageBulletListLabel(builder: (OudsAnnotatedAlertMessageBulletListLabel.Builder).() -> Unit): OudsAnnotatedAlertMessageBulletListLabel {
45+
return buildOudsAnnotatedString<OudsAnnotatedAlertMessageBulletListLabel, OudsAnnotatedAlertMessageBulletListLabel.Builder>(builder)
46+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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.common.text
14+
15+
import androidx.compose.ui.text.AnnotatedString
16+
17+
class OudsAnnotatedAlertMessageDescription internal constructor(annotatedString: AnnotatedString) :
18+
OudsAnnotatedString<OudsAnnotatedAlertMessageDescription>(annotatedString) {
19+
20+
class Builder(capacity: Int = 16) :
21+
OudsAnnotatedString.Builder<OudsAnnotatedAlertMessageDescription>(capacity, OudsAnnotatedAlertMessageDescription::class.java),
22+
StrongBuilder, LinkBuilder {
23+
24+
constructor(text: String) : this() {
25+
append(text)
26+
}
27+
28+
constructor(text: OudsAnnotatedHelperText) : this() {
29+
append(text)
30+
}
31+
32+
override fun addStrong(start: Int, end: Int) = addStrongImpl(start, end)
33+
34+
override fun pushStrong(): Int = pushStrongImpl()
35+
36+
override fun addLink(url: OudsLinkAnnotation.Url, start: Int, end: Int) = addLinkImpl(url, start, end)
37+
38+
override fun addLink(clickable: OudsLinkAnnotation.Clickable, start: Int, end: Int) = addLinkImpl(clickable, start, end)
39+
40+
override fun pushLink(link: OudsLinkAnnotation): Int = pushLinkImpl(link)
41+
}
42+
}
43+
44+
fun buildOudsAnnotatedAlertMessageString(builder: (OudsAnnotatedAlertMessageDescription.Builder).() -> Unit): OudsAnnotatedAlertMessageDescription {
45+
return buildOudsAnnotatedString<OudsAnnotatedAlertMessageDescription, OudsAnnotatedAlertMessageDescription.Builder>(builder)
46+
}

core/src/main/java/com/orange/ouds/core/component/common/text/OudsAnnotatedErrorMessage.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@ class OudsAnnotatedErrorMessage internal constructor(annotatedString: AnnotatedS
2727
append(text)
2828
}
2929

30-
override fun addStrong(start: Int, end: Int) = addStrongInternal(start, end)
30+
override fun addStrong(start: Int, end: Int) = addStrongImpl(start, end)
3131

32-
override fun pushStrong(): Int = pushStrongInternal()
32+
override fun pushStrong(): Int = pushStrongImpl()
3333
}
3434
}
3535

3636
fun buildOudsAnnotatedErrorMessage(builder: (OudsAnnotatedErrorMessage.Builder).() -> Unit): OudsAnnotatedErrorMessage {
37-
return buildOudsAnnotatedString<OudsAnnotatedErrorMessage.Builder, OudsAnnotatedErrorMessage>(builder)
37+
return buildOudsAnnotatedString<OudsAnnotatedErrorMessage, OudsAnnotatedErrorMessage.Builder>(builder)
3838
}

core/src/main/java/com/orange/ouds/core/component/common/text/OudsAnnotatedHelperText.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,12 @@ class OudsAnnotatedHelperText internal constructor(annotatedString: AnnotatedStr
2626
append(text)
2727
}
2828

29-
override fun addStrong(start: Int, end: Int) = addStrongInternal(start, end)
29+
override fun addStrong(start: Int, end: Int) = addStrongImpl(start, end)
3030

31-
override fun pushStrong(): Int = pushStrongInternal()
31+
override fun pushStrong(): Int = pushStrongImpl()
3232
}
3333
}
3434

3535
fun buildOudsAnnotatedHelperText(builder: (OudsAnnotatedHelperText.Builder).() -> Unit): OudsAnnotatedHelperText {
36-
return buildOudsAnnotatedString<OudsAnnotatedHelperText.Builder, OudsAnnotatedHelperText>(builder)
36+
return buildOudsAnnotatedString<OudsAnnotatedHelperText, OudsAnnotatedHelperText.Builder>(builder)
3737
}

core/src/main/java/com/orange/ouds/core/component/common/text/OudsAnnotatedString.kt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ open class OudsAnnotatedString<T> internal constructor(internal val annotatedStr
5050

5151
open class Builder<T> internal constructor(capacity: Int, private val clazz: Class<T>) : Appendable where T : OudsAnnotatedString<T> {
5252

53-
internal companion object {
53+
private companion object {
5454

5555
val strongStyle = SpanStyle(fontWeight = FontWeight.Bold)
5656
}
@@ -74,21 +74,21 @@ open class OudsAnnotatedString<T> internal constructor(internal val annotatedStr
7474
return constructor(builder.toAnnotatedString())
7575
}
7676

77-
internal fun addStrongInternal(start: Int, end: Int) {
77+
protected fun addStrongImpl(start: Int, end: Int) {
7878
builder.addStyle(strongStyle, start, end)
7979
}
8080

81-
internal fun addLinkInternal(url: OudsLinkAnnotation.Url, start: Int, end: Int) {
81+
protected fun addLinkImpl(url: OudsLinkAnnotation.Url, start: Int, end: Int) {
8282
builder.addLink(url.linkAnnotation, start, end)
8383
}
8484

85-
internal fun addLinkInternal(clickable: OudsLinkAnnotation.Clickable, start: Int, end: Int) {
85+
protected fun addLinkImpl(clickable: OudsLinkAnnotation.Clickable, start: Int, end: Int) {
8686
builder.addLink(clickable.linkAnnotation, start, end)
8787
}
8888

89-
internal fun pushStrongInternal(): Int = builder.pushStyle(strongStyle)
89+
protected fun pushStrongImpl(): Int = builder.pushStyle(strongStyle)
9090

91-
internal fun pushLinkInternal(link: OudsLinkAnnotation): Int = builder.pushLink(link.linkAnnotation)
91+
protected fun pushLinkImpl(link: OudsLinkAnnotation): Int = builder.pushLink(link.linkAnnotation)
9292

9393
fun pop(): Unit = builder.pop()
9494

@@ -153,8 +153,8 @@ inline fun <R : Any> OudsAnnotatedString.LinkBuilder.withLink(link: OudsLinkAnno
153153
}
154154
}
155155

156-
inline fun <reified T, U> buildOudsAnnotatedString(noinline builder: (T).() -> Unit): U where T : OudsAnnotatedString.Builder<U>, U : OudsAnnotatedString<U> {
157-
return buildOudsAnnotatedString(T::class.java, builder)
156+
inline fun <T, reified U> buildOudsAnnotatedString(noinline builder: (U).() -> Unit): T where U : OudsAnnotatedString.Builder<T>, T : OudsAnnotatedString<T> {
157+
return buildOudsAnnotatedString(U::class.java, builder)
158158
}
159159

160160
@PublishedApi

0 commit comments

Comments
 (0)