@@ -20,7 +20,10 @@ import androidx.compose.foundation.layout.Arrangement
2020import androidx.compose.foundation.layout.Box
2121import androidx.compose.foundation.layout.Row
2222import androidx.compose.foundation.layout.padding
23+ import androidx.compose.foundation.layout.size
2324import androidx.compose.foundation.shape.RoundedCornerShape
25+ import androidx.compose.material3.CircularProgressIndicator
26+ import androidx.compose.material3.Icon
2427import androidx.compose.material3.SnackbarData
2528import androidx.compose.material3.SnackbarDuration
2629import androidx.compose.material3.SnackbarHost
@@ -32,8 +35,10 @@ import androidx.compose.runtime.Composable
3235import androidx.compose.ui.Alignment
3336import androidx.compose.ui.Modifier
3437import androidx.compose.ui.draw.shadow
38+ import androidx.compose.ui.res.painterResource
3539import androidx.compose.ui.tooling.preview.Preview
3640import androidx.compose.ui.unit.dp
41+ import io.getstream.chat.android.compose.R
3742import io.getstream.chat.android.compose.ui.components.button.StreamButtonSize
3843import io.getstream.chat.android.compose.ui.components.button.StreamButtonStyleDefaults
3944import io.getstream.chat.android.compose.ui.components.button.StreamTextButton
@@ -42,9 +47,34 @@ import io.getstream.chat.android.compose.ui.theme.ChatTheme
4247import 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+
96173private 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() {
159229internal 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 )
0 commit comments