Merge pull request #242 from android/av/built-in-insets

Update material3 with built-in insets support
pull/304/head
Alex Vanyo 2 years ago committed by GitHub
commit 14ae167b69
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -31,7 +31,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.windowsizeclass.WindowSizeClass
import androidx.compose.runtime.Composable
@ -45,6 +44,7 @@ import androidx.compose.ui.semantics.testTagsAsResourceId
import androidx.navigation.NavDestination
import androidx.navigation.NavDestination.Companion.hierarchy
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaBackground
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaGradientBackground
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaNavigationBar
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaNavigationBarItem
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaNavigationRail
@ -52,6 +52,7 @@ import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaNavig
import com.google.samples.apps.nowinandroid.core.designsystem.icon.Icon.DrawableResourceIcon
import com.google.samples.apps.nowinandroid.core.designsystem.icon.Icon.ImageVectorIcon
import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
import com.google.samples.apps.nowinandroid.feature.foryou.navigation.ForYouDestination
import com.google.samples.apps.nowinandroid.navigation.NiaNavHost
import com.google.samples.apps.nowinandroid.navigation.TopLevelDestination
@ -66,13 +67,20 @@ fun NiaApp(
appState: NiaAppState = rememberNiaAppState(windowSizeClass)
) {
NiaTheme {
NiaBackground {
val background: @Composable (@Composable () -> Unit) -> Unit =
when (appState.currentDestination?.route) {
ForYouDestination.route -> { content -> NiaGradientBackground(content = content) }
else -> { content -> NiaBackground(content = content) }
}
background {
Scaffold(
modifier = Modifier.semantics {
testTagsAsResourceId = true
},
containerColor = Color.Transparent,
contentColor = MaterialTheme.colorScheme.onBackground,
contentWindowInsets = WindowInsets(0, 0, 0, 0),
bottomBar = {
if (appState.shouldShowBottomBar) {
NiaBottomBar(
@ -158,42 +166,32 @@ private fun NiaBottomBar(
onNavigateToDestination: (TopLevelDestination) -> Unit,
currentDestination: NavDestination?
) {
// Wrap the navigation bar in a surface so the color behind the system
// navigation is equal to the container color of the navigation bar.
Surface(color = MaterialTheme.colorScheme.surface) {
NiaNavigationBar(
modifier = Modifier.windowInsetsPadding(
WindowInsets.safeDrawing.only(
WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom
)
NiaNavigationBar {
destinations.forEach { destination ->
val selected =
currentDestination?.hierarchy?.any { it.route == destination.route } == true
NiaNavigationBarItem(
selected = selected,
onClick = { onNavigateToDestination(destination) },
icon = {
val icon = if (selected) {
destination.selectedIcon
} else {
destination.unselectedIcon
}
when (icon) {
is ImageVectorIcon -> Icon(
imageVector = icon.imageVector,
contentDescription = null
)
is DrawableResourceIcon -> Icon(
painter = painterResource(id = icon.id),
contentDescription = null
)
}
},
label = { Text(stringResource(destination.iconTextId)) }
)
) {
destinations.forEach { destination ->
val selected =
currentDestination?.hierarchy?.any { it.route == destination.route } == true
NiaNavigationBarItem(
selected = selected,
onClick = { onNavigateToDestination(destination) },
icon = {
val icon = if (selected) {
destination.selectedIcon
} else {
destination.unselectedIcon
}
when (icon) {
is ImageVectorIcon -> Icon(
imageVector = icon.imageVector,
contentDescription = null
)
is DrawableResourceIcon -> Icon(
painter = painterResource(id = icon.id),
contentDescription = null
)
}
},
label = { Text(stringResource(destination.iconTextId)) }
)
}
}
}
}

@ -16,13 +16,13 @@
package com.google.samples.apps.nowinandroid.core.designsystem.component
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilterChip
import androidx.compose.material3.FilterChipDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ProvideTextStyle
import androidx.compose.material3.Shapes
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@ -58,13 +58,13 @@ fun NiaFilterChip(
},
modifier = modifier,
enabled = enabled,
selectedIcon = {
trailingIcon = {
Icon(
imageVector = NiaIcons.Check,
contentDescription = null
)
},
shape = Shapes.Full,
shape = CircleShape,
border = FilterChipDefaults.filterChipBorder(
borderColor = MaterialTheme.colorScheme.onBackground,
selectedBorderColor = MaterialTheme.colorScheme.onBackground,

@ -88,7 +88,6 @@ fun NiaNavigationBar(
) {
NavigationBar(
modifier = modifier,
containerColor = NiaNavigationDefaults.NavigationContainerColor,
contentColor = NiaNavigationDefaults.navigationContentColor(),
tonalElevation = 0.dp,
content = content
@ -155,7 +154,7 @@ fun NiaNavigationRail(
) {
NavigationRail(
modifier = modifier,
containerColor = NiaNavigationDefaults.NavigationContainerColor,
containerColor = Color.Transparent,
contentColor = NiaNavigationDefaults.navigationContentColor(),
header = header,
content = content
@ -166,7 +165,6 @@ fun NiaNavigationRail(
* Now in Android navigation default values.
*/
object NiaNavigationDefaults {
val NavigationContainerColor = Color.Transparent
@Composable
fun navigationContentColor() = MaterialTheme.colorScheme.onSurfaceVariant
@Composable

@ -21,6 +21,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
@ -33,6 +34,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun NiaTopAppBar(
@StringRes titleRes: Int,
@ -73,6 +75,7 @@ fun NiaTopAppBar(
/**
* Top app bar with action, displayed on the right
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun NiaTopAppBar(
@StringRes titleRes: Int,
@ -98,6 +101,7 @@ fun NiaTopAppBar(
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Preview("Top App Bar")
@Composable
fun NiaTopAppBarPreview() {

@ -21,14 +21,11 @@ import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.consumedWindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.windowInsetsBottomHeight
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.lazy.grid.GridCells.Adaptive
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
@ -45,7 +42,6 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaGradientBackground
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaTopAppBar
import com.google.samples.apps.nowinandroid.core.designsystem.icon.NiaIcons
import com.google.samples.apps.nowinandroid.core.ui.NewsFeedUiState
@ -72,46 +68,42 @@ fun BookmarksScreen(
removeFromBookmarks: (String) -> Unit,
modifier: Modifier = Modifier
) {
NiaGradientBackground {
Scaffold(
topBar = {
NiaTopAppBar(
titleRes = R.string.top_app_bar_title_saved,
actionIcon = NiaIcons.AccountCircle,
actionIconContentDescription = stringResource(
id = R.string.top_app_bar_action_menu
),
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
containerColor = Color.Transparent
),
modifier = Modifier.windowInsetsPadding(
WindowInsets.safeDrawing.only(WindowInsetsSides.Top)
)
)
},
containerColor = Color.Transparent
) { innerPadding ->
LazyVerticalGrid(
columns = Adaptive(300.dp),
contentPadding = PaddingValues(16.dp),
horizontalArrangement = Arrangement.spacedBy(32.dp),
verticalArrangement = Arrangement.spacedBy(24.dp),
modifier = modifier
.fillMaxSize()
.testTag("bookmarks:feed")
.padding(innerPadding)
.consumedWindowInsets(innerPadding)
) {
newsFeed(
feedState = feedState,
onNewsResourcesCheckedChanged = { id, _ -> removeFromBookmarks(id) },
showLoadingUIIfLoading = true,
loadingContentDescription = R.string.saved_loading
Scaffold(
topBar = {
NiaTopAppBar(
titleRes = R.string.top_app_bar_title_saved,
actionIcon = NiaIcons.AccountCircle,
actionIconContentDescription = stringResource(
id = R.string.top_app_bar_action_menu
),
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
containerColor = Color.Transparent
)
)
},
containerColor = Color.Transparent,
contentWindowInsets = WindowInsets(0, 0, 0, 0)
) { innerPadding ->
LazyVerticalGrid(
columns = Adaptive(300.dp),
contentPadding = PaddingValues(16.dp),
horizontalArrangement = Arrangement.spacedBy(32.dp),
verticalArrangement = Arrangement.spacedBy(24.dp),
modifier = modifier
.fillMaxSize()
.testTag("bookmarks:feed")
.padding(innerPadding)
.consumedWindowInsets(innerPadding)
) {
newsFeed(
feedState = feedState,
onNewsResourcesCheckedChanged = { id, _ -> removeFromBookmarks(id) },
showLoadingUIIfLoading = true,
loadingContentDescription = R.string.saved_loading
)
item(span = { GridItemSpan(maxLineSpan) }) {
Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.safeDrawing))
}
item(span = { GridItemSpan(maxLineSpan) }) {
Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.safeDrawing))
}
}
}

@ -25,19 +25,16 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.consumedWindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.windowInsetsBottomHeight
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.grid.GridCells
@ -80,7 +77,6 @@ import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coil.compose.AsyncImage
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaFilledButton
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaGradientBackground
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaLoadingWheel
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaToggleButton
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaTopAppBar
@ -127,97 +123,93 @@ fun ForYouScreen(
onNewsResourcesCheckedChanged: (String, Boolean) -> Unit,
modifier: Modifier = Modifier,
) {
NiaGradientBackground {
Scaffold(
topBar = {
NiaTopAppBar(
titleRes = R.string.top_app_bar_title,
actionIcon = NiaIcons.AccountCircle,
actionIconContentDescription = stringResource(
id = R.string.for_you_top_app_bar_action_my_account
),
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
containerColor = Color.Transparent
),
modifier = Modifier.windowInsetsPadding(
WindowInsets.safeDrawing.only(WindowInsetsSides.Top)
)
Scaffold(
topBar = {
NiaTopAppBar(
titleRes = R.string.top_app_bar_title,
actionIcon = NiaIcons.AccountCircle,
actionIconContentDescription = stringResource(
id = R.string.for_you_top_app_bar_action_my_account
),
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
containerColor = Color.Transparent
)
},
containerColor = Color.Transparent
) { innerPadding ->
// Workaround to call Activity.reportFullyDrawn from Jetpack Compose.
// This code should be called when the UI is ready for use
// and relates to Time To Full Display.
val interestsLoaded =
interestsSelectionState !is ForYouInterestsSelectionUiState.Loading
val feedLoaded = feedState !is NewsFeedUiState.Loading
)
},
containerColor = Color.Transparent,
contentWindowInsets = WindowInsets(0, 0, 0, 0)
) { innerPadding ->
// Workaround to call Activity.reportFullyDrawn from Jetpack Compose.
// This code should be called when the UI is ready for use
// and relates to Time To Full Display.
val interestsLoaded =
interestsSelectionState !is ForYouInterestsSelectionUiState.Loading
val feedLoaded = feedState !is NewsFeedUiState.Loading
if (interestsLoaded && feedLoaded) {
val localView = LocalView.current
// We use Unit to call reportFullyDrawn only on the first recomposition,
// however it will be called again if this composable goes out of scope.
// Activity.reportFullyDrawn() has its own check for this
// and is safe to call multiple times though.
LaunchedEffect(Unit) {
// We're leveraging the fact, that the current view is directly set as content of Activity.
val activity = localView.context as? Activity ?: return@LaunchedEffect
// To be sure not to call in the middle of a frame draw.
localView.doOnPreDraw { activity.reportFullyDrawn() }
}
if (interestsLoaded && feedLoaded) {
val localView = LocalView.current
// We use Unit to call reportFullyDrawn only on the first recomposition,
// however it will be called again if this composable goes out of scope.
// Activity.reportFullyDrawn() has its own check for this
// and is safe to call multiple times though.
LaunchedEffect(Unit) {
// We're leveraging the fact, that the current view is directly set as content of Activity.
val activity = localView.context as? Activity ?: return@LaunchedEffect
// To be sure not to call in the middle of a frame draw.
localView.doOnPreDraw { activity.reportFullyDrawn() }
}
}
val tag = "forYou:feed"
val tag = "forYou:feed"
val lazyGridState = rememberLazyGridState()
TrackScrollJank(scrollableState = lazyGridState, stateName = tag)
val lazyGridState = rememberLazyGridState()
TrackScrollJank(scrollableState = lazyGridState, stateName = tag)
LazyVerticalGrid(
columns = Adaptive(300.dp),
contentPadding = PaddingValues(16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalArrangement = Arrangement.spacedBy(24.dp),
modifier = modifier
.padding(innerPadding)
.consumedWindowInsets(innerPadding)
.fillMaxSize()
.testTag("forYou:feed"),
state = lazyGridState
) {
interestsSelection(
interestsSelectionState = interestsSelectionState,
onAuthorCheckedChanged = onAuthorCheckedChanged,
onTopicCheckedChanged = onTopicCheckedChanged,
saveFollowedTopics = saveFollowedTopics,
// Custom LayoutModifier to remove the enforced parent 16.dp contentPadding
// from the LazyVerticalGrid and enable edge-to-edge scrolling for this section
interestsItemModifier = Modifier.layout { measurable, constraints ->
val placeable = measurable.measure(
constraints.copy(
maxWidth = constraints.maxWidth + 32.dp.roundToPx()
)
LazyVerticalGrid(
columns = Adaptive(300.dp),
contentPadding = PaddingValues(16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalArrangement = Arrangement.spacedBy(24.dp),
modifier = modifier
.padding(innerPadding)
.consumedWindowInsets(innerPadding)
.fillMaxSize()
.testTag("forYou:feed"),
state = lazyGridState
) {
interestsSelection(
interestsSelectionState = interestsSelectionState,
onAuthorCheckedChanged = onAuthorCheckedChanged,
onTopicCheckedChanged = onTopicCheckedChanged,
saveFollowedTopics = saveFollowedTopics,
// Custom LayoutModifier to remove the enforced parent 16.dp contentPadding
// from the LazyVerticalGrid and enable edge-to-edge scrolling for this section
interestsItemModifier = Modifier.layout { measurable, constraints ->
val placeable = measurable.measure(
constraints.copy(
maxWidth = constraints.maxWidth + 32.dp.roundToPx()
)
layout(placeable.width, placeable.height) {
placeable.place(0, 0)
}
)
layout(placeable.width, placeable.height) {
placeable.place(0, 0)
}
)
}
)
newsFeed(
feedState = feedState,
// Avoid showing a second loading wheel if we already are for the interests
// selection
showLoadingUIIfLoading =
interestsSelectionState !is ForYouInterestsSelectionUiState.Loading,
onNewsResourcesCheckedChanged = onNewsResourcesCheckedChanged,
loadingContentDescription = R.string.for_you_loading
)
newsFeed(
feedState = feedState,
// Avoid showing a second loading wheel if we already are for the interests
// selection
showLoadingUIIfLoading =
interestsSelectionState !is ForYouInterestsSelectionUiState.Loading,
onNewsResourcesCheckedChanged = onNewsResourcesCheckedChanged,
loadingContentDescription = R.string.for_you_loading
)
item(span = { GridItemSpan(maxLineSpan) }) {
Column {
Spacer(modifier = Modifier.height(8.dp))
Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.safeDrawing))
}
item(span = { GridItemSpan(maxLineSpan) }) {
Column {
Spacer(modifier = Modifier.height(8.dp))
Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.safeDrawing))
}
}
}

@ -17,10 +17,7 @@
package com.google.samples.apps.nowinandroid.feature.interests
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.windowInsetsTopHeight
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
@ -77,6 +74,7 @@ fun InterestsRoute(
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun InterestsScreen(
uiState: InterestsUiState,
@ -92,8 +90,6 @@ fun InterestsScreen(
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally
) {
Spacer(Modifier.windowInsetsTopHeight(WindowInsets.safeDrawing))
NiaTopAppBar(
titleRes = R.string.interests,
actionIcon = NiaIcons.MoreVert,

@ -7,7 +7,7 @@ androidxAppCompat = "1.5.1"
androidxCompose = "1.3.0-beta02"
androidxComposeRuntimeTracing = "1.0.0-alpha01"
androidxComposeCompiler = "1.3.1"
androidxComposeMaterial3 = "1.0.0-alpha13"
androidxComposeMaterial3 = "1.0.0-beta03"
androidxCore = "1.9.0"
androidxCoreSplashscreen = "1.0.0"
androidxCustomView = "1.0.0"

@ -28,7 +28,7 @@ dependencyResolutionManagement {
repositories {
// Register the AndroidX snapshot repository first so snapshots don't attempt (and fail)
// to download from the non-snapshot repositories
maven(url = "https://androidx.dev/snapshots/builds/8455591/artifacts/repository") {
maven(url = "https://androidx.dev/snapshots/builds/9042167/artifacts/repository") {
content {
// The AndroidX snapshot repository will only have androidx artifacts, don't
// bother trying to find other ones

Loading…
Cancel
Save