diff --git a/AnimationCodelab/finished/build.gradle b/AnimationCodelab/finished/build.gradle index 23fba0f11..f59ad5422 100644 --- a/AnimationCodelab/finished/build.gradle +++ b/AnimationCodelab/finished/build.gradle @@ -21,11 +21,11 @@ plugins { android { namespace "com.example.android.codelab.animation" - compileSdkVersion 34 + compileSdk 34 defaultConfig { applicationId 'com.example.android.codelab.animation' minSdkVersion 21 - targetSdkVersion 33 + targetSdk 34 versionCode 1 versionName '1.0' } @@ -51,17 +51,7 @@ dependencies { implementation 'androidx.activity:activity-compose:1.8.1' implementation 'androidx.core:core-ktx:1.12.0' implementation "androidx.compose.ui:ui" - implementation "androidx.compose.material:material" + implementation "androidx.compose.material3:material3" implementation "androidx.compose.ui:ui-tooling-preview" debugImplementation "androidx.compose.ui:ui-tooling" } - -// Compiler flag to use experimental Compose APIs -tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { - kotlinOptions { - jvmTarget = "1.8" - freeCompilerArgs += [ - "-opt-in=kotlin.RequiresOptIn" - ] - } -} diff --git a/AnimationCodelab/finished/src/main/java/com/example/android/codelab/animation/MainActivity.kt b/AnimationCodelab/finished/src/main/java/com/example/android/codelab/animation/MainActivity.kt index b364f3978..1ad646112 100644 --- a/AnimationCodelab/finished/src/main/java/com/example/android/codelab/animation/MainActivity.kt +++ b/AnimationCodelab/finished/src/main/java/com/example/android/codelab/animation/MainActivity.kt @@ -19,6 +19,7 @@ package com.example.android.codelab.animation import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge import com.example.android.codelab.animation.ui.AnimationCodelabTheme import com.example.android.codelab.animation.ui.home.Home @@ -26,6 +27,7 @@ class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + enableEdgeToEdge() setContent { AnimationCodelabTheme { Home() diff --git a/AnimationCodelab/finished/src/main/java/com/example/android/codelab/animation/ui/Color.kt b/AnimationCodelab/finished/src/main/java/com/example/android/codelab/animation/ui/Color.kt index a2fa16c58..f1c0e0b07 100644 --- a/AnimationCodelab/finished/src/main/java/com/example/android/codelab/animation/ui/Color.kt +++ b/AnimationCodelab/finished/src/main/java/com/example/android/codelab/animation/ui/Color.kt @@ -18,10 +18,11 @@ package com.example.android.codelab.animation.ui import androidx.compose.ui.graphics.Color -val Purple100 = Color(0xFFE1BEE7) -val Purple500 = Color(0xFF6200EE) -val Purple700 = Color(0xFF3700B3) -val Teal200 = Color(0xFF03DAC5) -val Green300 = Color(0xFF81C784) -val Green800 = Color(0xFF2E7D32) val Amber600 = Color(0xFFFFB300) + +val Melon = Color(0xFFfec5bb) +val PaleDogwood = Color(0xFFfcd5ce) +val Seashell = Color(0xFFf8edeb) +val Peach = Color(0xFFfec89a) +val Green = Color(0xFFd8e2dc) +val GreenLight = Color(0xFFEBF1EE) diff --git a/AnimationCodelab/finished/src/main/java/com/example/android/codelab/animation/ui/Theme.kt b/AnimationCodelab/finished/src/main/java/com/example/android/codelab/animation/ui/Theme.kt index 12a0fcd49..a46656ec5 100644 --- a/AnimationCodelab/finished/src/main/java/com/example/android/codelab/animation/ui/Theme.kt +++ b/AnimationCodelab/finished/src/main/java/com/example/android/codelab/animation/ui/Theme.kt @@ -16,19 +16,22 @@ package com.example.android.codelab.animation.ui -import androidx.compose.material.MaterialTheme -import androidx.compose.material.lightColors +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color @Composable fun AnimationCodelabTheme(content: @Composable () -> Unit) { - val colors = lightColors( - primary = Purple500, - primaryVariant = Purple700, - secondary = Teal200 + val colors = lightColorScheme( + primary = Melon, + primaryContainer = PaleDogwood, + onPrimary = Color.Black, + secondary = Peach, + onSecondary = Color.Black ) MaterialTheme( - colors = colors, + colorScheme = colors, content = content ) -} +} \ No newline at end of file diff --git a/AnimationCodelab/finished/src/main/java/com/example/android/codelab/animation/ui/home/Home.kt b/AnimationCodelab/finished/src/main/java/com/example/android/codelab/animation/ui/home/Home.kt index 5ca4a0deb..ec194df17 100644 --- a/AnimationCodelab/finished/src/main/java/com/example/android/codelab/animation/ui/home/Home.kt +++ b/AnimationCodelab/finished/src/main/java/com/example/android/codelab/animation/ui/home/Home.kt @@ -49,14 +49,23 @@ import androidx.compose.foundation.layout.Column 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.asPaddingValues +import androidx.compose.foundation.layout.calculateEndPadding +import androidx.compose.foundation.layout.consumeWindowInsets 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.offset +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.windowInsetsPadding +import androidx.compose.foundation.layout.windowInsetsTopHeight import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState @@ -64,17 +73,6 @@ import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.FloatingActionButton -import androidx.compose.material.Icon -import androidx.compose.material.IconButton -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Scaffold -import androidx.compose.material.Surface -import androidx.compose.material.TabPosition -import androidx.compose.material.TabRow -import androidx.compose.material.Text -import androidx.compose.material.TextButton import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AccountBox import androidx.compose.material.icons.filled.Check @@ -82,11 +80,22 @@ import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.filled.Home import androidx.compose.material.icons.filled.Info import androidx.compose.material.icons.filled.Refresh +import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface +import androidx.compose.material3.TabPosition +import androidx.compose.material3.TabRow +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.key +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -109,15 +118,16 @@ import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.example.android.codelab.animation.R import com.example.android.codelab.animation.ui.Amber600 import com.example.android.codelab.animation.ui.AnimationCodelabTheme -import com.example.android.codelab.animation.ui.Green300 -import com.example.android.codelab.animation.ui.Green800 -import com.example.android.codelab.animation.ui.Purple100 -import com.example.android.codelab.animation.ui.Purple700 +import com.example.android.codelab.animation.ui.Green +import com.example.android.codelab.animation.ui.GreenLight +import com.example.android.codelab.animation.ui.PaleDogwood +import com.example.android.codelab.animation.ui.Seashell import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -178,7 +188,10 @@ fun Home() { val lazyListState = rememberLazyListState() // The background color. The value is changed by the current tab. - val backgroundColor by animateColorAsState(if (tabPage == TabPage.Home) Purple100 else Green300) + val backgroundColor by animateColorAsState( + if (tabPage == TabPage.Home) Seashell else GreenLight, + label = "background color" + ) // The coroutine scope for event handlers calling suspend functions. val coroutineScope = rememberCoroutineScope() @@ -190,7 +203,7 @@ fun Home() { onTabSelected = { tabPage = it } ) }, - backgroundColor = backgroundColor, + containerColor = backgroundColor, floatingActionButton = { HomeFloatingActionButton( extended = lazyListState.isScrollingUp(), @@ -202,69 +215,71 @@ fun Home() { ) } ) { padding -> - LazyColumn( - contentPadding = PaddingValues(horizontal = 16.dp, vertical = 32.dp), - state = lazyListState, - modifier = Modifier.padding(padding) - ) { - // Weather - item { Header(title = stringResource(R.string.weather)) } - item { Spacer(modifier = Modifier.height(16.dp)) } - item { - Surface( - modifier = Modifier.fillMaxWidth(), - elevation = 2.dp - ) { - if (weatherLoading) { - LoadingRow() - } else { - WeatherRow(onRefresh = { - coroutineScope.launch { - loadWeather() - } - }) - } - } - } - item { Spacer(modifier = Modifier.height(32.dp)) } - - // Topics - item { Header(title = stringResource(R.string.topics)) } - item { Spacer(modifier = Modifier.height(16.dp)) } - items(allTopics) { topic -> - TopicRow( - topic = topic, - expanded = expandedTopic == topic, - onClick = { - expandedTopic = if (expandedTopic == topic) null else topic - } - ) - } - item { Spacer(modifier = Modifier.height(32.dp)) } - - // Tasks - item { Header(title = stringResource(R.string.tasks)) } - item { Spacer(modifier = Modifier.height(16.dp)) } - if (tasks.isEmpty()) { + Box(modifier = Modifier.padding( + top = padding.calculateTopPadding(), + start = padding.calculateLeftPadding(LayoutDirection.Ltr), + end = padding.calculateEndPadding(LayoutDirection.Ltr) + )) { + LazyColumn( + contentPadding = WindowInsets(16.dp, 32.dp, 16.dp, + padding.calculateBottomPadding() + 32.dp + ).asPaddingValues(), + state = lazyListState + ) { + // Weather + item { Header(title = stringResource(R.string.weather)) } + item { Spacer(modifier = Modifier.height(16.dp)) } item { - TextButton(onClick = { tasks.clear(); tasks.addAll(allTasks) }) { - Text(stringResource(R.string.add_tasks)) + Surface( + modifier = Modifier.fillMaxWidth(), + shadowElevation = 2.dp + ) { + if (weatherLoading) { + LoadingRow() + } else { + WeatherRow(onRefresh = { + coroutineScope.launch { + loadWeather() + } + }) + } } } - } - items(count = tasks.size) { i -> - val task = tasks.getOrNull(i) - if (task != null) { - key(task) { - TaskRow( - task = task, - onRemove = { tasks.remove(task) } - ) + item { Spacer(modifier = Modifier.height(32.dp)) } + + // Topics + item { Header(title = stringResource(R.string.topics)) } + item { Spacer(modifier = Modifier.height(16.dp)) } + items(allTopics) { topic -> + TopicRow( + topic = topic, + expanded = expandedTopic == topic, + onClick = { + expandedTopic = if (expandedTopic == topic) null else topic + } + ) + } + item { Spacer(modifier = Modifier.height(32.dp)) } + + // Tasks + item { Header(title = stringResource(R.string.tasks)) } + item { Spacer(modifier = Modifier.height(16.dp)) } + if (tasks.isEmpty()) { + item { + TextButton(onClick = { tasks.clear(); tasks.addAll(allTasks) }) { + Text(stringResource(R.string.add_tasks)) + } } } + items(tasks, key = { it }) { task -> + TaskRow( + task = task, + onRemove = { tasks.remove(task) } + ) + } } + EditMessage(editMessageShown) } - EditMessage(editMessageShown) } } @@ -321,11 +336,12 @@ private fun EditMessage(shown: Boolean) { ) { Surface( modifier = Modifier.fillMaxWidth(), - color = MaterialTheme.colors.secondary, - elevation = 4.dp + color = MaterialTheme.colorScheme.secondary, + shadowElevation = 4.dp ) { Text( text = stringResource(R.string.edit_message), + color = MaterialTheme.colorScheme.onSecondary, modifier = Modifier.padding(16.dp) ) } @@ -337,8 +353,8 @@ private fun EditMessage(shown: Boolean) { */ @Composable private fun LazyListState.isScrollingUp(): Boolean { - var previousIndex by remember(this) { mutableStateOf(firstVisibleItemIndex) } - var previousScrollOffset by remember(this) { mutableStateOf(firstVisibleItemScrollOffset) } + var previousIndex by remember(this) { mutableIntStateOf(firstVisibleItemIndex) } + var previousScrollOffset by remember(this) { mutableIntStateOf(firstVisibleItemScrollOffset) } return remember(this) { derivedStateOf { if (previousIndex != firstVisibleItemIndex) { @@ -365,7 +381,7 @@ private fun Header( Text( text = title, modifier = Modifier.semantics { heading() }, - style = MaterialTheme.typography.h5 + style = MaterialTheme.typography.headlineLarge ) } @@ -376,14 +392,13 @@ private fun Header( * @param expanded Whether the row should be shown expanded with the topic body. * @param onClick Called when the row is clicked. */ -@OptIn(ExperimentalMaterialApi::class) @Composable private fun TopicRow(topic: String, expanded: Boolean, onClick: () -> Unit) { TopicRowSpacer(visible = expanded) Surface( modifier = Modifier .fillMaxWidth(), - elevation = 2.dp, + shadowElevation = 2.dp, onClick = onClick ) { Column( @@ -401,7 +416,7 @@ private fun TopicRow(topic: String, expanded: Boolean, onClick: () -> Unit) { Spacer(modifier = Modifier.width(16.dp)) Text( text = topic, - style = MaterialTheme.typography.body1 + style = MaterialTheme.typography.bodySmall ) } if (expanded) { @@ -439,23 +454,27 @@ private fun HomeTabBar( tabPage: TabPage, onTabSelected: (tabPage: TabPage) -> Unit ) { - TabRow( - selectedTabIndex = tabPage.ordinal, - backgroundColor = backgroundColor, - indicator = { tabPositions -> - HomeTabIndicator(tabPositions, tabPage) + Column(modifier = Modifier.windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal))) { + Spacer(Modifier.windowInsetsTopHeight(WindowInsets.safeDrawing)) + TabRow( + selectedTabIndex = tabPage.ordinal, + containerColor = backgroundColor, + contentColor = MaterialTheme.colorScheme.onPrimary, + indicator = { tabPositions -> + HomeTabIndicator(tabPositions, tabPage) + } + ) { + HomeTab( + icon = Icons.Default.Home, + title = stringResource(R.string.home), + onClick = { onTabSelected(TabPage.Home) } + ) + HomeTab( + icon = Icons.Default.AccountBox, + title = stringResource(R.string.work), + onClick = { onTabSelected(TabPage.Work) } + ) } - ) { - HomeTab( - icon = Icons.Default.Home, - title = stringResource(R.string.home), - onClick = { onTabSelected(TabPage.Home) } - ) - HomeTab( - icon = Icons.Default.AccountBox, - title = stringResource(R.string.work), - onClick = { onTabSelected(TabPage.Work) } - ) } } @@ -509,7 +528,7 @@ private fun HomeTabIndicator( val color by transition.animateColor( label = "Border color" ) { page -> - if (page == TabPage.Home) Purple700 else Green800 + if (page == TabPage.Home) PaleDogwood else Green } Box( Modifier @@ -596,7 +615,7 @@ private fun WeatherRow( @Composable private fun LoadingRow() { // Creates an `InfiniteTransition` that runs infinite child animation values. - val infiniteTransition = rememberInfiniteTransition() + val infiniteTransition = rememberInfiniteTransition(label = "infinite loading") val alpha by infiniteTransition.animateFloat( initialValue = 0f, targetValue = 1f, @@ -612,7 +631,8 @@ private fun LoadingRow() { // When the value finishes animating from 0f to 1f, it repeats by reversing the // animation direction. repeatMode = RepeatMode.Reverse - ) + ), + label = "alpha" ) Row( modifier = Modifier @@ -648,7 +668,7 @@ private fun TaskRow(task: String, onRemove: () -> Unit) { modifier = Modifier .fillMaxWidth() .swipeToDismiss(onRemove), - elevation = 2.dp + shadowElevation = 2.dp ) { Row( modifier = Modifier @@ -662,7 +682,7 @@ private fun TaskRow(task: String, onRemove: () -> Unit) { Spacer(modifier = Modifier.width(16.dp)) Text( text = task, - style = MaterialTheme.typography.body1 + style = MaterialTheme.typography.bodySmall ) } } @@ -735,11 +755,13 @@ private fun Modifier.swipeToDismiss( @Preview @Composable private fun PreviewHomeTabBar() { - HomeTabBar( - backgroundColor = Purple100, - tabPage = TabPage.Home, - onTabSelected = {} - ) + AnimationCodelabTheme { + HomeTabBar( + backgroundColor = Color.White, + tabPage = TabPage.Home, + onTabSelected = {} + ) + } } @Preview diff --git a/AnimationCodelab/start/build.gradle b/AnimationCodelab/start/build.gradle index 23fba0f11..f59ad5422 100644 --- a/AnimationCodelab/start/build.gradle +++ b/AnimationCodelab/start/build.gradle @@ -21,11 +21,11 @@ plugins { android { namespace "com.example.android.codelab.animation" - compileSdkVersion 34 + compileSdk 34 defaultConfig { applicationId 'com.example.android.codelab.animation' minSdkVersion 21 - targetSdkVersion 33 + targetSdk 34 versionCode 1 versionName '1.0' } @@ -51,17 +51,7 @@ dependencies { implementation 'androidx.activity:activity-compose:1.8.1' implementation 'androidx.core:core-ktx:1.12.0' implementation "androidx.compose.ui:ui" - implementation "androidx.compose.material:material" + implementation "androidx.compose.material3:material3" implementation "androidx.compose.ui:ui-tooling-preview" debugImplementation "androidx.compose.ui:ui-tooling" } - -// Compiler flag to use experimental Compose APIs -tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { - kotlinOptions { - jvmTarget = "1.8" - freeCompilerArgs += [ - "-opt-in=kotlin.RequiresOptIn" - ] - } -} diff --git a/AnimationCodelab/start/src/main/java/com/example/android/codelab/animation/MainActivity.kt b/AnimationCodelab/start/src/main/java/com/example/android/codelab/animation/MainActivity.kt index b364f3978..1ad646112 100644 --- a/AnimationCodelab/start/src/main/java/com/example/android/codelab/animation/MainActivity.kt +++ b/AnimationCodelab/start/src/main/java/com/example/android/codelab/animation/MainActivity.kt @@ -19,6 +19,7 @@ package com.example.android.codelab.animation import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge import com.example.android.codelab.animation.ui.AnimationCodelabTheme import com.example.android.codelab.animation.ui.home.Home @@ -26,6 +27,7 @@ class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + enableEdgeToEdge() setContent { AnimationCodelabTheme { Home() diff --git a/AnimationCodelab/start/src/main/java/com/example/android/codelab/animation/ui/Color.kt b/AnimationCodelab/start/src/main/java/com/example/android/codelab/animation/ui/Color.kt index a2fa16c58..2f33317e6 100644 --- a/AnimationCodelab/start/src/main/java/com/example/android/codelab/animation/ui/Color.kt +++ b/AnimationCodelab/start/src/main/java/com/example/android/codelab/animation/ui/Color.kt @@ -18,10 +18,11 @@ package com.example.android.codelab.animation.ui import androidx.compose.ui.graphics.Color -val Purple100 = Color(0xFFE1BEE7) -val Purple500 = Color(0xFF6200EE) -val Purple700 = Color(0xFF3700B3) -val Teal200 = Color(0xFF03DAC5) -val Green300 = Color(0xFF81C784) -val Green800 = Color(0xFF2E7D32) val Amber600 = Color(0xFFFFB300) + +val Melon = Color(0xFFfec5bb) +val PaleDogwood = Color(0xFFfcd5ce) +val Seashell = Color(0xFFf8edeb) +val Peach = Color(0xFFfec89a) +val Green = Color(0xFFd8e2dc) +val GreenLight = Color(0xFFEBF1EE) \ No newline at end of file diff --git a/AnimationCodelab/start/src/main/java/com/example/android/codelab/animation/ui/Theme.kt b/AnimationCodelab/start/src/main/java/com/example/android/codelab/animation/ui/Theme.kt index 12a0fcd49..46d7cd068 100644 --- a/AnimationCodelab/start/src/main/java/com/example/android/codelab/animation/ui/Theme.kt +++ b/AnimationCodelab/start/src/main/java/com/example/android/codelab/animation/ui/Theme.kt @@ -16,19 +16,23 @@ package com.example.android.codelab.animation.ui -import androidx.compose.material.MaterialTheme -import androidx.compose.material.lightColors +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color @Composable fun AnimationCodelabTheme(content: @Composable () -> Unit) { - val colors = lightColors( - primary = Purple500, - primaryVariant = Purple700, - secondary = Teal200 + val colors = lightColorScheme( + primary = Melon, + primaryContainer = PaleDogwood, + onPrimary = Color.Black, + secondary = Peach, + onSecondary = Color.Black ) MaterialTheme( - colors = colors, + colorScheme = colors, content = content ) } + diff --git a/AnimationCodelab/start/src/main/java/com/example/android/codelab/animation/ui/home/Home.kt b/AnimationCodelab/start/src/main/java/com/example/android/codelab/animation/ui/home/Home.kt index ecd607065..0ea248583 100644 --- a/AnimationCodelab/start/src/main/java/com/example/android/codelab/animation/ui/home/Home.kt +++ b/AnimationCodelab/start/src/main/java/com/example/android/codelab/animation/ui/home/Home.kt @@ -17,6 +17,23 @@ package com.example.android.codelab.animation.ui.home import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.animateColor +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.animateContentSize +import androidx.compose.animation.core.FastOutLinearInEasing +import androidx.compose.animation.core.LinearOutSlowInEasing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.animateDp +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.keyframes +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.spring +import androidx.compose.animation.core.tween +import androidx.compose.animation.core.updateTransition +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically import androidx.compose.animation.splineBasedDecay import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background @@ -30,14 +47,26 @@ import androidx.compose.foundation.layout.Column 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.WindowInsetsSides.Companion.Horizontal +import androidx.compose.foundation.layout.WindowInsetsSides.Companion.Top +import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.calculateEndPadding +import androidx.compose.foundation.layout.consumeWindowInsets 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.offset +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.systemBars import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.layout.windowInsetsTopHeight import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState @@ -45,17 +74,6 @@ import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.FloatingActionButton -import androidx.compose.material.Icon -import androidx.compose.material.IconButton -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Scaffold -import androidx.compose.material.Surface -import androidx.compose.material.TabPosition -import androidx.compose.material.TabRow -import androidx.compose.material.Text -import androidx.compose.material.TextButton import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AccountBox import androidx.compose.material.icons.filled.Check @@ -63,11 +81,22 @@ import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.filled.Home import androidx.compose.material.icons.filled.Info import androidx.compose.material.icons.filled.Refresh +import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface +import androidx.compose.material3.TabPosition +import androidx.compose.material3.TabRow +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.key +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -79,6 +108,7 @@ import androidx.compose.ui.composed import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Color.Companion.White import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.positionChange @@ -90,15 +120,16 @@ import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.example.android.codelab.animation.R import com.example.android.codelab.animation.ui.Amber600 import com.example.android.codelab.animation.ui.AnimationCodelabTheme -import com.example.android.codelab.animation.ui.Green300 -import com.example.android.codelab.animation.ui.Green800 -import com.example.android.codelab.animation.ui.Purple100 -import com.example.android.codelab.animation.ui.Purple700 +import com.example.android.codelab.animation.ui.Green +import com.example.android.codelab.animation.ui.GreenLight +import com.example.android.codelab.animation.ui.PaleDogwood +import com.example.android.codelab.animation.ui.Seashell import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -158,7 +189,7 @@ fun Home() { // The background color. The value is changed by the current tab. // TODO 1: Animate this color change. - val backgroundColor = if (tabPage == TabPage.Home) Purple100 else Green300 + val backgroundColor = if (tabPage == TabPage.Home) Seashell else GreenLight // The coroutine scope for event handlers calling suspend functions. val coroutineScope = rememberCoroutineScope() @@ -170,7 +201,7 @@ fun Home() { onTabSelected = { tabPage = it } ) }, - backgroundColor = backgroundColor, + containerColor = backgroundColor, floatingActionButton = { HomeFloatingActionButton( extended = lazyListState.isScrollingUp(), @@ -182,69 +213,74 @@ fun Home() { ) } ) { padding -> - LazyColumn( - contentPadding = PaddingValues(horizontal = 16.dp, vertical = 32.dp), - state = lazyListState, - modifier = Modifier.padding(padding) - ) { - // Weather - item { Header(title = stringResource(R.string.weather)) } - item { Spacer(modifier = Modifier.height(16.dp)) } - item { - Surface( - modifier = Modifier.fillMaxWidth(), - elevation = 2.dp - ) { - if (weatherLoading) { - LoadingRow() - } else { - WeatherRow(onRefresh = { - coroutineScope.launch { - loadWeather() - } - }) - } - } - } - item { Spacer(modifier = Modifier.height(32.dp)) } - - // Topics - item { Header(title = stringResource(R.string.topics)) } - item { Spacer(modifier = Modifier.height(16.dp)) } - items(allTopics) { topic -> - TopicRow( - topic = topic, - expanded = expandedTopic == topic, - onClick = { - expandedTopic = if (expandedTopic == topic) null else topic - } - ) - } - item { Spacer(modifier = Modifier.height(32.dp)) } - - // Tasks - item { Header(title = stringResource(R.string.tasks)) } - item { Spacer(modifier = Modifier.height(16.dp)) } - if (tasks.isEmpty()) { + Box(modifier = Modifier.padding( + top = padding.calculateTopPadding(), + start = padding.calculateLeftPadding(LayoutDirection.Ltr), + end = padding.calculateEndPadding(LayoutDirection.Ltr) + )) { + LazyColumn( + contentPadding = WindowInsets( + 16.dp, + 32.dp, + 16.dp, + padding.calculateBottomPadding() + 32.dp + ).asPaddingValues(), + state = lazyListState + ) { + // Weather + item { Header(title = stringResource(R.string.weather)) } + item { Spacer(modifier = Modifier.height(16.dp)) } item { - TextButton(onClick = { tasks.clear(); tasks.addAll(allTasks) }) { - Text(stringResource(R.string.add_tasks)) + Surface( + modifier = Modifier.fillMaxWidth(), + shadowElevation = 2.dp + ) { + if (weatherLoading) { + LoadingRow() + } else { + WeatherRow(onRefresh = { + coroutineScope.launch { + loadWeather() + } + }) + } } } - } - items(count = tasks.size) { i -> - val task = tasks.getOrNull(i) - if (task != null) { - key(task) { - TaskRow( - task = task, - onRemove = { tasks.remove(task) } - ) + item { Spacer(modifier = Modifier.height(32.dp)) } + + // Topics + item { Header(title = stringResource(R.string.topics)) } + item { Spacer(modifier = Modifier.height(16.dp)) } + items(allTopics) { topic -> + TopicRow( + topic = topic, + expanded = expandedTopic == topic, + onClick = { + expandedTopic = if (expandedTopic == topic) null else topic + } + ) + } + item { Spacer(modifier = Modifier.height(32.dp)) } + + // Tasks + item { Header(title = stringResource(R.string.tasks)) } + item { Spacer(modifier = Modifier.height(16.dp)) } + if (tasks.isEmpty()) { + item { + TextButton(onClick = { tasks.clear(); tasks.addAll(allTasks) }) { + Text(stringResource(R.string.add_tasks)) + } } } + items(tasks, key = { it }) { task -> + TaskRow( + task = task, + onRemove = { tasks.remove(task) } + ) + } } + EditMessage(editMessageShown) } - EditMessage(editMessageShown) } } @@ -294,8 +330,8 @@ private fun EditMessage(shown: Boolean) { ) { Surface( modifier = Modifier.fillMaxWidth(), - color = MaterialTheme.colors.secondary, - elevation = 4.dp + color = MaterialTheme.colorScheme.secondary, + shadowElevation = 18.dp ) { Text( text = stringResource(R.string.edit_message), @@ -310,8 +346,8 @@ private fun EditMessage(shown: Boolean) { */ @Composable private fun LazyListState.isScrollingUp(): Boolean { - var previousIndex by remember(this) { mutableStateOf(firstVisibleItemIndex) } - var previousScrollOffset by remember(this) { mutableStateOf(firstVisibleItemScrollOffset) } + var previousIndex by remember(this) { mutableIntStateOf(firstVisibleItemIndex) } + var previousScrollOffset by remember(this) { mutableIntStateOf(firstVisibleItemScrollOffset) } return remember(this) { derivedStateOf { if (previousIndex != firstVisibleItemIndex) { @@ -338,7 +374,7 @@ private fun Header( Text( text = title, modifier = Modifier.semantics { heading() }, - style = MaterialTheme.typography.h5 + style = MaterialTheme.typography.headlineLarge ) } @@ -349,14 +385,13 @@ private fun Header( * @param expanded Whether the row should be shown expanded with the topic body. * @param onClick Called when the row is clicked. */ -@OptIn(ExperimentalMaterialApi::class) @Composable private fun TopicRow(topic: String, expanded: Boolean, onClick: () -> Unit) { TopicRowSpacer(visible = expanded) Surface( modifier = Modifier .fillMaxWidth(), - elevation = 2.dp, + shadowElevation = 2.dp, onClick = onClick ) { // TODO 3: Animate the size change of the content. @@ -373,7 +408,7 @@ private fun TopicRow(topic: String, expanded: Boolean, onClick: () -> Unit) { Spacer(modifier = Modifier.width(16.dp)) Text( text = topic, - style = MaterialTheme.typography.body1 + style = MaterialTheme.typography.bodySmall ) } if (expanded) { @@ -411,23 +446,27 @@ private fun HomeTabBar( tabPage: TabPage, onTabSelected: (tabPage: TabPage) -> Unit ) { - TabRow( - selectedTabIndex = tabPage.ordinal, - backgroundColor = backgroundColor, - indicator = { tabPositions -> - HomeTabIndicator(tabPositions, tabPage) + Column(modifier = Modifier.windowInsetsPadding(WindowInsets.safeDrawing.only(Horizontal))) { + Spacer(Modifier.windowInsetsTopHeight(WindowInsets.safeDrawing)) + TabRow( + selectedTabIndex = tabPage.ordinal, + containerColor = backgroundColor, + contentColor = MaterialTheme.colorScheme.onPrimary, + indicator = { tabPositions -> + HomeTabIndicator(tabPositions, tabPage) + } + ){ + HomeTab( + icon = Icons.Default.Home, + title = stringResource(R.string.home), + onClick = { onTabSelected(TabPage.Home) } + ) + HomeTab( + icon = Icons.Default.AccountBox, + title = stringResource(R.string.work), + onClick = { onTabSelected(TabPage.Work) } + ) } - ) { - HomeTab( - icon = Icons.Default.Home, - title = stringResource(R.string.home), - onClick = { onTabSelected(TabPage.Home) } - ) - HomeTab( - icon = Icons.Default.AccountBox, - title = stringResource(R.string.work), - onClick = { onTabSelected(TabPage.Work) } - ) } } @@ -445,7 +484,7 @@ private fun HomeTabIndicator( // TODO 4: Animate these value changes. val indicatorLeft = tabPositions[tabPage.ordinal].left val indicatorRight = tabPositions[tabPage.ordinal].right - val color = if (tabPage == TabPage.Home) Purple700 else Green800 + val color = if (tabPage == TabPage.Home) PaleDogwood else Green Box( Modifier .fillMaxSize() @@ -566,7 +605,7 @@ private fun TaskRow(task: String, onRemove: () -> Unit) { modifier = Modifier .fillMaxWidth() .swipeToDismiss(onRemove), - elevation = 2.dp + shadowElevation = 2.dp ) { Row( modifier = Modifier @@ -580,7 +619,7 @@ private fun TaskRow(task: String, onRemove: () -> Unit) { Spacer(modifier = Modifier.width(16.dp)) Text( text = task, - style = MaterialTheme.typography.body1 + style = MaterialTheme.typography.bodySmall ) } } @@ -638,11 +677,13 @@ private fun Modifier.swipeToDismiss( @Preview @Composable private fun PreviewHomeTabBar() { - HomeTabBar( - backgroundColor = Purple100, - tabPage = TabPage.Home, - onTabSelected = {} - ) + AnimationCodelabTheme { + HomeTabBar( + backgroundColor = White, + tabPage = TabPage.Home, + onTabSelected = {} + ) + } } @Preview diff --git a/AnimationCodelab/start/src/main/res/values/themes.xml b/AnimationCodelab/start/src/main/res/values/themes.xml index 142395811..6af42df8e 100644 --- a/AnimationCodelab/start/src/main/res/values/themes.xml +++ b/AnimationCodelab/start/src/main/res/values/themes.xml @@ -16,7 +16,5 @@ --> diff --git a/README.md b/README.md index 58f992add..b6b0b7249 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ For more information about Jetpack Compose, please [read the documentation](http ## 💻 Requirements -[Android Studio Chipmunk](https://developer.android.com/studio). +[Latest version of Android Studio](https://developer.android.com/studio). ## 🧬 Codelabs