Skip to content

Commit 13b3c6c

Browse files
committed
Add snackbar variants and leading content to StreamSnackbar.
- Introduced `StreamSnackbarVariant` (Default, Error, Success, Loading) and `StreamSnackbarVisuals`. - Updated `StreamSnackbar` to render leading icons or a loading indicator based on the variant. - Added new previews and Paparazzi snapshot tests for all snackbar variants. - Updated `StreamSnackbarTest` to use landscape orientation for snapshots.
1 parent bc1a229 commit 13b3c6c

9 files changed

Lines changed: 185 additions & 29 deletions

stream-chat-android-compose/api/stream-chat-android-compose.api

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6280,9 +6280,12 @@ public final class io/getstream/chat/android/compose/ui/util/ComposableSingleton
62806280
public final class io/getstream/chat/android/compose/ui/util/ComposableSingletons$StreamSnackbarKt {
62816281
public static final field INSTANCE Lio/getstream/chat/android/compose/ui/util/ComposableSingletons$StreamSnackbarKt;
62826282
public fun <init> ()V
6283+
public final fun getLambda$-1718309156$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2;
62836284
public final fun getLambda$-269638538$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function3;
62846285
public final fun getLambda$-678058255$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2;
6285-
public final fun getLambda$-695861522$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2;
6286+
public final fun getLambda$-784221929$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2;
6287+
public final fun getLambda$-853186653$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2;
6288+
public final fun getLambda$2015419422$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2;
62866289
}
62876290

62886291
public final class io/getstream/chat/android/compose/ui/util/DefaultReactionResolver : io/getstream/chat/android/compose/ui/util/ReactionResolver {

stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/util/StreamSnackbar.kt

Lines changed: 154 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ import androidx.compose.foundation.layout.Arrangement
2020
import androidx.compose.foundation.layout.Box
2121
import androidx.compose.foundation.layout.Row
2222
import androidx.compose.foundation.layout.padding
23+
import androidx.compose.foundation.layout.size
2324
import androidx.compose.foundation.shape.RoundedCornerShape
25+
import androidx.compose.material3.CircularProgressIndicator
26+
import androidx.compose.material3.Icon
2427
import androidx.compose.material3.SnackbarData
2528
import androidx.compose.material3.SnackbarDuration
2629
import androidx.compose.material3.SnackbarHost
@@ -32,8 +35,10 @@ import androidx.compose.runtime.Composable
3235
import androidx.compose.ui.Alignment
3336
import androidx.compose.ui.Modifier
3437
import androidx.compose.ui.draw.shadow
38+
import androidx.compose.ui.res.painterResource
3539
import androidx.compose.ui.tooling.preview.Preview
3640
import androidx.compose.ui.unit.dp
41+
import io.getstream.chat.android.compose.R
3742
import io.getstream.chat.android.compose.ui.components.button.StreamButtonSize
3843
import io.getstream.chat.android.compose.ui.components.button.StreamButtonStyleDefaults
3944
import io.getstream.chat.android.compose.ui.components.button.StreamTextButton
@@ -42,9 +47,34 @@ import io.getstream.chat.android.compose.ui.theme.ChatTheme
4247
import io.getstream.chat.android.compose.ui.theme.StreamTokens
4348

4449
/**
45-
* Stream-styled snackbar that renders a message and an optional action button.
50+
* Visual styles a [StreamSnackbar] can render in. Selected via [StreamSnackbarVisuals.variant] when
51+
* pushing a snackbar onto a [SnackbarHostState].
52+
*/
53+
internal enum class StreamSnackbarVariant {
54+
Default,
55+
Error,
56+
Success,
57+
Loading,
58+
}
59+
60+
/**
61+
* [SnackbarVisuals] carrying a [StreamSnackbarVariant]. Pass to
62+
* [SnackbarHostState.showSnackbar] so [StreamSnackbar] can render the matching leading element
63+
* (e.g. exclamation icon for [StreamSnackbarVariant.Error]).
64+
*/
65+
internal data class StreamSnackbarVisuals(
66+
override val message: String,
67+
override val actionLabel: String? = null,
68+
override val withDismissAction: Boolean = false,
69+
override val duration: SnackbarDuration = SnackbarDuration.Short,
70+
val variant: StreamSnackbarVariant = StreamSnackbarVariant.Default,
71+
) : SnackbarVisuals
72+
73+
/**
74+
* Stream-styled snackbar that renders a message, an optional leading element (based on the
75+
* snackbar's variant), and an optional action button.
4676
*
47-
* @param snackbarData The [SnackbarData] driving the message and optional action.
77+
* @param snackbarData The [SnackbarData] driving the message, variant, and optional action.
4878
* @param modifier Modifier applied to the inner [Surface] (e.g. to control width or padding).
4979
*/
5080
@Composable
@@ -53,6 +83,8 @@ internal fun StreamSnackbar(
5383
modifier: Modifier = Modifier,
5484
) {
5585
val actionLabel = snackbarData.visuals.actionLabel
86+
val variant = (snackbarData.visuals as? StreamSnackbarVisuals)?.variant
87+
?: StreamSnackbarVariant.Default
5688
Box(modifier = Modifier.padding(StreamTokens.spacingMd)) {
5789
Surface(
5890
modifier = modifier.shadow(4.dp, shape = SnackbarShape),
@@ -68,15 +100,10 @@ internal fun StreamSnackbar(
68100
horizontalArrangement = Arrangement.spacedBy(StreamTokens.spacing2xs),
69101
verticalAlignment = Alignment.CenterVertically,
70102
) {
71-
Text(
72-
modifier = Modifier
73-
.weight(1f, fill = false)
74-
.padding(
75-
horizontal = StreamTokens.spacingXs,
76-
vertical = StreamTokens.spacingSm,
77-
),
78-
text = snackbarData.visuals.message,
79-
style = ChatTheme.typography.bodyDefault,
103+
SnackbarContent(
104+
modifier = Modifier.weight(1f, fill = false),
105+
variant = variant,
106+
message = snackbarData.visuals.message,
80107
)
81108
if (actionLabel != null) {
82109
StreamTextButton(
@@ -93,6 +120,56 @@ internal fun StreamSnackbar(
93120
}
94121
}
95122

123+
@Composable
124+
private fun SnackbarContent(
125+
modifier: Modifier,
126+
variant: StreamSnackbarVariant,
127+
message: String,
128+
) {
129+
Row(
130+
modifier = modifier.padding(
131+
start = if (variant == StreamSnackbarVariant.Default) {
132+
StreamTokens.spacingXs
133+
} else {
134+
StreamTokens.spacing2xs
135+
},
136+
end = StreamTokens.spacingXs,
137+
),
138+
horizontalArrangement = Arrangement.spacedBy(StreamTokens.spacingXs),
139+
verticalAlignment = Alignment.CenterVertically,
140+
) {
141+
SnackbarLeadingContent(variant = variant)
142+
Text(
143+
modifier = Modifier.padding(vertical = StreamTokens.spacingSm),
144+
text = message,
145+
style = ChatTheme.typography.bodyDefault,
146+
)
147+
}
148+
}
149+
150+
@Composable
151+
private fun SnackbarLeadingContent(variant: StreamSnackbarVariant) {
152+
when (variant) {
153+
StreamSnackbarVariant.Default -> Unit
154+
StreamSnackbarVariant.Error -> Icon(
155+
painter = painterResource(id = R.drawable.stream_design_ic_exclamation_circle_fill),
156+
contentDescription = null,
157+
tint = ChatTheme.colors.textOnInverse,
158+
)
159+
StreamSnackbarVariant.Success -> Icon(
160+
painter = painterResource(id = R.drawable.stream_design_ic_checkmark),
161+
contentDescription = null,
162+
tint = ChatTheme.colors.textOnInverse,
163+
)
164+
StreamSnackbarVariant.Loading -> CircularProgressIndicator(
165+
modifier = Modifier.size(20.dp),
166+
strokeWidth = 2.dp,
167+
trackColor = ChatTheme.colors.textOnInverse.copy(alpha = .35f),
168+
color = ChatTheme.colors.textOnInverse,
169+
)
170+
}
171+
}
172+
96173
private val SnackbarShape = RoundedCornerShape(StreamTokens.radius3xl)
97174

98175
/**
@@ -123,26 +200,19 @@ private class PreviewSnackbarData(
123200
override fun dismiss() = Unit
124201
}
125202

126-
private class PreviewSnackbarVisuals(
127-
override val message: String,
128-
override val actionLabel: String? = null,
129-
override val withDismissAction: Boolean = false,
130-
override val duration: SnackbarDuration = SnackbarDuration.Short,
131-
) : SnackbarVisuals
132-
133203
@Preview(showBackground = true)
134204
@Composable
135-
private fun StreamSnackbarMessageOnlyPreview() {
205+
private fun StreamSnackbarDefaultPreview() {
136206
ChatPreviewTheme {
137-
StreamSnackbarMessageOnly()
207+
StreamSnackbarDefault()
138208
}
139209
}
140210

141211
@Composable
142-
internal fun StreamSnackbarMessageOnly() {
212+
internal fun StreamSnackbarDefault() {
143213
StreamSnackbar(
144214
snackbarData = PreviewSnackbarData(
145-
visuals = PreviewSnackbarVisuals(message = "This is a snackbar message"),
215+
visuals = StreamSnackbarVisuals(message = "This is a snackbar message"),
146216
),
147217
)
148218
}
@@ -159,9 +229,70 @@ private fun StreamSnackbarWithActionPreview() {
159229
internal fun StreamSnackbarWithAction() {
160230
StreamSnackbar(
161231
snackbarData = PreviewSnackbarData(
162-
visuals = PreviewSnackbarVisuals(
232+
visuals = StreamSnackbarVisuals(
163233
message = "Something went wrong",
164234
actionLabel = "Retry",
235+
variant = StreamSnackbarVariant.Error,
236+
),
237+
),
238+
)
239+
}
240+
241+
@Preview(showBackground = true)
242+
@Composable
243+
private fun StreamSnackbarErrorPreview() {
244+
ChatPreviewTheme {
245+
StreamSnackbarError()
246+
}
247+
}
248+
249+
@Composable
250+
internal fun StreamSnackbarError() {
251+
StreamSnackbar(
252+
snackbarData = PreviewSnackbarData(
253+
visuals = StreamSnackbarVisuals(
254+
message = "Not available while editing",
255+
variant = StreamSnackbarVariant.Error,
256+
),
257+
),
258+
)
259+
}
260+
261+
@Preview(showBackground = true)
262+
@Composable
263+
private fun StreamSnackbarSuccessPreview() {
264+
ChatPreviewTheme {
265+
StreamSnackbarSuccess()
266+
}
267+
}
268+
269+
@Composable
270+
internal fun StreamSnackbarSuccess() {
271+
StreamSnackbar(
272+
snackbarData = PreviewSnackbarData(
273+
visuals = StreamSnackbarVisuals(
274+
message = "Message sent",
275+
variant = StreamSnackbarVariant.Success,
276+
),
277+
),
278+
)
279+
}
280+
281+
@Preview(showBackground = true)
282+
@Composable
283+
private fun StreamSnackbarLoadingPreview() {
284+
ChatPreviewTheme {
285+
StreamSnackbarLoading()
286+
}
287+
}
288+
289+
@Composable
290+
internal fun StreamSnackbarLoading() {
291+
StreamSnackbar(
292+
snackbarData = PreviewSnackbarData(
293+
visuals = StreamSnackbarVisuals(
294+
message = "Uploading attachment",
295+
variant = StreamSnackbarVariant.Loading,
165296
),
166297
),
167298
)

stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/util/StreamSnackbarTest.kt

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package io.getstream.chat.android.compose.ui.util
1919
import app.cash.paparazzi.DeviceConfig
2020
import app.cash.paparazzi.Paparazzi
2121
import com.android.ide.common.rendering.api.SessionParams
22+
import com.android.resources.ScreenOrientation
2223
import io.getstream.chat.android.compose.ui.PaparazziComposeTest
2324
import org.junit.Rule
2425
import org.junit.Test
@@ -27,21 +28,42 @@ internal class StreamSnackbarTest : PaparazziComposeTest {
2728

2829
@get:Rule
2930
override val paparazzi = Paparazzi(
30-
deviceConfig = DeviceConfig.PIXEL_2,
31+
deviceConfig = DeviceConfig.PIXEL_2.copy(orientation = ScreenOrientation.LANDSCAPE),
3132
renderingMode = SessionParams.RenderingMode.SHRINK,
3233
)
3334

3435
@Test
35-
fun `snackbar message only`() {
36-
snapshotWithDarkMode {
37-
StreamSnackbarMessageOnly()
36+
fun `snackbar default`() {
37+
snapshotWithDarkModeRow {
38+
StreamSnackbarDefault()
3839
}
3940
}
4041

4142
@Test
4243
fun `snackbar with action`() {
43-
snapshotWithDarkMode {
44+
snapshotWithDarkModeRow {
4445
StreamSnackbarWithAction()
4546
}
4647
}
48+
49+
@Test
50+
fun `snackbar error`() {
51+
snapshotWithDarkModeRow {
52+
StreamSnackbarError()
53+
}
54+
}
55+
56+
@Test
57+
fun `snackbar success`() {
58+
snapshotWithDarkModeRow {
59+
StreamSnackbarSuccess()
60+
}
61+
}
62+
63+
@Test
64+
fun `snackbar loading`() {
65+
snapshotWithDarkModeRow {
66+
StreamSnackbarLoading()
67+
}
68+
}
4769
}
Loading
Loading
Loading
Loading
Loading

0 commit comments

Comments
 (0)