From 306f606bde5156ad46d8d0890946a6284dd3a942 Mon Sep 17 00:00:00 2001 From: Pranjal Singh Date: Thu, 26 Feb 2026 19:10:59 +0530 Subject: [PATCH 1/6] Adding callback mechanism for Stackable Snackbar --- .../V2StackableSnackbarActivity.kt | 5 +- .../notification/StackableSnackbar.kt | 60 +++++++++++-------- 2 files changed, 39 insertions(+), 26 deletions(-) diff --git a/FluentUI.Demo/src/main/java/com/microsoft/fluentuidemo/V2StackableSnackbarActivity.kt b/FluentUI.Demo/src/main/java/com/microsoft/fluentuidemo/V2StackableSnackbarActivity.kt index bba170f9e..5ab567e71 100644 --- a/FluentUI.Demo/src/main/java/com/microsoft/fluentuidemo/V2StackableSnackbarActivity.kt +++ b/FluentUI.Demo/src/main/java/com/microsoft/fluentuidemo/V2StackableSnackbarActivity.kt @@ -108,7 +108,10 @@ fun SnackBarStackDemoLayout(context: V2StackableSnackbarActivity) { state = stackState, snackBarStackConfig = SnackBarStackConfig( snackbarGapWhenExpanded = 10.dp - ) + ), + getTrailingIconBasedOnOverflow = { + null + } ) Spacer(modifier = Modifier.height(10.dp)) Row() { diff --git a/fluentui_notification/src/main/java/com/microsoft/fluentui/tokenized/notification/StackableSnackbar.kt b/fluentui_notification/src/main/java/com/microsoft/fluentui/tokenized/notification/StackableSnackbar.kt index 4c9b5fb7c..a1a7a4c69 100644 --- a/fluentui_notification/src/main/java/com/microsoft/fluentui/tokenized/notification/StackableSnackbar.kt +++ b/fluentui_notification/src/main/java/com/microsoft/fluentui/tokenized/notification/StackableSnackbar.kt @@ -430,6 +430,7 @@ fun SnackBarStack( state: SnackBarStackState, snackBarStackConfig: SnackBarStackConfig = SnackBarStackConfig(), enableSwipeToDismiss: Boolean = true, + getTrailingIconBasedOnOverflow: (Boolean) -> FluentIcon? ) { val localDensity = LocalDensity.current @@ -482,7 +483,8 @@ fun SnackBarStack( }, snackBarStackConfig = snackBarStackConfig, enableSwipeToDismiss = enableSwipeToDismiss, - screenWidthPx = screenWidthPx + screenWidthPx = screenWidthPx, + getTrailingIconBasedOnOverflow = getTrailingIconBasedOnOverflow ) } } @@ -510,7 +512,8 @@ private fun SnackBarStackItem( onSwipedAway: (String) -> Unit, snackBarStackConfig: SnackBarStackConfig, enableSwipeToDismiss: Boolean = true, - screenWidthPx: Float + screenWidthPx: Float, + getTrailingIconBasedOnOverflow: (Boolean) -> FluentIcon? ) { val modelWrapper = state.snapshotStateList[trueIndex] val model = modelWrapper.model @@ -706,6 +709,8 @@ private fun SnackBarStackItem( .testTag(SnackBarTestTags.SNACK_BAR), verticalAlignment = Alignment.CenterVertically ) { + + var trailingIcon by remember { mutableStateOf(model.trailingIcon) } if (model.leadingIcon != null && model.leadingIcon.isIconAvailable()) { Box( modifier = Modifier @@ -743,7 +748,10 @@ private fun SnackBarStackItem( text = model.message, style = token.titleTypography(snackBarInfo), maxLines = messageMaxLines, - overflow = TextOverflow.Ellipsis + overflow = TextOverflow.Ellipsis, + onTextLayout = { textLayout -> + trailingIcon = getTrailingIconBasedOnOverflow(textLayout.hasVisualOverflow) ?: model.trailingIcon + } ) if (!model.subTitle.isNullOrBlank()) { Text( @@ -786,29 +794,31 @@ private fun SnackBarStackItem( ) } - if (model.trailingIcon != null && model.trailingIcon!!.isIconAvailable()) { - Box( - modifier = Modifier - .testTag(SnackBarTestTags.SNACK_BAR_ICON) - .then( - if (model.trailingIcon!!.onClick != null) { - Modifier.clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = rememberRipple(), - enabled = true, - role = Role.Image, - onClick = model.trailingIcon!!.onClick!! - ) - } else Modifier - ) - ) { - Icon( - model.trailingIcon!!, + trailingIcon?.let { icon -> + if (icon.isIconAvailable()) { + Box( modifier = Modifier - .padding(top = 12.dp, bottom = 12.dp, end = 16.dp) - .size(token.leftIconSize(snackBarInfo)), - tint = token.iconColor(snackBarInfo) - ) + .testTag(SnackBarTestTags.SNACK_BAR_ICON) + .then( + icon.onClick?.let { + Modifier.clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = rememberRipple(), + enabled = true, + role = Role.Image, + onClick = model.trailingIcon!!.onClick!! + ) + } ?: Modifier + ) + ) { + Icon( + icon, + modifier = Modifier + .padding(top = 12.dp, bottom = 12.dp, end = 16.dp) + .size(token.leftIconSize(snackBarInfo)), + tint = token.iconColor(snackBarInfo) + ) + } } } } From 4f6e4880cb22ab392400ea5f53cd8f81929548e9 Mon Sep 17 00:00:00 2001 From: Pranjal Singh Date: Mon, 2 Mar 2026 10:06:06 +0530 Subject: [PATCH 2/6] adding callback to model itself --- .../tokenized/notification/StackableSnackbar.kt | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/fluentui_notification/src/main/java/com/microsoft/fluentui/tokenized/notification/StackableSnackbar.kt b/fluentui_notification/src/main/java/com/microsoft/fluentui/tokenized/notification/StackableSnackbar.kt index a1a7a4c69..61f231c3a 100644 --- a/fluentui_notification/src/main/java/com/microsoft/fluentui/tokenized/notification/StackableSnackbar.kt +++ b/fluentui_notification/src/main/java/com/microsoft/fluentui/tokenized/notification/StackableSnackbar.kt @@ -137,7 +137,8 @@ data class SnackBarItemModel( val subTitle: String? = null, val actionText: String? = null, val snackBarToken: StackableSnackBarTokens = DEFAULT_SNACKBAR_TOKENS, - val onActionTextClicked: () -> Unit = {} + val onActionTextClicked: () -> Unit = {}, + val getTrailingIconBasedOnOverflow: (Boolean) -> FluentIcon? ) internal data class SnackbarItemInternal( @@ -429,8 +430,7 @@ data class SnackBarStackConfig( fun SnackBarStack( state: SnackBarStackState, snackBarStackConfig: SnackBarStackConfig = SnackBarStackConfig(), - enableSwipeToDismiss: Boolean = true, - getTrailingIconBasedOnOverflow: (Boolean) -> FluentIcon? + enableSwipeToDismiss: Boolean = true ) { val localDensity = LocalDensity.current @@ -483,8 +483,7 @@ fun SnackBarStack( }, snackBarStackConfig = snackBarStackConfig, enableSwipeToDismiss = enableSwipeToDismiss, - screenWidthPx = screenWidthPx, - getTrailingIconBasedOnOverflow = getTrailingIconBasedOnOverflow + screenWidthPx = screenWidthPx ) } } @@ -512,8 +511,7 @@ private fun SnackBarStackItem( onSwipedAway: (String) -> Unit, snackBarStackConfig: SnackBarStackConfig, enableSwipeToDismiss: Boolean = true, - screenWidthPx: Float, - getTrailingIconBasedOnOverflow: (Boolean) -> FluentIcon? + screenWidthPx: Float ) { val modelWrapper = state.snapshotStateList[trueIndex] val model = modelWrapper.model @@ -750,7 +748,7 @@ private fun SnackBarStackItem( maxLines = messageMaxLines, overflow = TextOverflow.Ellipsis, onTextLayout = { textLayout -> - trailingIcon = getTrailingIconBasedOnOverflow(textLayout.hasVisualOverflow) ?: model.trailingIcon + trailingIcon = model.getTrailingIconBasedOnOverflow(textLayout.hasVisualOverflow) ?: model.trailingIcon } ) if (!model.subTitle.isNullOrBlank()) { From 22e068059e0325dc7b7d3120f2661d59752a6cba Mon Sep 17 00:00:00 2001 From: Pranjal Singh Date: Thu, 5 Mar 2026 10:16:09 +0530 Subject: [PATCH 3/6] changes in activity --- .../fluentuidemo/V2StackableSnackbarActivity.kt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/FluentUI.Demo/src/main/java/com/microsoft/fluentuidemo/V2StackableSnackbarActivity.kt b/FluentUI.Demo/src/main/java/com/microsoft/fluentuidemo/V2StackableSnackbarActivity.kt index 5ab567e71..f89c40ae4 100644 --- a/FluentUI.Demo/src/main/java/com/microsoft/fluentuidemo/V2StackableSnackbarActivity.kt +++ b/FluentUI.Demo/src/main/java/com/microsoft/fluentuidemo/V2StackableSnackbarActivity.kt @@ -108,10 +108,7 @@ fun SnackBarStackDemoLayout(context: V2StackableSnackbarActivity) { state = stackState, snackBarStackConfig = SnackBarStackConfig( snackbarGapWhenExpanded = 10.dp - ), - getTrailingIconBasedOnOverflow = { - null - } + ) ) Spacer(modifier = Modifier.height(10.dp)) Row() { @@ -136,6 +133,9 @@ fun SnackBarStackDemoLayout(context: V2StackableSnackbarActivity) { } } ), + getTrailingIconBasedOnOverflow = { + null + }, onActionTextClicked = { stackState.toggleExpandedState() }) @@ -173,6 +173,9 @@ fun SnackBarStackDemoLayout(context: V2StackableSnackbarActivity) { } } ), + getTrailingIconBasedOnOverflow = { + null + }, onActionTextClicked = { stackState.toggleExpandedState() }) From 3a0eddcc36222db61781da70f5a2ad3db5ee399d Mon Sep 17 00:00:00 2001 From: Pranjal Singh Date: Thu, 5 Mar 2026 10:57:04 +0530 Subject: [PATCH 4/6] copilot suggestions --- .../fluentui/tokenized/notification/StackableSnackbar.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/fluentui_notification/src/main/java/com/microsoft/fluentui/tokenized/notification/StackableSnackbar.kt b/fluentui_notification/src/main/java/com/microsoft/fluentui/tokenized/notification/StackableSnackbar.kt index 61f231c3a..139f769d1 100644 --- a/fluentui_notification/src/main/java/com/microsoft/fluentui/tokenized/notification/StackableSnackbar.kt +++ b/fluentui_notification/src/main/java/com/microsoft/fluentui/tokenized/notification/StackableSnackbar.kt @@ -126,6 +126,7 @@ private val DEFAULT_SNACKBAR_TOKENS = StackableSnackBarTokens() * @property actionText Optional text for the action button. If null, no action button is shown. * @property snackBarToken The tokens for customizing the snackbar's appearance. * @property onActionTextClicked The callback to be invoked when the action button is clicked. + * @property getTrailingIconBasedOnOverflow The callback to determine the trailing icon based on whether the text has overflow. It receives a boolean indicating if there is an overflow and returns a FluentIcon to be used as the trailing icon. */ @Stable data class SnackBarItemModel( @@ -138,7 +139,7 @@ data class SnackBarItemModel( val actionText: String? = null, val snackBarToken: StackableSnackBarTokens = DEFAULT_SNACKBAR_TOKENS, val onActionTextClicked: () -> Unit = {}, - val getTrailingIconBasedOnOverflow: (Boolean) -> FluentIcon? + val getTrailingIconBasedOnOverflow: (Boolean) -> FluentIcon? = { _ -> trailingIcon } ) internal data class SnackbarItemInternal( @@ -798,13 +799,13 @@ private fun SnackBarStackItem( modifier = Modifier .testTag(SnackBarTestTags.SNACK_BAR_ICON) .then( - icon.onClick?.let { + icon.onClick?.let { onClick -> Modifier.clickable( interactionSource = remember { MutableInteractionSource() }, indication = rememberRipple(), enabled = true, role = Role.Image, - onClick = model.trailingIcon!!.onClick!! + onClick = onClick ) } ?: Modifier ) From f466c87cdfaeaed052543b0ec2e754fc0484649f Mon Sep 17 00:00:00 2001 From: Pranjal Singh Date: Thu, 5 Mar 2026 11:14:45 +0530 Subject: [PATCH 5/6] using derived state to reduce recompositions --- .../fluentui/tokenized/notification/StackableSnackbar.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/fluentui_notification/src/main/java/com/microsoft/fluentui/tokenized/notification/StackableSnackbar.kt b/fluentui_notification/src/main/java/com/microsoft/fluentui/tokenized/notification/StackableSnackbar.kt index 139f769d1..0c3d57f67 100644 --- a/fluentui_notification/src/main/java/com/microsoft/fluentui/tokenized/notification/StackableSnackbar.kt +++ b/fluentui_notification/src/main/java/com/microsoft/fluentui/tokenized/notification/StackableSnackbar.kt @@ -709,7 +709,8 @@ private fun SnackBarStackItem( verticalAlignment = Alignment.CenterVertically ) { - var trailingIcon by remember { mutableStateOf(model.trailingIcon) } + var hasTextOverflow by remember { mutableStateOf(false) } + val trailingIcon by remember { derivedStateOf { model.getTrailingIconBasedOnOverflow(hasTextOverflow) ?: model.trailingIcon } } if (model.leadingIcon != null && model.leadingIcon.isIconAvailable()) { Box( modifier = Modifier @@ -749,7 +750,9 @@ private fun SnackBarStackItem( maxLines = messageMaxLines, overflow = TextOverflow.Ellipsis, onTextLayout = { textLayout -> - trailingIcon = model.getTrailingIconBasedOnOverflow(textLayout.hasVisualOverflow) ?: model.trailingIcon + if (hasTextOverflow != textLayout.hasVisualOverflow) { + hasTextOverflow = textLayout.hasVisualOverflow + } } ) if (!model.subTitle.isNullOrBlank()) { From 6458c9dde7d3bbb16d2721321d921b3abe531fec Mon Sep 17 00:00:00 2001 From: Pranjal Singh Date: Mon, 9 Mar 2026 19:46:00 +0530 Subject: [PATCH 6/6] conflicts resolution comma --- .../fluentui/tokenized/notification/StackableSnackbar.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fluentui_notification/src/main/java/com/microsoft/fluentui/tokenized/notification/StackableSnackbar.kt b/fluentui_notification/src/main/java/com/microsoft/fluentui/tokenized/notification/StackableSnackbar.kt index 05a893d87..2a3cf11e3 100644 --- a/fluentui_notification/src/main/java/com/microsoft/fluentui/tokenized/notification/StackableSnackbar.kt +++ b/fluentui_notification/src/main/java/com/microsoft/fluentui/tokenized/notification/StackableSnackbar.kt @@ -149,7 +149,7 @@ data class SnackBarItemModel( val actionText: String? = null, val snackBarToken: StackableSnackBarTokens = DEFAULT_SNACKBAR_TOKENS, val onActionTextClicked: () -> Unit = {}, - val enableSwipeToDismiss: Boolean = true + val enableSwipeToDismiss: Boolean = true, val getTrailingIconBasedOnOverflow: (Boolean) -> FluentIcon? = { _ -> trailingIcon } )