Add gradient background to For You Screen

Change-Id: I6a85ee82a78b1897020f1737503611bd100764a1
pull/2/head
Jolanda Verhoef 2 years ago committed by Don Turner
parent cce9402b04
commit ce0142b98a

@ -55,6 +55,7 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
@ -64,6 +65,7 @@ import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.google.samples.apps.nowinandroid.R
import com.google.samples.apps.nowinandroid.core.ui.ClearRippleTheme
import com.google.samples.apps.nowinandroid.core.ui.component.NiaBackground
import com.google.samples.apps.nowinandroid.core.ui.theme.NiaTheme
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
@ -78,41 +80,45 @@ fun NiaApp(windowSizeClass: WindowSizeClass) {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
Scaffold(
modifier = Modifier,
bottomBar = {
if (windowSizeClass.widthSizeClass == WindowWidthSizeClass.Compact) {
NiABottomBar(
navigationActions = navigationActions,
currentDestination = currentDestination
)
NiaBackground {
Scaffold(
modifier = Modifier,
containerColor = Color.Transparent,
contentColor = MaterialTheme.colorScheme.onBackground,
bottomBar = {
if (windowSizeClass.widthSizeClass == WindowWidthSizeClass.Compact) {
NiABottomBar(
navigationActions = navigationActions,
currentDestination = currentDestination
)
}
}
}
) { padding ->
Row(
Modifier
.fillMaxSize()
.windowInsetsPadding(
WindowInsets.safeDrawing.only(
WindowInsetsSides.Horizontal
) { padding ->
Row(
Modifier
.fillMaxSize()
.windowInsetsPadding(
WindowInsets.safeDrawing.only(
WindowInsetsSides.Horizontal
)
)
)
) {
if (windowSizeClass.widthSizeClass != WindowWidthSizeClass.Compact) {
NiANavRail(
navigationActions = navigationActions,
currentDestination = currentDestination,
modifier = Modifier.safeDrawingPadding()
) {
if (windowSizeClass.widthSizeClass != WindowWidthSizeClass.Compact) {
NiANavRail(
navigationActions = navigationActions,
currentDestination = currentDestination,
modifier = Modifier.safeDrawingPadding()
)
}
NiaNavGraph(
windowSizeClass = windowSizeClass,
navController = navController,
modifier = Modifier
.padding(padding)
.consumedWindowInsets(padding)
)
}
NiaNavGraph(
windowSizeClass = windowSizeClass,
navController = navController,
modifier = Modifier
.padding(padding)
.consumedWindowInsets(padding)
)
}
}
}

@ -0,0 +1,143 @@
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.samples.apps.nowinandroid.core.ui.component
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.google.samples.apps.nowinandroid.core.ui.theme.LocalBackgroundTheme
import com.google.samples.apps.nowinandroid.core.ui.theme.NiaTheme
/**
* The main background for the app.
* Uses [LocalBackgroundTheme] to set the color and tonal elevation of a [Surface].
*
* @param modifier Modifier to be applied to the background.
* @param content The background content.
*/
@Composable
fun NiaBackground(
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
val color = LocalBackgroundTheme.current.color
val tonalElevation = LocalBackgroundTheme.current.tonalElevation
Surface(
color = if (color == Color.Unspecified) Color.Transparent else color,
tonalElevation = if (tonalElevation == Dp.Unspecified) 0.dp else tonalElevation,
modifier = modifier.fillMaxSize(),
content = content
)
}
/**
* A gradient background for select screens, to be overlaid on top of [NiaBackground].
* Uses [LocalBackgroundTheme] to set the gradient colors of a [Box].
*
* @param modifier Modifier to be applied to the background.
* @param topColor The top gradient color to be rendered.
* @param bottomColor The bottom gradient color to be rendered.
* @param content The background content.
*/
@Composable
fun NiaGradientBackground(
modifier: Modifier = Modifier,
topColor: Color = LocalBackgroundTheme.current.primaryGradientColor,
bottomColor: Color = LocalBackgroundTheme.current.secondaryGradientColor,
content: @Composable () -> Unit
) {
val gradientModifier = Modifier.background(
Brush.verticalGradient(
listOf(
if (topColor == Color.Unspecified) Color.Transparent else topColor,
Color.Transparent,
if (bottomColor == Color.Unspecified) Color.Transparent else bottomColor
)
)
)
Box(
modifier = modifier
.fillMaxSize()
.then(gradientModifier)
) {
content()
}
}
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO, name = "Light theme")
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, name = "Dark theme")
@Composable
fun BackgroundDefault() {
NiaTheme {
NiaBackground(Modifier.size(100.dp), content = {})
}
}
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO, name = "Light theme")
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, name = "Dark theme")
@Composable
fun BackgroundDynamic() {
NiaTheme(dynamicColor = true) {
NiaBackground(Modifier.size(100.dp), content = {})
}
}
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO, name = "Light theme")
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, name = "Dark theme")
@Composable
fun BackgroundAndroid() {
NiaTheme(androidTheme = true) {
NiaBackground(Modifier.size(100.dp), content = {})
}
}
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO, name = "Light theme")
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, name = "Dark theme")
@Composable
fun GradientBackgroundDefault() {
NiaTheme {
NiaGradientBackground(Modifier.size(100.dp), content = {})
}
}
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO, name = "Light theme")
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, name = "Dark theme")
@Composable
fun GradientBackgroundDynamic() {
NiaTheme(dynamicColor = true) {
NiaGradientBackground(Modifier.size(100.dp), content = {})
}
}
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO, name = "Light theme")
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, name = "Dark theme")
@Composable
fun GradientBackgroundAndroid() {
NiaTheme(androidTheme = true) {
NiaGradientBackground(Modifier.size(100.dp), content = {})
}
}

@ -0,0 +1,41 @@
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.samples.apps.nowinandroid.core.ui.theme
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
/**
* A class to model background values for Now in Android,
* including color, tonal elevation and gradient colors.
*/
@Immutable
data class BackgroundTheme(
val color: Color = Color.Unspecified,
val tonalElevation: Dp = Dp.Unspecified,
val primaryGradientColor: Color = Color.Unspecified,
val secondaryGradientColor: Color = Color.Unspecified,
val tertiaryGradientColor: Color = Color.Unspecified,
val neutralGradientColor: Color = Color.Unspecified
)
/**
* A composition local for [BackgroundTheme].
*/
val LocalBackgroundTheme = staticCompositionLocalOf { BackgroundTheme() }

@ -17,6 +17,8 @@
package com.google.samples.apps.nowinandroid.core.ui.theme
import androidx.compose.ui.graphics.Color
import androidx.core.graphics.ColorUtils
import kotlin.math.roundToInt
/**
* Now in Android colors.
@ -27,6 +29,7 @@ val Blue30 = Color(0xFF004D61)
val Blue40 = Color(0xFF006781)
val Blue80 = Color(0xFF5DD4FB)
val Blue90 = Color(0xFFB5EAFF)
val Blue95 = Color(0xFFDCF5FF)
val DarkGreen10 = Color(0xFF0D1F12)
val DarkGreen20 = Color(0xFF223526)
val DarkGreen30 = Color(0xFF394B3C)
@ -35,9 +38,11 @@ val DarkGreen80 = Color(0xFFB7CCB8)
val DarkGreen90 = Color(0xFFD3E8D3)
val DarkGreenGray10 = Color(0xFF1A1C1A)
val DarkGreenGray90 = Color(0xFFE2E3DE)
val DarkGreenGray95 = Color(0xFFF0F1EC)
val DarkGreenGray99 = Color(0xFFFBFDF7)
val DarkPurpleGray10 = Color(0xFF201A1B)
val DarkPurpleGray90 = Color(0xFFECDFE0)
val DarkPurpleGray95 = Color(0xFFFAEEEF)
val DarkPurpleGray99 = Color(0xFFFCFCFC)
val Green10 = Color(0xFF00210B)
val Green20 = Color(0xFF003919)
@ -56,12 +61,14 @@ val Orange30 = Color(0xFF812800)
val Orange40 = Color(0xFFA23F16)
val Orange80 = Color(0xFFFFB599)
val Orange90 = Color(0xFFFFDBCE)
val Orange95 = Color(0xFFFFEDE6)
val Purple10 = Color(0xFF36003D)
val Purple20 = Color(0xFF560A5E)
val Purple30 = Color(0xFF702776)
val Purple40 = Color(0xFF8C4190)
val Purple80 = Color(0xFFFFA8FF)
val Purple90 = Color(0xFFFFD5FC)
val Purple95 = Color(0xFFFFEBFB)
val PurpleGray30 = Color(0xFF4E444C)
val PurpleGray50 = Color(0xFF7F747C)
val PurpleGray60 = Color(0xFF998D96)
@ -79,3 +86,23 @@ val Teal30 = Color(0xFF214D56)
val Teal40 = Color(0xFF3A656F)
val Teal80 = Color(0xFFA2CED9)
val Teal90 = Color(0xFFBEEAF6)
/**
* Lighten the current [Color] instance to the given [luminance].
*
* This is needed because we can't access the token values directly. For the dynamic color theme,
* this makes it impossible to get the 95% luminance token of the different theme colors.
* TODO: Link to bug
*/
internal fun Color.lighten(luminance: Float): Color {
val hsl = FloatArray(3)
ColorUtils.RGBToHSL(
(red * 256).roundToInt(),
(green * 256).roundToInt(),
(blue * 256).roundToInt(),
hsl
)
hsl[2] = luminance
val color = Color(ColorUtils.HSLToColor(hsl))
return color
}

@ -24,8 +24,10 @@ import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
/**
* Light default theme color scheme
@ -171,9 +173,40 @@ fun NiaTheme(
darkTheme -> DarkDefaultColorScheme
else -> LightDefaultColorScheme
}
MaterialTheme(
colorScheme = colorScheme,
typography = NiaTypography,
content = content
)
val backgroundTheme = when {
androidTheme && darkTheme -> BackgroundTheme(
color = Color.Black
)
androidTheme -> BackgroundTheme(
color = DarkGreenGray95
)
darkTheme -> BackgroundTheme(
color = colorScheme.surface,
tonalElevation = 2.dp
)
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> BackgroundTheme(
color = colorScheme.surface,
tonalElevation = 2.dp,
primaryGradientColor = colorScheme.primary.lighten(0.95f),
secondaryGradientColor = colorScheme.secondary.lighten(0.95f),
tertiaryGradientColor = colorScheme.tertiary.lighten(0.95f),
neutralGradientColor = colorScheme.surface.lighten(0.95f)
)
else -> BackgroundTheme(
color = colorScheme.surface,
tonalElevation = 2.dp,
primaryGradientColor = Purple95,
secondaryGradientColor = Orange95,
tertiaryGradientColor = Blue95,
neutralGradientColor = DarkPurpleGray95
)
}
CompositionLocalProvider(LocalBackgroundTheme provides backgroundTheme) {
MaterialTheme(
colorScheme = colorScheme,
typography = NiaTypography,
content = content
)
}
}

@ -84,6 +84,7 @@ import com.google.samples.apps.nowinandroid.core.model.data.SaveableNewsResource
import com.google.samples.apps.nowinandroid.core.model.data.Topic
import com.google.samples.apps.nowinandroid.core.ui.LoadingWheel
import com.google.samples.apps.nowinandroid.core.ui.NewsResourceCardExpanded
import com.google.samples.apps.nowinandroid.core.ui.component.NiaGradientBackground
import com.google.samples.apps.nowinandroid.core.ui.component.NiaToggleButton
import com.google.samples.apps.nowinandroid.core.ui.component.NiaTopAppBar
import com.google.samples.apps.nowinandroid.core.ui.icon.NiaIcons
@ -123,68 +124,70 @@ fun ForYouScreen(
onNewsResourcesCheckedChanged: (String, Boolean) -> Unit,
modifier: Modifier = Modifier,
) {
// TODO: Replace with `LazyVerticalGrid` when blocking bugs are fixed:
// https://issuetracker.google.com/issues/230514914
// https://issuetracker.google.com/issues/231320714
BoxWithConstraints(
modifier = modifier
) {
val numberOfColumns = when (windowSizeClass.widthSizeClass) {
WindowWidthSizeClass.Compact, WindowWidthSizeClass.Medium -> 1
else -> floor(maxWidth / 300.dp).toInt().coerceAtLeast(1)
}
NiaGradientBackground {
// TODO: Replace with `LazyVerticalGrid` when blocking bugs are fixed:
// https://issuetracker.google.com/issues/230514914
// https://issuetracker.google.com/issues/231320714
BoxWithConstraints(
modifier = modifier
) {
val numberOfColumns = when (windowSizeClass.widthSizeClass) {
WindowWidthSizeClass.Compact, WindowWidthSizeClass.Medium -> 1
else -> floor(maxWidth / 300.dp).toInt().coerceAtLeast(1)
}
LazyColumn(modifier = Modifier.fillMaxSize()) {
item {
Spacer(
// TODO: Replace with windowInsetsTopHeight after
// https://issuetracker.google.com/issues/230383055
Modifier.windowInsetsPadding(
WindowInsets.safeDrawing.only(WindowInsetsSides.Top)
LazyColumn(modifier = Modifier.fillMaxSize()) {
item {
Spacer(
// TODO: Replace with windowInsetsTopHeight after
// https://issuetracker.google.com/issues/230383055
Modifier.windowInsetsPadding(
WindowInsets.safeDrawing.only(WindowInsetsSides.Top)
)
)
)
}
}
item {
NiaTopAppBar(
titleRes = R.string.top_app_bar_title,
navigationIcon = Icons.Filled.Search,
navigationIconContentDescription = stringResource(
id = R.string.top_app_bar_navigation_button_content_desc
),
actionIcon = Icons.Outlined.AccountCircle,
actionIconContentDescription = stringResource(
id = R.string.top_app_bar_navigation_button_content_desc
),
)
}
item {
NiaTopAppBar(
titleRes = R.string.top_app_bar_title,
navigationIcon = Icons.Filled.Search,
navigationIconContentDescription = stringResource(
id = R.string.top_app_bar_navigation_button_content_desc
),
actionIcon = Icons.Outlined.AccountCircle,
actionIconContentDescription = stringResource(
id = R.string.top_app_bar_navigation_button_content_desc
),
)
}
InterestsSelection(
interestsSelectionState = interestsSelectionState,
showLoadingUIIfLoading = true,
onAuthorCheckedChanged = onAuthorCheckedChanged,
onTopicCheckedChanged = onTopicCheckedChanged,
saveFollowedTopics = saveFollowedTopics
)
InterestsSelection(
interestsSelectionState = interestsSelectionState,
showLoadingUIIfLoading = true,
onAuthorCheckedChanged = onAuthorCheckedChanged,
onTopicCheckedChanged = onTopicCheckedChanged,
saveFollowedTopics = saveFollowedTopics
)
Feed(
feedState = feedState,
// Avoid showing a second loading wheel if we already are for the interests
// selection
showLoadingUIIfLoading =
interestsSelectionState !is ForYouInterestsSelectionState.Loading,
numberOfColumns = numberOfColumns,
onNewsResourcesCheckedChanged = onNewsResourcesCheckedChanged
)
Feed(
feedState = feedState,
// Avoid showing a second loading wheel if we already are for the interests
// selection
showLoadingUIIfLoading =
interestsSelectionState !is ForYouInterestsSelectionState.Loading,
numberOfColumns = numberOfColumns,
onNewsResourcesCheckedChanged = onNewsResourcesCheckedChanged
)
item {
Spacer(
// TODO: Replace with windowInsetsBottomHeight after
// https://issuetracker.google.com/issues/230383055
Modifier.windowInsetsPadding(
WindowInsets.safeDrawing.only(WindowInsetsSides.Bottom)
item {
Spacer(
// TODO: Replace with windowInsetsBottomHeight after
// https://issuetracker.google.com/issues/230383055
Modifier.windowInsetsPadding(
WindowInsets.safeDrawing.only(WindowInsetsSides.Bottom)
)
)
)
}
}
}
}

Loading…
Cancel
Save