Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make the Adaptive Layout Codelab edge-to-edge #485

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@

package com.example.reply.ui

import android.os.Build
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
Expand All @@ -34,6 +36,11 @@ class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

enableEdgeToEdge()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haven't seen this done before, should we add it to the compose docs for edge to edge?

window.isNavigationBarContrastEnforced = false
}

setContent {
ReplyTheme {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,15 @@ import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.calculateEndPadding
import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
Expand All @@ -42,9 +48,13 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import com.example.reply.R
import com.example.reply.data.Email
Expand All @@ -55,7 +65,13 @@ fun ReplyListPane(
onEmailClick: (Email) -> Unit,
modifier: Modifier = Modifier
) {
LazyColumn(modifier = modifier.fillMaxWidth()) {
val layoutDirection = LocalLayoutDirection.current

LazyColumn(
modifier = modifier.fillMaxWidth(),
contentPadding = WindowInsets.safeDrawing.asPaddingValues()
.copy(layoutDirection, bottom = 0.dp)
Comment on lines +72 to +73

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be done with built-in APIs as

Suggested change
contentPadding = WindowInsets.safeDrawing.asPaddingValues()
.copy(layoutDirection, bottom = 0.dp)
contentPadding = WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top).asPaddingValues()

) {
item {
ReplySearchBar(modifier = Modifier.fillMaxWidth())
}
Expand All @@ -73,7 +89,15 @@ fun ReplyDetailPane(
email: Email,
modifier: Modifier = Modifier
) {
LazyColumn(modifier = modifier.fillMaxWidth()) {

val layoutDirection = LocalLayoutDirection.current

LazyColumn(
modifier = modifier.fillMaxWidth(),
contentPadding = WindowInsets.safeDrawing.asPaddingValues()
.copy(layoutDirection = layoutDirection, bottom = 0.dp)
Comment on lines +97 to +98

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto here

Suggested change
contentPadding = WindowInsets.safeDrawing.asPaddingValues()
.copy(layoutDirection = layoutDirection, bottom = 0.dp)
contentPadding = WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top).asPaddingValues()


) {
item {
ReplyEmailThreadItem(email)
}
Expand Down Expand Up @@ -156,13 +180,13 @@ fun ReplyEmailThreadItem(
modifier: Modifier = Modifier
) {
Card(
modifier = modifier.padding(horizontal = 16.dp, vertical = 4.dp),
modifier = modifier.padding(horizontal = 16.dp),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface)
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(20.dp)
.padding(horizontal = 20.dp)
) {
Row(modifier = Modifier.fillMaxWidth()) {
ReplyProfileImage(
Expand Down Expand Up @@ -264,7 +288,7 @@ fun ReplySearchBar(modifier: Modifier = Modifier) {
Row(
modifier = modifier
.fillMaxWidth()
.padding(16.dp)
.padding(horizontal = 16.dp)
.background(MaterialTheme.colorScheme.surface, CircleShape),
verticalAlignment = Alignment.CenterVertically
) {
Expand All @@ -291,3 +315,17 @@ fun ReplySearchBar(modifier: Modifier = Modifier) {
)
}
}

private fun PaddingValues.copy(
layoutDirection: LayoutDirection,
start: Dp? = null,
top: Dp? = null,
end: Dp? = null,
bottom: Dp? = null,
) = PaddingValues(
start = start ?: calculateStartPadding(layoutDirection),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be removed entirely with the alternate APIs above, but one subtle behavior issue that can arise with this code:

This causes calculateStartPadding and other calculate* methods to be invoked during composition. The correct inset values (as exposed in the PaddingValues) won't necessarily be correct during composition, so anything based on the values that are computed during composition can end up resulting in being off by one frame.

A solution to this here would be to setup a PaddingValues that defers calling the calculate* values on the source PaddingValues, so that they are read later when the inset values will be correct:

fun PaddingValues.copy(
    start: Dp? = null,
    top: Dp? = null,
    end: Dp? = null,
    bottom: Dp? = null,
) = object : PaddingValues {
    override fun calculateBottomPadding(): Dp =
        bottom ?: this@copy.calculateBottomPadding()

    override fun calculateLeftPadding(layoutDirection: LayoutDirection): Dp =
        when (layoutDirection) {
            LayoutDirection.Ltr -> start
            LayoutDirection.Rtl -> end
        } ?: this@copy.calculateLeftPadding(layoutDirection)

    override fun calculateRightPadding(layoutDirection: LayoutDirection): Dp =
        when (layoutDirection) {
            LayoutDirection.Ltr -> end
            LayoutDirection.Rtl -> start
        } ?: this@copy.calculateRightPadding(layoutDirection)

    override fun calculateTopPadding(): Dp =
        top ?: this@copy.calculateTopPadding()
}

top = top ?: calculateTopPadding(),
end = end ?: calculateEndPadding(layoutDirection),
bottom = bottom ?: calculateBottomPadding(),
)

Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package com.example.reply.ui.theme

import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
Expand All @@ -25,11 +24,7 @@ import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.view.ViewCompat

// Material 3 color schemes
private val replyDarkColorScheme = darkColorScheme(
Expand Down Expand Up @@ -104,13 +99,6 @@ fun ReplyTheme(
darkTheme -> replyDarkColorScheme
else -> replyLightColorScheme
}
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
(view.context as Activity).window.statusBarColor = replyColorScheme.primary.toArgb()
ViewCompat.getWindowInsetsController(view)?.isAppearanceLightStatusBars = darkTheme
}
}

MaterialTheme(
colorScheme = replyColorScheme,
Expand Down