From 8f61919a63f5415b2f2d3d76be072503a3568681 Mon Sep 17 00:00:00 2001 From: Aniket Kamble Date: Wed, 12 Aug 2026 11:42:46 +0530 Subject: [PATCH 1/2] Migrate 1P compose code to 3P DAC --- .../compose/snippets/components/AppBar.kt | 229 +++++++++++++ .../compose/snippets/components/Button.kt | 324 ++++++++++++++++++ .../components/FloatingActionButton.kt | 20 ++ .../compose/snippets/components/IconButton.kt | 39 +++ .../compose/snippets/components/Menus.kt | 231 +++++++++++++ .../compose/snippets/components/Navigation.kt | 322 +++++++++++++++++ .../snippets/components/ProgressIndicator.kt | 161 +++++++++ gradle/libs.versions.toml | 3 +- 8 files changed, 1328 insertions(+), 1 deletion(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/components/AppBar.kt b/compose/snippets/src/main/java/com/example/compose/snippets/components/AppBar.kt index 72b4586a0..7fdc80447 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/components/AppBar.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/components/AppBar.kt @@ -24,6 +24,7 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed @@ -32,6 +33,7 @@ import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Edit +import androidx.compose.material.icons.filled.Favorite import androidx.compose.material.icons.filled.Image import androidx.compose.material.icons.filled.Menu import androidx.compose.material.icons.filled.Mic @@ -41,13 +43,16 @@ import androidx.compose.material3.BottomAppBarDefaults import androidx.compose.material3.Button import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.FloatingActionButtonDefaults import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.LargeFlexibleTopAppBar import androidx.compose.material3.LargeTopAppBar import androidx.compose.material3.ListItem import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.MediumFlexibleTopAppBar import androidx.compose.material3.MediumTopAppBar import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface @@ -61,6 +66,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.text.style.TextOverflow @@ -85,6 +91,10 @@ fun AppBarExamples( "topBarLarge" -> LargeTopAppBarExample() "topBarNavigation" -> TopBarNavigationExample { navigateBack() } "multiSelection" -> AppBarMultiSelectionExample() + "topBarSubtitle" -> SimpleCenterAlignedTopAppBarWithSubtitle() + "topBarEnterAlways" -> EnterAlwaysTopAppBar() + "topBarMediumFlexible" -> ExitUntilCollapsedCenterAlignedMediumFlexibleTopAppBar() + "topBarLargeFlexible" -> ExitUntilCollapsedCenterAlignedLargeFlexibleTopAppBar() else -> AppBarOptions( toBottom = { selection = "bottomBar" }, toTopBarSmall = { selection = "topBar" }, @@ -93,6 +103,10 @@ fun AppBarExamples( toTopBarLarge = { selection = "topBarLarge" }, toTopBarNavigation = { selection = "topBarNavigation" }, toMultiSelection = { selection = "multiSelection" }, + toTopBarSubtitle = { selection = "topBarSubtitle" }, + toTopBarEnterAlways = { selection = "topBarEnterAlways" }, + toTopBarMediumFlexible = { selection = "topBarMediumFlexible" }, + toTopBarLargeFlexible = { selection = "topBarLargeFlexible" }, ) } } @@ -107,6 +121,10 @@ fun AppBarOptions( toTopBarLarge: () -> Unit, toTopBarNavigation: () -> Unit, toMultiSelection: () -> Unit, + toTopBarSubtitle: () -> Unit, + toTopBarEnterAlways: () -> Unit, + toTopBarMediumFlexible: () -> Unit, + toTopBarLargeFlexible: () -> Unit, ) { Column() { Button({ toBottom() }) { @@ -130,6 +148,18 @@ fun AppBarOptions( Button({ toMultiSelection() }) { Text("Top bar with multi selection list") } + Button({ toTopBarSubtitle() }) { + Text("Top bar with subtitle") + } + Button({ toTopBarEnterAlways() }) { + Text("Enter always top bar") + } + Button({ toTopBarMediumFlexible() }) { + Text("Medium flexible top bar") + } + Button({ toTopBarLargeFlexible() }) { + Text("Large flexible top bar") + } } } @@ -552,3 +582,202 @@ private fun LazyListMultiSelectionPreview() { modifier = Modifier ) } + +// [START android_compose_expressive_components_centeralignedtopappbarwithsubtitle] +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun SimpleCenterAlignedTopAppBarWithSubtitle() { + val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() + Scaffold( + modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), + topBar = { + TopAppBar( + title = { Text("Simple TopAppBar", maxLines = 1, overflow = TextOverflow.Ellipsis) }, + subtitle = { Text("Subtitle", maxLines = 1, overflow = TextOverflow.Ellipsis) }, + titleHorizontalAlignment = Alignment.CenterHorizontally, + navigationIcon = { + IconButton(onClick = { /* onBackClick() */ }) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "Localized description", + ) + } + }, + actions = { + IconButton(onClick = { /* doSomething() */ }) { + Icon(imageVector = Icons.Filled.Favorite, contentDescription = "Localized description") + } + }, + scrollBehavior = scrollBehavior, + ) + }, + content = { innerPadding -> + LazyColumn(contentPadding = innerPadding, verticalArrangement = Arrangement.spacedBy(8.dp)) { + val list = (0..75).map { it.toString() } + items(count = list.size) { + Text( + text = list[it], + style = MaterialTheme.typography.bodyLarge, + modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), + ) + } + } + }, + ) +} +// [END android_compose_expressive_components_centeralignedtopappbarwithsubtitle] + +@Preview +@Composable +private fun SimpleCenterAlignedTopAppBarWithSubtitlePreview() { + SimpleCenterAlignedTopAppBarWithSubtitle() +} + +// [START android_compose_expressive_components_alwaysentertopappbar] +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun EnterAlwaysTopAppBar() { + val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() + Scaffold( + modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), + topBar = { + TopAppBar( + title = { Text("TopAppBar", maxLines = 1, overflow = TextOverflow.Ellipsis) }, + subtitle = { Text("Subtitle", maxLines = 1, overflow = TextOverflow.Ellipsis) }, + navigationIcon = { + IconButton(onClick = { /* onBackClick() */ }) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "Localized description", + ) + } + }, + actions = { + IconButton(onClick = { /* doSomething() */ }) { + Icon(imageVector = Icons.Filled.Favorite, contentDescription = "Localized description") + } + }, + scrollBehavior = scrollBehavior, + ) + }, + content = { innerPadding -> + LazyColumn(contentPadding = innerPadding, verticalArrangement = Arrangement.spacedBy(8.dp)) { + val list = (0..75).map { it.toString() } + items(count = list.size) { + Text( + text = list[it], + style = MaterialTheme.typography.bodyLarge, + modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), + ) + } + } + }, + ) +} +// [END android_compose_expressive_components_alwaysentertopappbar] + +@Preview +@Composable +private fun EnterAlwaysTopAppBarPreview() { + EnterAlwaysTopAppBar() +} + +// [START android_compose_expressive_components_exituntillcollapsedtopappbar] +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun ExitUntilCollapsedCenterAlignedMediumFlexibleTopAppBar() { + val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() + Scaffold( + modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), + topBar = { + MediumFlexibleTopAppBar( + title = { Text("Medium TopAppBar", maxLines = 1, overflow = TextOverflow.Ellipsis) }, + subtitle = { Text("Subtitle", maxLines = 1, overflow = TextOverflow.Ellipsis) }, + titleHorizontalAlignment = Alignment.CenterHorizontally, + navigationIcon = { + IconButton(onClick = { /* onBackClick() */ }) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "Localized description", + ) + } + }, + actions = { + IconButton(onClick = { /* doSomething() */ }) { + Icon(imageVector = Icons.Filled.Favorite, contentDescription = "Localized description") + } + }, + scrollBehavior = scrollBehavior, + ) + }, + content = { innerPadding -> + LazyColumn(contentPadding = innerPadding, verticalArrangement = Arrangement.spacedBy(8.dp)) { + val list = (0..75).map { it.toString() } + items(count = list.size) { + Text( + text = list[it], + style = MaterialTheme.typography.bodyLarge, + modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), + ) + } + } + }, + ) +} +// [END android_compose_expressive_components_exituntillcollapsedtopappbar] + +@Preview +@Composable +private fun ExitUntilCollapsedCenterAlignedMediumFlexibleTopAppBarPreview() { + ExitUntilCollapsedCenterAlignedMediumFlexibleTopAppBar() +} + +// [START android_compose_expressive_components_exituntillcollapsedlargetopappbar] +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun ExitUntilCollapsedCenterAlignedLargeFlexibleTopAppBar() { + val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() + Scaffold( + modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), + topBar = { + LargeFlexibleTopAppBar( + title = { Text("Large TopAppBar", maxLines = 1, overflow = TextOverflow.Ellipsis) }, + subtitle = { Text("Subtitle", maxLines = 1, overflow = TextOverflow.Ellipsis) }, + titleHorizontalAlignment = Alignment.CenterHorizontally, + navigationIcon = { + IconButton(onClick = { /* onBackClick() */ }) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "Localized description", + ) + } + }, + actions = { + IconButton(onClick = { /* doSomething() */ }) { + Icon(imageVector = Icons.Filled.Favorite, contentDescription = "Localized description") + } + }, + scrollBehavior = scrollBehavior, + ) + }, + content = { innerPadding -> + LazyColumn(contentPadding = innerPadding, verticalArrangement = Arrangement.spacedBy(8.dp)) { + val list = (0..75).map { it.toString() } + items(count = list.size) { + Text( + text = list[it], + style = MaterialTheme.typography.bodyLarge, + modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), + ) + } + } + }, + ) +} +// [END android_compose_expressive_components_exituntillcollapsedlargetopappbar] + +@Preview +@Composable +private fun ExitUntilCollapsedCenterAlignedLargeFlexibleTopAppBarPreview() { + ExitUntilCollapsedCenterAlignedLargeFlexibleTopAppBar() +} diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/components/Button.kt b/compose/snippets/src/main/java/com/example/compose/snippets/components/Button.kt index 1208f816f..c791d5ae1 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/components/Button.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/components/Button.kt @@ -19,15 +19,36 @@ package com.example.compose.snippets.components import android.util.Log import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Edit +import androidx.compose.material.icons.filled.Favorite +import androidx.compose.material.icons.outlined.Edit +import androidx.compose.material.icons.outlined.Favorite import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ElevatedButton +import androidx.compose.material3.ElevatedToggleButton +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.FilledTonalButton +import androidx.compose.material3.Icon import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.OutlinedToggleButton import androidx.compose.material3.Text import androidx.compose.material3.TextButton +import androidx.compose.material3.ToggleButton +import androidx.compose.material3.ToggleButtonDefaults +import androidx.compose.material3.ToggleButtonShapes +import androidx.compose.material3.TonalToggleButton import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp @@ -51,6 +72,36 @@ fun ButtonExamples() { OutlinedButtonExample(onClick = { Log.d("Outlined button", "Outlined button clicked.") }) Text("Text button") TextButtonExample(onClick = { Log.d("Text button", "Text button clicked.") }) + Text("Toggle button:") + ToggleButtonSample() + Text("Elevated toggle button:") + ElevatedToggleButtonSample() + Text("Tonal toggle button:") + TonalToggleButtonSample() + Text("Outlined toggle button:") + OutlinedToggleButtonSample() + Text("Button with icon:") + ButtonWithIconSample() + Text("Toggle button with icon:") + ToggleButtonWithIconSample() + Text("XSmall button with icon:") + XSmallButtonWithIconSample() + Text("XSmall toggle button with icon:") + XSmallToggleButtonWithIconSample() + Text("Medium button with icon:") + MediumButtonWithIconSample() + Text("Medium toggle button with icon:") + MediumToggleButtonWithIconSample() + Text("Large button with icon:") + LargeButtonWithIconSample() + Text("Large toggle button with icon:") + LargeToggleButtonWithIconSample() + Text("XLarge button with icon:") + XLargeButtonWithIconSample() + Text("XLarge toggle button with icon:") + XLargeToggleButtonWithIconSample() + Text("Square toggle button:") + SquareToggleButtonSample() } } @@ -100,3 +151,276 @@ fun TextButtonExample(onClick: () -> Unit) { } } // [END android_compose_components_textbutton] + +// [START android_compose_expressive_components_filledtogglebutton] +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun ToggleButtonSample() { + var checked by remember { mutableStateOf(false) } + ToggleButton(checked = checked, onCheckedChange = { checked = it }) { Text("Button") } +} +// [END android_compose_expressive_components_filledtogglebutton] + +// [START android_compose_expressive_components_elevatedtogglebutton] +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun ElevatedToggleButtonSample() { + var checked by remember { mutableStateOf(false) } + ElevatedToggleButton(checked = checked, onCheckedChange = { checked = it }) { + Text("Elevated Button") + } +} +// [END android_compose_expressive_components_elevatedtogglebutton] + +// [START android_compose_expressive_components_tonaltogglebutton] +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun TonalToggleButtonSample() { + var checked by remember { mutableStateOf(false) } + TonalToggleButton(checked = checked, onCheckedChange = { checked = it }) { Text("Tonal Button") } +} +// [END android_compose_expressive_components_tonaltogglebutton] + +// [START android_compose_expressive_components_outlinedtogglebutton] +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun OutlinedToggleButtonSample() { + var checked by remember { mutableStateOf(false) } + OutlinedToggleButton(checked = checked, onCheckedChange = { checked = it }) { + Text("Outlined Button") + } +} +// [END android_compose_expressive_components_outlinedtogglebutton] + +// [START android_compose_expressive_components_buttonwithicon] +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun ButtonWithIconSample() { + Button( + onClick = { /* Do something! */ }, + contentPadding = ButtonDefaults.contentPaddingFor(ButtonDefaults.MinHeight, hasStartIcon = true), + ) { + Icon( + Icons.Filled.Favorite, + contentDescription = "Localized description", + modifier = Modifier.size(ButtonDefaults.iconSizeFor(ButtonDefaults.MinHeight)), + ) + Spacer(Modifier.size(ButtonDefaults.iconSpacingFor(ButtonDefaults.MinHeight))) + Text("Like") + } +} +// [END android_compose_expressive_components_buttonwithicon] + +// [START android_compose_expressive_components_togglebuttonwithicon] +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun ToggleButtonWithIconSample() { + var checked by remember { mutableStateOf(false) } + ToggleButton(checked = checked, onCheckedChange = { checked = it }) { + Icon( + if (checked) Icons.Filled.Favorite else Icons.Outlined.Favorite, + contentDescription = "Localized description", + modifier = Modifier.size(ButtonDefaults.IconSize), + ) + Spacer(Modifier.size(ButtonDefaults.IconSpacing)) + Text("Like") + } +} +// [END android_compose_expressive_components_togglebuttonwithicon] + +// [START android_compose_expressive_components_xmsallbuttonwithicon] +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun XSmallButtonWithIconSample() { + val size = ButtonDefaults.ExtraSmallContainerHeight + Button( + onClick = { /* Do something! */ }, + modifier = Modifier.heightIn(size), + contentPadding = ButtonDefaults.contentPaddingFor(size, hasStartIcon = true), + ) { + Icon( + Icons.Filled.Edit, + contentDescription = "Localized description", + modifier = Modifier.size(ButtonDefaults.iconSizeFor(size)), + ) + Spacer(Modifier.size(ButtonDefaults.iconSpacingFor(size))) + Text("Label") + } +} +// [END android_compose_expressive_components_xmsallbuttonwithicon] + +// [START android_compose_expressive_components_xmsalltogglebuttonwithicon] +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun XSmallToggleButtonWithIconSample() { + var checked by remember { mutableStateOf(false) } + val size = ButtonDefaults.ExtraSmallContainerHeight + ToggleButton( + checked = checked, + onCheckedChange = { checked = it }, + modifier = Modifier.heightIn(size), + shapes = ToggleButtonDefaults.shapesFor(size), + contentPadding = ButtonDefaults.contentPaddingFor(size), + ) { + Icon( + if (checked) Icons.Filled.Edit else Icons.Outlined.Edit, + contentDescription = "Localized description", + modifier = Modifier.size(ButtonDefaults.iconSizeFor(size)), + ) + Spacer(Modifier.size(ButtonDefaults.iconSpacingFor(size))) + Text("Label") + } +} +// [END android_compose_expressive_components_xmsalltogglebuttonwithicon] + +// [START android_compose_expressive_components_mediumbuttonwithicon] +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun MediumButtonWithIconSample() { + val size = ButtonDefaults.MediumContainerHeight + Button( + onClick = { /* Do something! */ }, + modifier = Modifier.heightIn(size), + contentPadding = ButtonDefaults.contentPaddingFor(size, hasStartIcon = true), + ) { + Icon( + Icons.Filled.Edit, + contentDescription = "Localized description", + modifier = Modifier.size(ButtonDefaults.iconSizeFor(size)), + ) + Spacer(Modifier.size(ButtonDefaults.iconSpacingFor(size))) + Text("Label", style = ButtonDefaults.textStyleFor(size)) + } +} +// [END android_compose_expressive_components_mediumbuttonwithicon] + +// [START android_compose_expressive_components_mediumtogglebuttonwithicon] +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun MediumToggleButtonWithIconSample() { + var checked by remember { mutableStateOf(false) } + val size = ButtonDefaults.MediumContainerHeight + ToggleButton( + checked = checked, + onCheckedChange = { checked = it }, + modifier = Modifier.heightIn(size), + shapes = ToggleButtonDefaults.shapesFor(size), + contentPadding = ButtonDefaults.contentPaddingFor(size), + ) { + Icon( + if (checked) Icons.Filled.Edit else Icons.Outlined.Edit, + contentDescription = "Localized description", + modifier = Modifier.size(ButtonDefaults.iconSizeFor(size)), + ) + Spacer(Modifier.size(ButtonDefaults.iconSpacingFor(size))) + Text("Label", style = ButtonDefaults.textStyleFor(size)) + } +} +// [END android_compose_expressive_components_mediumtogglebuttonwithicon] + +// [START android_compose_expressive_components_largebuttonwithicon] +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun LargeButtonWithIconSample() { + val size = ButtonDefaults.LargeContainerHeight + Button( + onClick = { /* Do something! */ }, + modifier = Modifier.heightIn(size), + contentPadding = ButtonDefaults.contentPaddingFor(size, hasStartIcon = true), + ) { + Icon( + Icons.Filled.Edit, + contentDescription = "Localized description", + modifier = Modifier.size(ButtonDefaults.iconSizeFor(size)), + ) + Spacer(Modifier.size(ButtonDefaults.iconSpacingFor(size))) + Text("Label", style = ButtonDefaults.textStyleFor(size)) + } +} +// [END android_compose_expressive_components_largebuttonwithicon] + +// [START android_compose_expressive_components_largetogglebuttonwithicon] +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun LargeToggleButtonWithIconSample() { + var checked by remember { mutableStateOf(false) } + val size = ButtonDefaults.LargeContainerHeight + ToggleButton( + checked = checked, + onCheckedChange = { checked = it }, + modifier = Modifier.heightIn(size), + shapes = ToggleButtonDefaults.shapesFor(size), + contentPadding = ButtonDefaults.contentPaddingFor(size), + ) { + Icon( + if (checked) Icons.Filled.Edit else Icons.Outlined.Edit, + contentDescription = "Localized description", + modifier = Modifier.size(ButtonDefaults.iconSizeFor(size)), + ) + Spacer(Modifier.size(ButtonDefaults.iconSpacingFor(size))) + Text("Label", style = ButtonDefaults.textStyleFor(size)) + } +} +// [END android_compose_expressive_components_largetogglebuttonwithicon] + +// [START android_compose_expressive_components_xlargebuttonwithicon] +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun XLargeButtonWithIconSample() { + val size = ButtonDefaults.ExtraLargeContainerHeight + Button( + onClick = { /* Do something! */ }, + modifier = Modifier.heightIn(size), + contentPadding = ButtonDefaults.contentPaddingFor(size, hasStartIcon = true), + ) { + Icon( + Icons.Filled.Edit, + contentDescription = "Localized description", + modifier = Modifier.size(ButtonDefaults.iconSizeFor(size)), + ) + Spacer(Modifier.size(ButtonDefaults.iconSpacingFor(size))) + Text("Label", style = ButtonDefaults.textStyleFor(size)) + } +} +// [END android_compose_expressive_components_xlargebuttonwithicon] + +// [START android_compose_expressive_components_xlargetogglebuttonwithicon] +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun XLargeToggleButtonWithIconSample() { + var checked by remember { mutableStateOf(false) } + val size = ButtonDefaults.ExtraLargeContainerHeight + ToggleButton( + checked = checked, + onCheckedChange = { checked = it }, + modifier = Modifier.heightIn(size), + shapes = ToggleButtonDefaults.shapesFor(size), + contentPadding = ButtonDefaults.contentPaddingFor(size), + ) { + Icon( + if (checked) Icons.Filled.Edit else Icons.Outlined.Edit, + contentDescription = "Localized description", + modifier = Modifier.size(ButtonDefaults.iconSizeFor(size)), + ) + Spacer(Modifier.size(ButtonDefaults.iconSpacingFor(size))) + Text("Label", style = ButtonDefaults.textStyleFor(size)) + } +} +// [END android_compose_expressive_components_xlargetogglebuttonwithicon] + +// [START android_compose_expressive_components_squaretogglebutton] +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun SquareToggleButtonSample() { + var checked by remember { mutableStateOf(false) } + val shapes = + ToggleButtonShapes( + shape = ToggleButtonDefaults.squareShape, + pressedShape = ToggleButtonDefaults.pressedShape, + checkedShape = ToggleButtonDefaults.roundShape, + ) + ToggleButton(checked = checked, onCheckedChange = { checked = it }, shapes = shapes) { + Text("Button") + } +} +// [END android_compose_expressive_components_squaretogglebutton] \ No newline at end of file diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/components/FloatingActionButton.kt b/compose/snippets/src/main/java/com/example/compose/snippets/components/FloatingActionButton.kt index 61d8074c8..45fa49e18 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/components/FloatingActionButton.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/components/FloatingActionButton.kt @@ -21,15 +21,19 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Edit +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExtendedFloatingActionButton import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.FloatingActionButtonDefaults import androidx.compose.material3.Icon import androidx.compose.material3.LargeFloatingActionButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.MediumFloatingActionButton import androidx.compose.material3.SmallFloatingActionButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -54,6 +58,8 @@ fun FloatingActionButtonExamples() { LargeExample(onClick = { Log.d("FAB", "Large FAB clicked.") }) Text("Floating action button with text:") ExtendedExample(onClick = { Log.d("FAB", "Extended FAB clicked.") }) + Text("Medium floating action button:") + MediumFloatingActionButtonSample() } } @@ -103,3 +109,17 @@ fun LargeExample(onClick: () -> Unit) { } } // [END android_compose_components_largefab] + +// [START android_compose_expressive_components_mediumfab] +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun MediumFloatingActionButtonSample() { + MediumFloatingActionButton(onClick = {}) { + Icon( + Icons.Filled.Add, + contentDescription = "Add", + modifier = Modifier.size(FloatingActionButtonDefaults.MediumIconSize), + ) + } +} +// [END android_compose_expressive_components_mediumfab] diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/components/IconButton.kt b/compose/snippets/src/main/java/com/example/compose/snippets/components/IconButton.kt index 59727c71b..daea99531 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/components/IconButton.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/components/IconButton.kt @@ -21,8 +21,13 @@ import androidx.compose.foundation.interaction.collectIsPressedAsState import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Lock +import androidx.compose.material.icons.outlined.Lock +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.IconToggleButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -122,3 +127,37 @@ fun MomentaryIconButtonExample() { } } // [END android_compose_components_momentaryiconbuttons] + +// [START android_compose_expressive_components_animatediconbuttons] +@Preview +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun IconButtonWithAnimatedShapeSample() { + IconButton( + onClick = { /* doSomething() */ }, + shapes = androidx.compose.material3.IconButtonDefaults.shapes(), + ) { + Icon(Icons.Filled.Lock, contentDescription = "Localized description") + } +} +// [END android_compose_expressive_components_animatediconbuttons] + +// [START android_compose_expressive_components_animatedtoggleiconbuttons] +@Preview +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun IconToggleButtonWithAnimatedShapeSample() { + var checked by remember { mutableStateOf(false) } + IconToggleButton( + checked = checked, + onCheckedChange = { checked = it }, + shapes = androidx.compose.material3.IconButtonDefaults.toggleableShapes(), + ) { + if (checked) { + Icon(Icons.Filled.Lock, contentDescription = "Localized description") + } else { + Icon(Icons.Outlined.Lock, contentDescription = "Localized description") + } + } +} +// [END android_compose_expressive_components_animatedtoggleiconbuttons] \ No newline at end of file diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/components/Menus.kt b/compose/snippets/src/main/java/com/example/compose/snippets/components/Menus.kt index a3468a7c2..4487fea30 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/components/Menus.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/components/Menus.kt @@ -16,13 +16,17 @@ package com.example.compose.snippets.components +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.DirectionsBike @@ -33,31 +37,58 @@ import androidx.compose.material.icons.automirrored.outlined.OpenInNew import androidx.compose.material.icons.automirrored.outlined.Send import androidx.compose.material.icons.filled.ArrowDropDown import androidx.compose.material.icons.filled.Check +import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.filled.Hiking +import androidx.compose.material.icons.filled.Home +import androidx.compose.material.icons.filled.Info import androidx.compose.material.icons.filled.MoreVert +import androidx.compose.material.icons.filled.Settings +import androidx.compose.material.icons.filled.TagFaces +import androidx.compose.material.icons.filled.ThumbDown +import androidx.compose.material.icons.filled.ThumbUp import androidx.compose.material.icons.filled.Tune +import androidx.compose.material.icons.outlined.Edit import androidx.compose.material.icons.outlined.Feedback +import androidx.compose.material.icons.outlined.Home import androidx.compose.material.icons.outlined.Info +import androidx.compose.material.icons.outlined.MoreVert import androidx.compose.material.icons.outlined.Person import androidx.compose.material.icons.outlined.Settings import androidx.compose.material3.Button +import androidx.compose.material3.ButtonGroup +import androidx.compose.material3.ButtonGroupDefaults import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuGroup import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.DropdownMenuPopup +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.FilledIconButton import androidx.compose.material3.FilterChip import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.IconButtonDefaults +import androidx.compose.material3.MenuDefaults +import androidx.compose.material3.PlainTooltip import androidx.compose.material3.Text +import androidx.compose.material3.TooltipAnchorPosition +import androidx.compose.material3.TooltipBox +import androidx.compose.material3.TooltipDefaults +import androidx.compose.material3.rememberTooltipState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.compose.ui.util.fastForEachIndexed @Composable fun MenusExamples() { @@ -90,6 +121,9 @@ fun MenusExamples() { Button(onClick = { currentExample = { DropdownFilter() } }) { Text("Menu for applying a filter, attached to a filter chip") } + Button(onClick = { currentExample = { GroupedMenuSample() } }) { + Text("Grouped menu with sub-items and interactions") + } } } } @@ -302,3 +336,200 @@ fun DropdownFilterChip(modifier: Modifier = Modifier) { private fun DropdownFilterPreview() { DropdownFilter() } + +// [START android_compose_expressive_components_groupedmenusample] +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun GroupedMenuSample() { + val groupInteractionSource = remember { MutableInteractionSource() } + var expanded by remember { mutableStateOf(false) } + val groupLabels = listOf("Modification", "Navigation") + val groupItemLabels = listOf(listOf("Edit", "Settings"), listOf("Home", "More Options")) + val groupItemLeadingIcons = + listOf(listOf(Icons.Outlined.Edit, Icons.Outlined.Settings), listOf(null, Icons.Outlined.Info)) + val groupItemCheckedLeadingIcons = + listOf( + listOf(Icons.Filled.Edit, Icons.Filled.Settings), + listOf(Icons.Filled.Check, Icons.Filled.Info), + ) + val groupItemTrailingIcons: List> = + listOf(listOf(null, null), listOf(Icons.Outlined.Home, Icons.Outlined.MoreVert)) + val groupItemCheckedTrailingIcons: List> = + listOf(listOf(null, null), listOf(Icons.Filled.Home, Icons.Filled.MoreVert)) + val groupItemSupportingText: List> = + listOf(listOf("Edit mode", null), listOf(null, "Opens menu")) + val checked = remember { + listOf(mutableStateListOf(false, false), mutableStateListOf(false, false)) + } + + Box(modifier = Modifier.fillMaxSize().wrapContentSize(Alignment.TopStart)) { + // Icon button should have a tooltip associated with it for a11y. + TooltipBox( + positionProvider = + TooltipDefaults.rememberTooltipPositionProvider(TooltipAnchorPosition.Above), + tooltip = { PlainTooltip { Text("Localized description") } }, + state = rememberTooltipState(), + ) { + IconButton(onClick = { expanded = true }) { + Icon(Icons.Default.MoreVert, contentDescription = "Localized description") + } + } + DropdownMenuPopup(expanded = expanded, onDismissRequest = { expanded = false }) { + val groupCount = groupLabels.size + groupLabels.fastForEachIndexed { groupIndex, label -> + DropdownMenuGroup( + shapes = MenuDefaults.groupShape(groupIndex, groupCount), + interactionSource = groupInteractionSource, + ) { + MenuDefaults.Label { Text(label) } + HorizontalDivider(modifier = Modifier.padding(MenuDefaults.HorizontalDividerPadding)) + val groupItemCount = groupItemLabels[groupIndex].size + groupItemLabels[groupIndex].fastForEachIndexed { itemIndex, itemLabel -> + DropdownMenuItem( + text = { Text(itemLabel) }, + supportingText = + groupItemSupportingText[groupIndex][itemIndex]?.let { supportingText -> + { Text(supportingText) } + }, + shapes = MenuDefaults.itemShape(itemIndex, groupItemCount), + leadingIcon = + groupItemLeadingIcons[groupIndex][itemIndex]?.let { iconData -> + { + Icon( + iconData, + modifier = Modifier.size(MenuDefaults.LeadingIconSize), + contentDescription = null, + ) + } + }, + checkedLeadingIcon = { + Icon( + groupItemCheckedLeadingIcons[groupIndex][itemIndex], + modifier = Modifier.size(MenuDefaults.LeadingIconSize), + contentDescription = null, + ) + }, + trailingIcon = + if (checked[groupIndex][itemIndex]) { + groupItemCheckedTrailingIcons[groupIndex][itemIndex]?.let { iconData -> + { + Icon( + iconData, + modifier = Modifier.size(MenuDefaults.TrailingIconSize), + contentDescription = null, + ) + } + } + } else { + groupItemTrailingIcons[groupIndex][itemIndex]?.let { iconData -> + { + Icon( + iconData, + modifier = Modifier.size(MenuDefaults.TrailingIconSize), + contentDescription = null, + ) + } + } + }, + checked = checked[groupIndex][itemIndex], + onCheckedChange = { checked[groupIndex][itemIndex] = it }, + ) + } + } + + if (groupIndex != groupCount - 1) { + Spacer(Modifier.height(MenuDefaults.GroupSpacing)) + } + } + if (checked.last().last()) { + DropdownMenuButtonGroup() + } + } + } +} +// [END android_compose_expressive_components_groupedmenusample] + +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +private fun DropdownMenuButtonGroup() { + ButtonGroup( + overflowIndicator = { menuState -> ButtonGroupDefaults.OverflowIndicator(menuState) }, + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(2.dp, Alignment.CenterHorizontally), + ) { + customItem( + buttonGroupContent = { + FilledIconButton( + onClick = {}, + colors = + IconButtonDefaults.filledIconButtonColors( + containerColor = MenuDefaults.groupStandardContainerColor + ), + ) { + Icon(Icons.Filled.ThumbUp, contentDescription = "Localized description") + } + }, + menuContent = { + DropdownMenuItem( + leadingIcon = { + Icon(Icons.Filled.ThumbUp, contentDescription = "Localized description") + }, + text = { Text("Thumbs up") }, + onClick = {}, + ) + }, + ) + + customItem( + buttonGroupContent = { + FilledIconButton( + onClick = {}, + colors = + IconButtonDefaults.filledIconButtonColors( + containerColor = MenuDefaults.groupStandardContainerColor + ), + ) { + Icon(Icons.Filled.ThumbDown, contentDescription = "Localized description") + } + }, + menuContent = { + DropdownMenuItem( + leadingIcon = { + Icon(Icons.Filled.ThumbDown, contentDescription = "Localized description") + }, + text = { Text("Thumbs down") }, + onClick = {}, + ) + }, + ) + + customItem( + buttonGroupContent = { + FilledIconButton( + onClick = {}, + colors = + IconButtonDefaults.filledIconButtonColors( + containerColor = MenuDefaults.groupStandardContainerColor + ), + ) { + Icon(Icons.Filled.TagFaces, contentDescription = "Localized description") + } + }, + menuContent = { + DropdownMenuItem( + leadingIcon = { + Icon(Icons.Filled.TagFaces, contentDescription = "Localized description") + }, + text = { Text("Emotes") }, + onClick = {}, + ) + }, + ) + } +} + +@Preview +@Composable +private fun GroupedMenuSamplePreview() { + GroupedMenuSample() +} diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/components/Navigation.kt b/compose/snippets/src/main/java/com/example/compose/snippets/components/Navigation.kt index 9cad68934..af9213ddf 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/components/Navigation.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/components/Navigation.kt @@ -16,38 +16,78 @@ package com.example.compose.snippets.components +import android.app.Activity +import android.content.pm.ActivityInfo import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.MenuOpen import androidx.compose.material.icons.filled.Album +import androidx.compose.material.icons.filled.Favorite +import androidx.compose.material.icons.filled.Home +import androidx.compose.material.icons.filled.Menu import androidx.compose.material.icons.filled.MusicNote import androidx.compose.material.icons.filled.PlaylistAddCircle +import androidx.compose.material.icons.filled.Search +import androidx.compose.material.icons.filled.Settings +import androidx.compose.material.icons.filled.Star +import androidx.compose.material.icons.outlined.FavoriteBorder +import androidx.compose.material.icons.outlined.Home +import androidx.compose.material.icons.outlined.Search +import androidx.compose.material.icons.outlined.Settings +import androidx.compose.material.icons.outlined.StarBorder +import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.ModalWideNavigationRail import androidx.compose.material3.NavigationBar import androidx.compose.material3.NavigationBarDefaults import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.NavigationItemIconPosition import androidx.compose.material3.NavigationRail import androidx.compose.material3.NavigationRailItem import androidx.compose.material3.PrimaryTabRow import androidx.compose.material3.Scaffold +import androidx.compose.material3.ShortNavigationBar +import androidx.compose.material3.ShortNavigationBarArrangement +import androidx.compose.material3.ShortNavigationBarItem import androidx.compose.material3.Tab import androidx.compose.material3.Text +import androidx.compose.material3.WideNavigationRail +import androidx.compose.material3.WideNavigationRailItem +import androidx.compose.material3.WideNavigationRailValue +import androidx.compose.material3.rememberWideNavigationRailState import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.stateDescription +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController +import kotlinx.coroutines.launch @Composable fun SongsScreen(modifier: Modifier = Modifier) { @@ -212,3 +252,285 @@ fun NavigationTabExample(modifier: Modifier = Modifier) { } } // [END android_compose_components_navigationtabexample] + +@Preview() +// [START android_compose_expressive_components_verticalitemsnavigationbarexample] +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun ShortNavigationBarSample() { + var selectedItem by remember { mutableIntStateOf(0) } + val items = listOf("Songs", "Artists", "Playlists") + + ShortNavigationBar { + items.forEachIndexed { index, item -> + ShortNavigationBarItem( + icon = { + Icon( + if (selectedItem == index) Icons.Filled.Favorite else Icons.Outlined.FavoriteBorder, + contentDescription = null, + ) + }, + label = { Text(item) }, + selected = selectedItem == index, + onClick = { selectedItem = index }, + ) + } + } +} +// [END android_compose_expressive_components_verticalitemsnavigationbarexample] + +@Preview() +// [START android_compose_expressive_components_horizontalitemsnavigationbarexample] +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun ShortNavigationBarWithHorizontalItemsSample() { + var selectedItem by remember { mutableIntStateOf(0) } + val items = listOf("Songs", "Artists", "Playlists") + + Column { + Text( + "Note: this is configuration is better displayed in medium screen sizes.", + Modifier.padding(16.dp), + ) + + Spacer(Modifier.height(32.dp)) + + ShortNavigationBar(arrangement = ShortNavigationBarArrangement.Centered) { + items.forEachIndexed { index, item -> + ShortNavigationBarItem( + iconPosition = NavigationItemIconPosition.Start, + icon = { + Icon( + if (selectedItem == index) Icons.Filled.Favorite else Icons.Outlined.FavoriteBorder, + contentDescription = null, + ) + }, + label = { Text(item) }, + selected = selectedItem == index, + onClick = { selectedItem = index }, + ) + } + } + } +} +// [END android_compose_expressive_components_horizontalitemsnavigationbarexample] + +@Preview() +// [START android_compose_expressive_components_widenavigationrailexample] +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Suppress("SourceLockedOrientationActivity") +@Composable +fun WideNavigationRailResponsiveSample() { + var selectedItem by remember { mutableIntStateOf(0) } + val items = listOf("Home", "Search", "Settings") + val selectedIcons = listOf(Icons.Filled.Home, Icons.Filled.Favorite, Icons.Filled.Star) + val unselectedIcons = + listOf(Icons.Outlined.Home, Icons.Outlined.FavoriteBorder, Icons.Outlined.StarBorder) + val state = rememberWideNavigationRailState() + val scope = rememberCoroutineScope() + + Row(Modifier.fillMaxWidth()) { + WideNavigationRail( + state = state, + header = { + IconButton( + modifier = + Modifier + .padding(start = 24.dp) + .semantics { + // The button must announce the expanded or collapsed state of the rail + // for accessibility. + stateDescription = + if (state.currentValue == WideNavigationRailValue.Expanded) "Expanded" + else "Collapsed" + }, + onClick = { + scope.launch { + if (state.targetValue == WideNavigationRailValue.Expanded) state.collapse() + else state.expand() + } + }, + ) { + if (state.targetValue == WideNavigationRailValue.Expanded) { + Icon(Icons.AutoMirrored.Filled.MenuOpen, "Collapse rail") + } else { + Icon(Icons.Filled.Menu, "Expand rail") + } + } + }, + ) { + items.forEachIndexed { index, item -> + WideNavigationRailItem( + railExpanded = state.targetValue == WideNavigationRailValue.Expanded, + icon = { + val imageVector = + if (selectedItem == index) { + selectedIcons[index] + } else { + unselectedIcons[index] + } + Icon(imageVector = imageVector, contentDescription = null) + }, + label = { Text(item) }, + selected = selectedItem == index, + onClick = { selectedItem = index }, + ) + } + } + + val textString = + if (state.currentValue == WideNavigationRailValue.Expanded) "expanded" else "collapsed" + Column { + Text(modifier = Modifier.padding(16.dp), text = "Is animating: " + state.isAnimating) + Text(modifier = Modifier.padding(16.dp), text = "The rail is $textString.") + Text( + modifier = Modifier.padding(16.dp), + text = + "Note: The orientation of this demo has been locked to portrait mode, because" + + " landscape mode may result in a compact height in certain devices. For" + + " any compact screen dimensions, use a Navigation Bar instead.", + ) + } + } + + // Lock the orientation for this demo as the navigation rail may look cut off in landscape in + // smaller screens. + val context = LocalContext.current + DisposableEffect(context) { + (context as? Activity)?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + onDispose { + (context as? Activity)?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED + } + } +} +// [END android_compose_expressive_components_widenavigationrailexample] + +@Preview() +// [START android_compose_expressive_components_modalwidenavigationrailexample] +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Suppress("SourceLockedOrientationActivity") +@Composable +fun ModalWideNavigationRailSample() { + var selectedItem by remember { mutableIntStateOf(0) } + val items = listOf("Home", "Search", "Settings") + val selectedIcons = listOf(Icons.Filled.Home, Icons.Filled.Favorite, Icons.Filled.Star) + val unselectedIcons = + listOf(Icons.Outlined.Home, Icons.Outlined.FavoriteBorder, Icons.Outlined.StarBorder) + val state = rememberWideNavigationRailState() + val scope = rememberCoroutineScope() + + Row(Modifier.fillMaxWidth()) { + ModalWideNavigationRail( + state = state, + // Note: the value of expandedHeaderTopPadding depends on the layout of your screen in + // order to achieve the best alignment. + expandedHeaderTopPadding = 64.dp, + header = { + IconButton( + modifier = + Modifier + .padding(start = 24.dp) + .semantics { + // The button must announce the expanded or collapsed state of the rail + // for accessibility. + stateDescription = + if (state.currentValue == WideNavigationRailValue.Expanded) "Expanded" + else "Collapsed" + }, + onClick = { + scope.launch { + if (state.targetValue == WideNavigationRailValue.Expanded) state.collapse() + else state.expand() + } + }, + ) { + if (state.targetValue == WideNavigationRailValue.Expanded) + Icon(Icons.AutoMirrored.Filled.MenuOpen, "Collapse rail") + else Icon(Icons.Filled.Menu, "Expand rail") + } + }, + ) { + items.forEachIndexed { index, item -> + WideNavigationRailItem( + railExpanded = state.targetValue == WideNavigationRailValue.Expanded, + icon = { + Icon( + if (selectedItem == index) selectedIcons[index] else unselectedIcons[index], + contentDescription = item, + ) + }, + label = { Text(item) }, + selected = selectedItem == index, + onClick = { selectedItem = index }, + ) + } + } + + val textString = + if (state.currentValue == WideNavigationRailValue.Expanded) "expanded" else "collapsed" + Column { + Text(modifier = Modifier.padding(16.dp), text = "The rail is $textString.") + Text( + modifier = Modifier.padding(16.dp), + text = + "Note: The orientation of this demo has been locked to portrait mode, because" + + " landscape mode may result in a compact height in certain devices. For" + + " any compact screen dimensions, use a Navigation Bar instead.", + ) + } + + // Lock the orientation for this demo as the navigation rail may look cut off in landscape + // in smaller screens. + val context = LocalContext.current + DisposableEffect(context) { + (context as? Activity)?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + onDispose { + (context as? Activity)?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED + } + } + } +} +// [END android_compose_expressive_components_modalwidenavigationrailexample] + +@Preview() +// [START android_compose_expressive_components_dismissiblemodalwidenavigationrailexample] +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun DismissibleModalWideNavigationRailSample() { + var selectedItem by remember { mutableIntStateOf(0) } + val items = listOf("Home", "Search", "Settings") + val selectedIcons = listOf(Icons.Filled.Home, Icons.Filled.Search, Icons.Filled.Settings) + val unselectedIcons = listOf(Icons.Outlined.Home, Icons.Outlined.Search, Icons.Outlined.Settings) + val state = rememberWideNavigationRailState() + val scope = rememberCoroutineScope() + + Row(Modifier.fillMaxSize()) { + ModalWideNavigationRail(state = state, hideOnCollapse = true) { + items.forEachIndexed { index, item -> + WideNavigationRailItem( + railExpanded = true, + icon = { + Icon( + if (selectedItem == index) selectedIcons[index] else unselectedIcons[index], + contentDescription = null, + ) + }, + label = { Text(item) }, + selected = selectedItem == index, + onClick = { + selectedItem = index + scope.launch { state.collapse() } + }, + ) + } + } + + Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) { + val currentPage = items.get(selectedItem) + Button(onClick = { scope.launch { state.expand() } }, Modifier.padding(32.dp)) { + Text(text = "$currentPage Page\nOpen modal rail", textAlign = TextAlign.Center) + } + } + } +} +// [END android_compose_expressive_components_dismissiblemodalwidenavigationrailexample] diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/components/ProgressIndicator.kt b/compose/snippets/src/main/java/com/example/compose/snippets/components/ProgressIndicator.kt index be18829e2..135667466 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/components/ProgressIndicator.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/components/ProgressIndicator.kt @@ -16,16 +16,24 @@ package com.example.compose.snippets.components +import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.requiredHeight import androidx.compose.foundation.layout.width import androidx.compose.material3.Button import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.CircularWavyProgressIndicator +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.material3.LinearWavyProgressIndicator import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ProgressIndicatorDefaults +import androidx.compose.material3.Slider import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -41,6 +49,7 @@ import androidx.compose.ui.unit.dp import kotlinx.coroutines.delay import kotlinx.coroutines.launch +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Preview @Composable fun ProgressIndicatorExamples() { @@ -59,6 +68,22 @@ fun ProgressIndicatorExamples() { CircularDeterminateIndicator() Text("Indeterminate circular indicator:") IndeterminateCircularIndicator() + Text("Determinate linear wavy indicator:") + LinearWavyProgressIndicatorSample() + Text("Indeterminate linear wavy indicator:") + IndeterminateLinearWavyProgressIndicatorSample() + Text("Determinate circular wavy indicator:") + CircularWavyProgressIndicatorSample() + Text("Indeterminate circular wavy indicator:") + IndeterminateCircularWavyProgressIndicatorSample() + Text("Determinate linear indicator Expressive:") + LinearProgressIndicatorSample() + Text("Indeterminate linear indicator Expressive:") + IndeterminateLinearProgressIndicatorSample() + Text("Determinate circular indicator Expressive:") + CircularProgressIndicatorSample() + Text("Indeterminate circular indicator Expressive:") + IndeterminateCircularProgressIndicatorSample() } } @@ -175,3 +200,139 @@ fun IndeterminateCircularIndicator() { ) } // [END android_compose_components_indeterminateindicator] + +// [START android_compose_expressive_components_determinatelinearwavyindicator] +@Preview +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun LinearWavyProgressIndicatorSample() { + var progress by remember { mutableFloatStateOf(0.1f) } + val animatedProgress by + animateFloatAsState( + targetValue = progress, + animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec, + ) + + Column(horizontalAlignment = Alignment.CenterHorizontally) { + LinearWavyProgressIndicator(progress = { animatedProgress }) + Spacer(Modifier.requiredHeight(30.dp)) + Text("Set progress:") + Slider( + modifier = Modifier.width(300.dp), + value = progress, + valueRange = 0f..1f, + onValueChange = { progress = it }, + ) + } +} +// [END android_compose_expressive_components_determinatelinearwavyindicator] + +// [START android_compose_expressive_components_indeterminatelinearwavyindicator] +@Preview +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun IndeterminateLinearWavyProgressIndicatorSample() { + Column(horizontalAlignment = Alignment.CenterHorizontally) { LinearWavyProgressIndicator() } +} +// [END android_compose_expressive_components_indeterminatelinearwavyindicator] + +// [START android_compose_expressive_components_determinatecircularwavyindicator] +@Preview +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun CircularWavyProgressIndicatorSample() { + var progress by remember { mutableFloatStateOf(0.1f) } + val animatedProgress by + animateFloatAsState( + targetValue = progress, + animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec, + ) + + Column(horizontalAlignment = Alignment.CenterHorizontally) { + CircularWavyProgressIndicator(progress = { animatedProgress }) + Spacer(Modifier.requiredHeight(30.dp)) + Text("Set progress:") + Slider( + modifier = Modifier.width(300.dp), + value = progress, + valueRange = 0f..1f, + onValueChange = { progress = it }, + ) + } +} +// [END android_compose_expressive_components_determinatecircularwavyindicator] + +// [START android_compose_expressive_components_indeterminatecircularwavyindicator] +@Preview +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +fun IndeterminateCircularWavyProgressIndicatorSample() { + Column(horizontalAlignment = Alignment.CenterHorizontally) { CircularWavyProgressIndicator() } +} +// [END android_compose_expressive_components_indeterminatecircularwavyindicator] + +// [START android_compose_expressive_components_determinatelinearindicator] +@Preview +@Composable +fun LinearProgressIndicatorSample() { + var progress by remember { mutableFloatStateOf(0.1f) } + val animatedProgress by + animateFloatAsState( + targetValue = progress, + animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec, + ) + + Column(horizontalAlignment = Alignment.CenterHorizontally) { + LinearProgressIndicator(progress = { animatedProgress }) + Spacer(Modifier.requiredHeight(30.dp)) + Text("Set progress:") + Slider( + modifier = Modifier.width(300.dp), + value = progress, + valueRange = 0f..1f, + onValueChange = { progress = it }, + ) + } +} +// [END android_compose_expressive_components_determinatelinearindicator] + +// [START android_compose_expressive_components_indeterminatelinearindicator] +@Preview +@Composable +fun IndeterminateLinearProgressIndicatorSample() { + Column(horizontalAlignment = Alignment.CenterHorizontally) { LinearProgressIndicator() } +} +// [END android_compose_expressive_components_indeterminatelinearindicator] + +// [START android_compose_expressive_components_determinatecircularindicator] +@Preview +@Composable +fun CircularProgressIndicatorSample() { + var progress by remember { mutableFloatStateOf(0.1f) } + val animatedProgress by + animateFloatAsState( + targetValue = progress, + animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec, + ) + + Column(horizontalAlignment = Alignment.CenterHorizontally) { + CircularProgressIndicator(progress = { animatedProgress }) + Spacer(Modifier.requiredHeight(30.dp)) + Text("Set progress:") + Slider( + modifier = Modifier.width(300.dp), + value = progress, + valueRange = 0f..1f, + onValueChange = { progress = it }, + ) + } +} +// [END android_compose_expressive_components_determinatecircularindicator] + +// [START android_compose_expressive_components_indeterminatecircularindicator] +@Preview +@Composable +fun IndeterminateCircularProgressIndicatorSample() { + Column(horizontalAlignment = Alignment.CenterHorizontally) { CircularProgressIndicator() } +} +// [END android_compose_expressive_components_indeterminatecircularindicator] diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 41fcb2866..78ec8b756 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,6 +9,7 @@ androidx-appfunctions = "1.0.0-alpha10" androidx-cameraX = "1.6.1" androidx-car = "1.7.0" androidx-compose-bom = "2026.06.01" +androidx-compose-material3 = "1.5.0-alpha18" androidx-compose-ui-test = "1.7.0-alpha08" androidx-compose-ui-test-junit4-accessibility = "1.11.4" androidx-concurrent = "1.3.0" @@ -157,7 +158,7 @@ androidx-compose-foundation-layout = { module = "androidx.compose.foundation:fou androidx-compose-material = { module = "androidx.compose.material:material", version.ref = "compose-latest" } androidx-compose-material-iconsExtended = { module = "androidx.compose.material:material-icons-extended" } androidx-compose-material-ripple = { module = "androidx.compose.material:material-ripple", version.ref = "compose-latest" } -androidx-compose-material3 = { module = "androidx.compose.material3:material3" } +androidx-compose-material3 = { module = "androidx.compose.material3:material3", version.ref = "androidx-compose-material3" } androidx-compose-material3-adaptive = { module = "androidx.compose.material3.adaptive:adaptive", version.ref = "material3-adaptive" } androidx-compose-material3-adaptive-layout = { module = "androidx.compose.material3.adaptive:adaptive-layout", version.ref = "material3-adaptive" } androidx-compose-material3-adaptive-navigation = { module = "androidx.compose.material3.adaptive:adaptive-navigation", version.ref = "material3-adaptive" } From 37cb80d6119cafc1eb4fb282052341075de80ac5 Mon Sep 17 00:00:00 2001 From: Aniket Kamble Date: Wed, 12 Aug 2026 12:00:20 +0530 Subject: [PATCH 2/2] Migrate 1P compose code to 3P DAC --- .../java/com/example/compose/snippets/components/IconButton.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/components/IconButton.kt b/compose/snippets/src/main/java/com/example/compose/snippets/components/IconButton.kt index daea99531..13d4f2c75 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/components/IconButton.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/components/IconButton.kt @@ -160,4 +160,4 @@ fun IconToggleButtonWithAnimatedShapeSample() { } } } -// [END android_compose_expressive_components_animatedtoggleiconbuttons] \ No newline at end of file +// [END android_compose_expressive_components_animatedtoggleiconbuttons]