Skip to content

Commit a82800b

Browse files
florentmaitrepaulinea
authored andcommitted
feat(top-app-bar): add the option to add a badge on an icon in the top app bar (#969)
* Add OudsComponentIconBadge and an icon badge parameter in OudsButton * Add snapshot tests for OudsButton with icon badge * Add OudsBadgedIcon and use it in OudsNavigationBarItem * Add snapshot tests for OudsBadgedIcon * Add OudsTopAppBarActionBadge * Add top app bar action badge to the demo screen * Add KDoc to OudsBadgedIcon * Fix accessibility on OudsBadgedIcon * Various fixes * Fix layout of standard OudsBadgedIcon in RTL * Review: Remove guard condition in when * Review: Change appearance of button to minimal in icon badge preview
1 parent 2ea241a commit a82800b

File tree

70 files changed

+541
-130
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+541
-130
lines changed

app/src/main/java/com/orange/ouds/app/ui/components/ComponentCode.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
package com.orange.ouds.app.ui.components
1414

1515
import androidx.annotation.DrawableRes
16+
import androidx.annotation.PluralsRes
1617
import androidx.annotation.StringRes
1718
import androidx.compose.ui.graphics.Color
1819
import androidx.compose.ui.graphics.toArgb
@@ -49,7 +50,11 @@ fun FunctionCall.Builder.stringArgument(name: String, @StringRes id: Int) = form
4950

5051
fun FunctionCall.Builder.contentDescriptionArgument(@StringRes id: Int) = stringArgument(Argument.ContentDescription, id)
5152
fun FunctionCall.Builder.contentDescriptionArgument(@StringRes id: Int, vararg formatArgs: Any) =
52-
stringResourceArgument(Argument.ContentDescription, id, formatArgs)
53+
stringResourceArgument(Argument.ContentDescription, id, *formatArgs)
54+
55+
fun FunctionCall.Builder.contentDescriptionArgument(@PluralsRes id: Int, count: Int, vararg formatArgs: Any) =
56+
pluralStringResourceArgument(Argument.ContentDescription, id, count, *formatArgs)
57+
5358

5459
fun FunctionCall.Builder.enabledArgument(value: Boolean) = typedArgument(Argument.Enabled, value)
5560

app/src/main/java/com/orange/ouds/app/ui/components/navigationbar/NavigationBarDemoScreen.kt

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import androidx.compose.ui.res.stringResource
2424
import androidx.compose.ui.tooling.preview.PreviewLightDark
2525
import com.orange.ouds.app.R
2626
import com.orange.ouds.app.ui.components.Component
27+
import com.orange.ouds.app.ui.components.contentDescriptionArgument
2728
import com.orange.ouds.app.ui.components.labelArgument
2829
import com.orange.ouds.app.ui.components.navigationbar.NavigationBarDemoState.Companion.ItemBadgeCount
2930
import com.orange.ouds.app.ui.components.navigationbar.NavigationBarDemoState.Companion.MaxNavigationBarItemCount
@@ -88,9 +89,6 @@ private fun NavigationBarDemoContent(state: NavigationBarDemoState) {
8889
items = navigationBarItems.take(itemCount).mapIndexed { index, item ->
8990
val label = stringResource(id = item.labelRes)
9091
val isLastItem = index == itemCount - 1
91-
val standardBadgeContentDescription = stringResource(id = R.string.app_components_common_unreadNotificationsBadge_a11y)
92-
val countBadgeContentDescription =
93-
pluralStringResource(id = R.plurals.app_components_common_unreadMessageCountBadge_a11y, count = ItemBadgeCount, ItemBadgeCount)
9492
OudsNavigationBarItem(
9593
selected = selectedItemId == index,
9694
onClick = { selectedItemId = index },
@@ -99,9 +97,9 @@ private fun NavigationBarDemoContent(state: NavigationBarDemoState) {
9997
badge = if (isLastItem) {
10098
when (lastItemBadge) {
10199
NavigationBarDemoState.ItemBadge.None -> null
102-
NavigationBarDemoState.ItemBadge.Standard -> OudsNavigationBarItemBadge(standardBadgeContentDescription)
100+
NavigationBarDemoState.ItemBadge.Standard -> OudsNavigationBarItemBadge(stringResource(id = R.string.app_components_common_unreadNotificationsBadge_a11y))
103101
NavigationBarDemoState.ItemBadge.Count -> OudsNavigationBarItemBadge(
104-
contentDescription = countBadgeContentDescription,
102+
contentDescription = pluralStringResource(id = R.plurals.app_components_common_unreadMessageCountBadge_a11y, count = ItemBadgeCount, ItemBadgeCount),
105103
count = ItemBadgeCount
106104
)
107105
}
@@ -131,6 +129,15 @@ private fun Code.Builder.navigationBarDemoCodeSnippet(state: NavigationBarDemoSt
131129
}
132130
if (isLastItem && lastItemBadge != NavigationBarDemoState.ItemBadge.None) {
133131
functionCallArgument("badge", OudsNavigationBarItemBadge::class.simpleName.orEmpty()) {
132+
when (lastItemBadge) {
133+
NavigationBarDemoState.ItemBadge.None -> {}
134+
NavigationBarDemoState.ItemBadge.Standard -> contentDescriptionArgument(id = R.string.app_components_common_unreadNotificationsBadge_a11y)
135+
NavigationBarDemoState.ItemBadge.Count -> contentDescriptionArgument(
136+
id = R.plurals.app_components_common_unreadMessageCountBadge_a11y,
137+
count = ItemBadgeCount,
138+
ItemBadgeCount
139+
)
140+
}
134141
if (lastItemBadge == NavigationBarDemoState.ItemBadge.Count) {
135142
typedArgument("count", ItemBadgeCount)
136143
}

app/src/main/java/com/orange/ouds/app/ui/components/topappbar/TopAppBarDemoScreen.kt

Lines changed: 66 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@
1212

1313
package com.orange.ouds.app.ui.components.topappbar
1414

15+
import android.R.attr.label
1516
import androidx.compose.foundation.layout.PaddingValues
1617
import androidx.compose.material3.ExperimentalMaterial3Api
1718
import androidx.compose.runtime.Composable
1819
import androidx.compose.ui.graphics.Color
1920
import androidx.compose.ui.res.painterResource
21+
import androidx.compose.ui.res.pluralStringResource
2022
import androidx.compose.ui.res.stringResource
2123
import androidx.compose.ui.tooling.preview.PreviewLightDark
2224
import com.orange.ouds.app.R
@@ -25,6 +27,7 @@ import com.orange.ouds.app.ui.components.colorArgument
2527
import com.orange.ouds.app.ui.components.contentDescriptionArgument
2628
import com.orange.ouds.app.ui.components.onClickArgument
2729
import com.orange.ouds.app.ui.components.painterArgument
30+
import com.orange.ouds.app.ui.components.topappbar.TopAppBarDemoState.Companion.ActionIconBadgeCount
2831
import com.orange.ouds.app.ui.utilities.Code
2932
import com.orange.ouds.app.ui.utilities.LocalThemeDrawableResources
3033
import com.orange.ouds.app.ui.utilities.ThemeDrawableResources
@@ -39,6 +42,7 @@ import com.orange.ouds.core.component.OudsLargeTopAppBar
3942
import com.orange.ouds.core.component.OudsMediumTopAppBar
4043
import com.orange.ouds.core.component.OudsTopAppBar
4144
import com.orange.ouds.core.component.OudsTopAppBarAction
45+
import com.orange.ouds.core.component.OudsTopAppBarActionBadge
4246
import com.orange.ouds.core.component.OudsTopAppBarNavigationIcon
4347
import com.orange.ouds.core.theme.OudsTheme
4448
import com.orange.ouds.foundation.extensions.orElse
@@ -89,17 +93,24 @@ private fun TopAppBarDemoBottomSheetContent(state: TopAppBarDemoState) {
8993
)
9094
CustomizationFilterChips(
9195
applyTopPadding = true,
92-
label = stringResource(R.string.app_components_topAppBar_avatar_label),
93-
chipLabels = TopAppBarDemoState.Avatar.entries.map { stringResource(it.labelRes) },
94-
selectedChipIndex = TopAppBarDemoState.Avatar.entries.indexOf(avatar),
95-
onSelectionChange = { id -> avatar = TopAppBarDemoState.Avatar.entries[id] }
96+
label = stringResource(R.string.app_components_topAppBar_actionIconBadge_label),
97+
chipLabels = TopAppBarDemoState.ActionIconBadge.entries.map { it.name },
98+
selectedChipIndex = TopAppBarDemoState.ActionIconBadge.entries.indexOf(actionIconBadge),
99+
onSelectionChange = { id -> actionIconBadge = TopAppBarDemoState.ActionIconBadge.entries[id] }
100+
)
101+
CustomizationFilterChips(
102+
applyTopPadding = true,
103+
label = stringResource(R.string.app_components_topAppBar_actionAvatar_label),
104+
chipLabels = TopAppBarDemoState.ActionAvatar.entries.map { stringResource(it.labelRes) },
105+
selectedChipIndex = TopAppBarDemoState.ActionAvatar.entries.indexOf(actionAvatar),
106+
onSelectionChange = { id -> actionAvatar = TopAppBarDemoState.ActionAvatar.entries[id] }
96107
)
97108
CustomizationTextField(
98109
applyTopPadding = true,
99-
label = stringResource(R.string.app_components_topAppBar_avatarMonogram_label),
100-
value = avatarMonogram.toString().trim(),
101-
onValueChange = { value -> avatarMonogram = value.firstOrNull().orElse { ' ' } },
102-
enabled = avatarMonogramTextFieldEnabled
110+
label = stringResource(R.string.app_components_topAppBar_actionAvatarMonogram_label),
111+
value = actionAvatarMonogram.toString().trim(),
112+
onValueChange = { value -> actionAvatarMonogram = value.firstOrNull().orElse { ' ' } },
113+
enabled = actionAvatarMonogramTextFieldEnabled
103114
)
104115
}
105116
}
@@ -120,29 +131,41 @@ private fun TopAppBarDemoContent(state: TopAppBarDemoState) {
120131
onClick = {}
121132
)
122133
}
123-
val avatarContentDescription = stringResource(R.string.app_components_topAppBar_secondAction_a11y)
124-
val avatarAction = when (avatar) {
125-
TopAppBarDemoState.Avatar.Image -> OudsTopAppBarAction.Avatar(
134+
135+
val firstAction = OudsTopAppBarAction.Icon(
136+
painter = painterResource(id = LocalThemeDrawableResources.current.tipsAndTricks),
137+
contentDescription = stringResource(R.string.app_components_topAppBar_firstAction_a11y),
138+
badge = when (actionIconBadge) {
139+
TopAppBarDemoState.ActionIconBadge.None -> null
140+
TopAppBarDemoState.ActionIconBadge.Standard -> OudsTopAppBarActionBadge(contentDescription = stringResource(id = R.string.app_components_common_unreadNotificationsBadge_a11y))
141+
TopAppBarDemoState.ActionIconBadge.Count -> OudsTopAppBarActionBadge(
142+
contentDescription = pluralStringResource(
143+
id = R.plurals.app_components_common_unreadMessageCountBadge_a11y,
144+
count = ActionIconBadgeCount,
145+
ActionIconBadgeCount
146+
),
147+
count = ActionIconBadgeCount
148+
)
149+
},
150+
onClick = {}
151+
)
152+
val secondActionContentDescription = stringResource(R.string.app_components_topAppBar_secondAction_a11y)
153+
val secondAction = when (actionAvatar) {
154+
TopAppBarDemoState.ActionAvatar.Image -> OudsTopAppBarAction.Avatar(
126155
painter = painterResource(id = R.drawable.il_top_app_bar_avatar),
127-
contentDescription = avatarContentDescription,
156+
contentDescription = secondActionContentDescription,
128157
onClick = {}
129158
)
130-
TopAppBarDemoState.Avatar.Monogram -> OudsTopAppBarAction.Avatar(
131-
monogram = avatarMonogram,
159+
TopAppBarDemoState.ActionAvatar.Monogram -> OudsTopAppBarAction.Avatar(
160+
monogram = actionAvatarMonogram,
132161
color = Color.White,
133162
backgroundColor = Color(0xff138126),
134-
contentDescription = avatarContentDescription,
163+
contentDescription = secondActionContentDescription,
135164
onClick = {}
136165
)
137166
}
138-
val actions = listOf(
139-
OudsTopAppBarAction.Icon(
140-
painter = painterResource(id = LocalThemeDrawableResources.current.tipsAndTricks),
141-
contentDescription = stringResource(R.string.app_components_topAppBar_firstAction_a11y),
142-
onClick = {}
143-
),
144-
avatarAction
145-
)
167+
val actions = listOf(firstAction, secondAction)
168+
146169
when (size) {
147170
TopAppBarDemoState.Size.Small -> {
148171
if (centerAligned) {
@@ -207,15 +230,31 @@ private fun Code.Builder.topAppBarDemoCodeSnippet(state: TopAppBarDemoState, the
207230
constructorCallArgument<OudsTopAppBarAction.Icon>(null) {
208231
painterArgument(themeDrawableResources.tipsAndTricks)
209232
contentDescriptionArgument(R.string.app_components_topAppBar_firstAction_a11y)
233+
if (actionIconBadge != TopAppBarDemoState.ActionIconBadge.None) {
234+
functionCallArgument("badge", OudsTopAppBarActionBadge::class.simpleName.orEmpty()) {
235+
when (actionIconBadge) {
236+
TopAppBarDemoState.ActionIconBadge.None -> {}
237+
TopAppBarDemoState.ActionIconBadge.Standard -> contentDescriptionArgument(id = R.string.app_components_common_unreadNotificationsBadge_a11y)
238+
TopAppBarDemoState.ActionIconBadge.Count -> contentDescriptionArgument(
239+
id = R.plurals.app_components_common_unreadMessageCountBadge_a11y,
240+
count = ActionIconBadgeCount,
241+
ActionIconBadgeCount
242+
)
243+
}
244+
if (actionIconBadge == TopAppBarDemoState.ActionIconBadge.Count) {
245+
typedArgument("count", ActionIconBadgeCount)
246+
}
247+
}
248+
}
210249
onClickArgument()
211250
}
212251
constructorCallArgument<OudsTopAppBarAction.Avatar>(null) {
213-
when (avatar) {
214-
TopAppBarDemoState.Avatar.Image -> {
252+
when (actionAvatar) {
253+
TopAppBarDemoState.ActionAvatar.Image -> {
215254
painterArgument(R.drawable.il_top_app_bar_avatar)
216255
}
217-
TopAppBarDemoState.Avatar.Monogram -> {
218-
typedArgument("monogram", avatarMonogram)
256+
TopAppBarDemoState.ActionAvatar.Monogram -> {
257+
typedArgument("monogram", actionAvatarMonogram)
219258
colorArgument("color", Color.White)
220259
colorArgument("backgroundColor", Color(0xff138126))
221260
}

app/src/main/java/com/orange/ouds/app/ui/components/topappbar/TopAppBarDemoState.kt

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,22 +27,26 @@ fun rememberTopAppBarDemoState(
2727
centerAligned: Boolean = false,
2828
navigationIcon: TopAppBarDemoState.NavigationIcon = TopAppBarDemoState.NavigationIcon.None,
2929
title: String = "Title",
30-
avatar: TopAppBarDemoState.Avatar = TopAppBarDemoState.Avatar.Image,
31-
avatarMonogram: Char = 'A'
32-
) = rememberSaveable(size, centerAligned, navigationIcon, title, avatar, avatarMonogram, saver = TopAppBarDemoState.Saver) {
33-
TopAppBarDemoState(size, centerAligned, navigationIcon, title, avatar, avatarMonogram)
30+
actionIconBadge: TopAppBarDemoState.ActionIconBadge = TopAppBarDemoState.ActionIconBadge.None,
31+
actionAvatar: TopAppBarDemoState.ActionAvatar = TopAppBarDemoState.ActionAvatar.Image,
32+
actionAvatarMonogram: Char = 'A'
33+
) = rememberSaveable(size, centerAligned, navigationIcon, title, actionIconBadge, actionAvatar, actionAvatarMonogram, saver = TopAppBarDemoState.Saver) {
34+
TopAppBarDemoState(size, centerAligned, navigationIcon, title, actionIconBadge, actionAvatar, actionAvatarMonogram)
3435
}
3536

3637
class TopAppBarDemoState(
3738
size: Size,
3839
centerAligned: Boolean,
3940
navigationIcon: NavigationIcon,
4041
title: String,
41-
avatar: Avatar,
42-
avatarMonogram: Char
42+
actionIconBadge: ActionIconBadge,
43+
actionAvatar: ActionAvatar,
44+
actionAvatarMonogram: Char
4345
) {
4446
companion object {
4547

48+
const val ActionIconBadgeCount = 1
49+
4650
val Saver = listSaver(
4751
save = { state ->
4852
with(state) {
@@ -51,8 +55,9 @@ class TopAppBarDemoState(
5155
centerAligned,
5256
navigationIcon,
5357
title,
54-
avatar,
55-
avatarMonogram
58+
actionIconBadge,
59+
actionAvatar,
60+
actionAvatarMonogram
5661
)
5762
}
5863
},
@@ -62,8 +67,9 @@ class TopAppBarDemoState(
6267
list[1] as Boolean,
6368
list[2] as NavigationIcon,
6469
list[3] as String,
65-
list[4] as Avatar,
66-
list[5] as Char
70+
list[4] as ActionIconBadge,
71+
list[5] as ActionAvatar,
72+
list[6] as Char
6773
)
6874
}
6975
)
@@ -85,32 +91,40 @@ class TopAppBarDemoState(
8591

8692
var title: String by mutableStateOf(title)
8793

88-
var avatar: Avatar by mutableStateOf(avatar)
94+
var actionIconBadge: ActionIconBadge by mutableStateOf(actionIconBadge)
95+
96+
var actionAvatar: ActionAvatar by mutableStateOf(actionAvatar)
8997

90-
var avatarMonogram: Char by mutableStateOf(avatarMonogram)
98+
var actionAvatarMonogram: Char by mutableStateOf(actionAvatarMonogram)
9199

92100
val centerAlignedSwitchEnabled: Boolean
93101
get() = size == Size.Small
94102

95-
val avatarMonogramTextFieldEnabled: Boolean
96-
get() = avatar == Avatar.Monogram
103+
val actionAvatarMonogramTextFieldEnabled: Boolean
104+
get() = actionAvatar == ActionAvatar.Monogram
97105

98106
enum class Size(@StringRes val labelRes: Int) {
99107
Small(R.string.app_components_topAppBar_smallSize_label),
100108
Medium(R.string.app_components_topAppBar_mediumSize_label),
101109
Large(R.string.app_components_topAppBar_largeSize_label)
102110
}
103111

104-
enum class Avatar(@StringRes val labelRes: Int) {
105-
Image(R.string.app_components_topAppBar_imageAvatar_label),
106-
Monogram(R.string.app_components_topAppBar_monogramAvatar_label)
107-
}
108-
109112
enum class NavigationIcon(@StringRes val labelRes: Int) {
110113
None(R.string.app_components_common_none_label),
111114
Back(R.string.app_components_topAppBar_backNavigationIcon_label),
112115
Close(R.string.app_components_topAppBar_closeNavigationIcon_label),
113116
Menu(R.string.app_components_topAppBar_menuNavigationIcon_label),
114117
Custom(R.string.app_components_topAppBar_customNavigationIcon_label)
115118
}
119+
120+
enum class ActionIconBadge(@StringRes val labelRes: Int) {
121+
None(R.string.app_components_common_none_label),
122+
Standard(R.string.app_components_badge_standardType_label),
123+
Count(R.string.app_components_badge_countType_label)
124+
}
125+
126+
enum class ActionAvatar(@StringRes val labelRes: Int) {
127+
Image(R.string.app_components_topAppBar_imageActionAvatar_label),
128+
Monogram(R.string.app_components_topAppBar_monogramActionAvatar_label)
129+
}
116130
}

0 commit comments

Comments
 (0)