diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b27623c76f..3196023998 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ ## Contributor License Agreements -We'd love to accept your sample apps and patches! Before we can take them, we +We'd love to accept your patches! Before we can take them, we have to jump a couple of legal hurdles. Please fill out either the individual or corporate Contributor License Agreement (CLA). diff --git a/Crane/app/build.gradle b/Crane/app/build.gradle index 31f398c4c2..d2eaf61750 100644 --- a/Crane/app/build.gradle +++ b/Crane/app/build.gradle @@ -96,9 +96,10 @@ android { } packagingOptions { - exclude "META-INF/licenses/**" - exclude "META-INF/AL2.0" - exclude "META-INF/LGPL2.1" + // Multiple dependency bring these files in. Exclude them to enable + // our test APK to build (has no effect on our AARs) + excludes += "/META-INF/AL2.0" + excludes += "/META-INF/LGPL2.1" } } diff --git a/Crane/app/src/androidTest/java/androidx/compose/samples/crane/calendar/CalendarTest.kt b/Crane/app/src/androidTest/java/androidx/compose/samples/crane/calendar/CalendarTest.kt index 27105bac9c..8986639ff3 100644 --- a/Crane/app/src/androidTest/java/androidx/compose/samples/crane/calendar/CalendarTest.kt +++ b/Crane/app/src/androidTest/java/androidx/compose/samples/crane/calendar/CalendarTest.kt @@ -26,13 +26,13 @@ import androidx.compose.samples.crane.calendar.model.DaySelectedStatus.Selected import androidx.compose.samples.crane.data.DatesRepository import androidx.compose.samples.crane.di.DispatchersModule import androidx.compose.samples.crane.ui.CraneTheme -import androidx.ui.test.ComposeTestRule -import androidx.ui.test.SemanticsMatcher -import androidx.ui.test.assertLabelEquals -import androidx.ui.test.createAndroidComposeRule -import androidx.ui.test.onNodeWithLabel -import androidx.ui.test.performClick -import androidx.ui.test.performScrollTo +import androidx.compose.ui.test.SemanticsMatcher +import androidx.compose.ui.test.assertLabelEquals +import androidx.compose.ui.test.junit4.ComposeTestRule +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithLabel +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performScrollTo import dagger.hilt.android.testing.HiltAndroidRule import dagger.hilt.android.testing.HiltAndroidTest import dagger.hilt.android.testing.UninstallModules diff --git a/Crane/app/src/androidTest/java/androidx/compose/samples/crane/details/DetailsActivityTest.kt b/Crane/app/src/androidTest/java/androidx/compose/samples/crane/details/DetailsActivityTest.kt index d61dc5db38..039aec10f9 100644 --- a/Crane/app/src/androidTest/java/androidx/compose/samples/crane/details/DetailsActivityTest.kt +++ b/Crane/app/src/androidTest/java/androidx/compose/samples/crane/details/DetailsActivityTest.kt @@ -21,15 +21,15 @@ import androidx.compose.samples.crane.data.DestinationsRepository import androidx.compose.samples.crane.data.ExploreModel import androidx.compose.samples.crane.data.MADRID import androidx.compose.samples.crane.di.DispatchersModule +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.onNodeWithText import androidx.test.espresso.Espresso.onView import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.platform.app.InstrumentationRegistry -import androidx.ui.test.AndroidComposeTestRule -import androidx.ui.test.assertIsDisplayed -import androidx.ui.test.onNodeWithText import com.google.android.libraries.maps.MapView import com.google.android.libraries.maps.model.CameraPosition import com.google.android.libraries.maps.model.LatLng diff --git a/Crane/app/src/androidTest/java/androidx/compose/samples/crane/home/HomeTest.kt b/Crane/app/src/androidTest/java/androidx/compose/samples/crane/home/HomeTest.kt index 97095b1e06..af4e73489c 100644 --- a/Crane/app/src/androidTest/java/androidx/compose/samples/crane/home/HomeTest.kt +++ b/Crane/app/src/androidTest/java/androidx/compose/samples/crane/home/HomeTest.kt @@ -16,12 +16,10 @@ package androidx.compose.samples.crane.home -import androidx.compose.material.Surface import androidx.compose.samples.crane.di.DispatchersModule -import androidx.compose.samples.crane.ui.CraneTheme -import androidx.ui.test.createAndroidComposeRule -import androidx.ui.test.onNodeWithText -import androidx.ui.test.performClick +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick import dagger.hilt.android.testing.HiltAndroidRule import dagger.hilt.android.testing.HiltAndroidTest import dagger.hilt.android.testing.UninstallModules @@ -42,15 +40,7 @@ class HomeTest { @Before fun setUp() { composeTestRule.setContent { - CraneTheme { - Surface { - CraneHomeContent( - onExploreItemClicked = { }, - onDateSelectionClicked = { }, - openDrawer = { } - ) - } - } + MainScreen({ }, { }) } } diff --git a/Crane/app/src/main/java/androidx/compose/samples/crane/base/BaseUserInput.kt b/Crane/app/src/main/java/androidx/compose/samples/crane/base/BaseUserInput.kt index 3e38c665f6..4ca8a97de8 100644 --- a/Crane/app/src/main/java/androidx/compose/samples/crane/base/BaseUserInput.kt +++ b/Crane/app/src/main/java/androidx/compose/samples/crane/base/BaseUserInput.kt @@ -17,18 +17,18 @@ package androidx.compose.samples.crane.base import androidx.annotation.DrawableRes -import androidx.compose.foundation.AmbientContentColor -import androidx.compose.foundation.BaseTextField import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.Text import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.preferredSize import androidx.compose.foundation.layout.preferredWidth +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.material.AmbientContentColor import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -93,13 +93,18 @@ fun CraneEditableUserInput( showCaption = { !isHint() }, vectorImageId = vectorImageId ) { - BaseTextField( + BasicTextField( value = textFieldState, onValueChange = { textFieldState = it if (!isHint()) onInputChanged(textFieldState.text) }, - textStyle = if (isHint()) captionTextStyle else MaterialTheme.typography.body1, + textStyle = if (isHint()) { + captionTextStyle.copy(color = AmbientContentColor.current) + } else { + MaterialTheme.typography.body1 + }, + cursorColor = AmbientContentColor.current ) } } diff --git a/Crane/app/src/main/java/androidx/compose/samples/crane/base/CraneDrawer.kt b/Crane/app/src/main/java/androidx/compose/samples/crane/base/CraneDrawer.kt index adc669e6d9..62ec5b4c6e 100644 --- a/Crane/app/src/main/java/androidx/compose/samples/crane/base/CraneDrawer.kt +++ b/Crane/app/src/main/java/androidx/compose/samples/crane/base/CraneDrawer.kt @@ -17,13 +17,13 @@ package androidx.compose.samples.crane.base import androidx.compose.foundation.Image -import androidx.compose.foundation.Text import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.preferredHeight import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.samples.crane.R import androidx.compose.samples.crane.ui.CraneTheme diff --git a/Crane/app/src/main/java/androidx/compose/samples/crane/base/CraneTabs.kt b/Crane/app/src/main/java/androidx/compose/samples/crane/base/CraneTabs.kt index 17644d879d..fb265a984a 100644 --- a/Crane/app/src/main/java/androidx/compose/samples/crane/base/CraneTabs.kt +++ b/Crane/app/src/main/java/androidx/compose/samples/crane/base/CraneTabs.kt @@ -18,7 +18,6 @@ package androidx.compose.samples.crane.base import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image -import androidx.compose.foundation.Text import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.ExperimentalLayout @@ -30,6 +29,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.MaterialTheme import androidx.compose.material.Tab import androidx.compose.material.TabRow +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.samples.crane.R import androidx.compose.samples.crane.home.CraneScreen diff --git a/Crane/app/src/main/java/androidx/compose/samples/crane/base/ExploreSection.kt b/Crane/app/src/main/java/androidx/compose/samples/crane/base/ExploreSection.kt index ffacb17f7a..7c6160ab1d 100644 --- a/Crane/app/src/main/java/androidx/compose/samples/crane/base/ExploreSection.kt +++ b/Crane/app/src/main/java/androidx/compose/samples/crane/base/ExploreSection.kt @@ -17,7 +17,6 @@ package androidx.compose.samples.crane.base import androidx.compose.foundation.Image -import androidx.compose.foundation.Text import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -33,6 +32,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Divider import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.samples.crane.R import androidx.compose.samples.crane.data.ExploreModel diff --git a/Crane/app/src/main/java/androidx/compose/samples/crane/calendar/Calendar.kt b/Crane/app/src/main/java/androidx/compose/samples/crane/calendar/Calendar.kt index af3590d99c..8d68529e14 100644 --- a/Crane/app/src/main/java/androidx/compose/samples/crane/calendar/Calendar.kt +++ b/Crane/app/src/main/java/androidx/compose/samples/crane/calendar/Calendar.kt @@ -17,7 +17,6 @@ package androidx.compose.samples.crane.calendar import androidx.compose.foundation.ScrollableColumn -import androidx.compose.foundation.Text import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -35,6 +34,7 @@ import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.material.Colors import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.samples.crane.calendar.model.CalendarDay import androidx.compose.samples.crane.calendar.model.CalendarMonth diff --git a/Crane/app/src/main/java/androidx/compose/samples/crane/calendar/CalendarActivity.kt b/Crane/app/src/main/java/androidx/compose/samples/crane/calendar/CalendarActivity.kt index 1ac1d93dde..a7109681e1 100644 --- a/Crane/app/src/main/java/androidx/compose/samples/crane/calendar/CalendarActivity.kt +++ b/Crane/app/src/main/java/androidx/compose/samples/crane/calendar/CalendarActivity.kt @@ -21,11 +21,11 @@ import android.content.Intent import android.os.Bundle import androidx.activity.ComponentActivity import androidx.compose.foundation.Image -import androidx.compose.foundation.Text import androidx.compose.foundation.layout.Column import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface +import androidx.compose.material.Text import androidx.compose.material.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.samples.crane.R diff --git a/Crane/app/src/main/java/androidx/compose/samples/crane/details/DetailsActivity.kt b/Crane/app/src/main/java/androidx/compose/samples/crane/details/DetailsActivity.kt index 6aaead5a75..7c5b3932f1 100644 --- a/Crane/app/src/main/java/androidx/compose/samples/crane/details/DetailsActivity.kt +++ b/Crane/app/src/main/java/androidx/compose/samples/crane/details/DetailsActivity.kt @@ -21,7 +21,6 @@ import android.content.Intent import android.os.Bundle import androidx.activity.ComponentActivity import androidx.annotation.VisibleForTesting -import androidx.compose.foundation.Text import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -32,6 +31,7 @@ import androidx.compose.foundation.layout.preferredHeight import androidx.compose.material.Button import androidx.compose.material.ButtonConstants import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember diff --git a/Crane/app/src/main/java/androidx/compose/samples/crane/home/LandingScreen.kt b/Crane/app/src/main/java/androidx/compose/samples/crane/home/LandingScreen.kt index 577aab16e5..04b9b383ec 100644 --- a/Crane/app/src/main/java/androidx/compose/samples/crane/home/LandingScreen.kt +++ b/Crane/app/src/main/java/androidx/compose/samples/crane/home/LandingScreen.kt @@ -20,7 +20,9 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedTask +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.samples.crane.R import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -32,9 +34,12 @@ private const val SplashWaitTime: Long = 2000 @Composable fun LandingScreen(modifier: Modifier = Modifier, onTimeout: () -> Unit) { Box(modifier = modifier.fillMaxSize(), alignment = Alignment.Center) { - LaunchedTask { + // Adds composition consistency. Use the value when LaunchedEffect is first called + val currentOnTimeout by rememberUpdatedState(onTimeout) + + LaunchedEffect(Unit) { delay(SplashWaitTime) - onTimeout() + currentOnTimeout() } Image(asset = vectorResource(id = R.drawable.ic_crane_drawer)) } diff --git a/Crane/app/src/main/java/androidx/compose/samples/crane/home/MainActivity.kt b/Crane/app/src/main/java/androidx/compose/samples/crane/home/MainActivity.kt index b22e9eda1a..512b9e5611 100644 --- a/Crane/app/src/main/java/androidx/compose/samples/crane/home/MainActivity.kt +++ b/Crane/app/src/main/java/androidx/compose/samples/crane/home/MainActivity.kt @@ -18,6 +18,7 @@ package androidx.compose.samples.crane.home import android.os.Bundle import androidx.activity.ComponentActivity +import androidx.annotation.VisibleForTesting import androidx.compose.animation.DpPropKey import androidx.compose.animation.core.FloatPropKey import androidx.compose.animation.core.Spring.StiffnessLow @@ -51,29 +52,31 @@ class MainActivity : ComponentActivity() { super.onCreate(savedInstanceState) setContent { - CraneScaffold { - val onExploreItemClicked: OnExploreItemClicked = remember { - { launchDetailsActivity(context = this, item = it) } - } - val onDateSelectionClicked = remember { - { launchCalendarActivity(this) } - } + MainScreen( + onExploreItemClicked = { launchDetailsActivity(context = this, item = it) }, + onDateSelectionClicked = { launchCalendarActivity(this) } + ) + } + } +} - var splashShown by remember { mutableStateOf(SplashState.Shown) } - val transition = transition(splashTransitionDefinition, splashShown) - Box { - LandingScreen( - modifier = Modifier.drawOpacity(transition[splashAlphaKey]), - onTimeout = { splashShown = SplashState.Completed } - ) - MainContent( - modifier = Modifier.drawOpacity(transition[contentAlphaKey]), - topPadding = transition[contentTopPaddingKey], - onExploreItemClicked = onExploreItemClicked, - onDateSelectionClicked = onDateSelectionClicked - ) - } - } +@VisibleForTesting +@Composable +fun MainScreen(onExploreItemClicked: OnExploreItemClicked, onDateSelectionClicked: () -> Unit) { + CraneScaffold { + var splashShown by remember { mutableStateOf(SplashState.Shown) } + val transition = transition(splashTransitionDefinition, splashShown) + Box { + LandingScreen( + modifier = Modifier.drawOpacity(transition[splashAlphaKey]), + onTimeout = { splashShown = SplashState.Completed } + ) + MainContent( + modifier = Modifier.drawOpacity(transition[contentAlphaKey]), + topPadding = transition[contentTopPaddingKey], + onExploreItemClicked = onExploreItemClicked, + onDateSelectionClicked = onDateSelectionClicked + ) } } } diff --git a/Crane/app/src/main/java/androidx/compose/samples/crane/home/SearchUserInput.kt b/Crane/app/src/main/java/androidx/compose/samples/crane/home/SearchUserInput.kt index 46cffbf6b9..3110fbcb12 100644 --- a/Crane/app/src/main/java/androidx/compose/samples/crane/home/SearchUserInput.kt +++ b/Crane/app/src/main/java/androidx/compose/samples/crane/home/SearchUserInput.kt @@ -20,10 +20,10 @@ import androidx.compose.animation.ColorPropKey import androidx.compose.animation.core.transitionDefinition import androidx.compose.animation.core.tween import androidx.compose.animation.transition -import androidx.compose.foundation.Text import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf diff --git a/Crane/build.gradle b/Crane/build.gradle index 96fe956cf1..776095811c 100644 --- a/Crane/build.gradle +++ b/Crane/build.gradle @@ -67,6 +67,10 @@ subprojects { // Use experimental APIs freeCompilerArgs += '-Xopt-in=kotlin.RequiresOptIn' freeCompilerArgs += '-Xallow-jvm-ir-dependencies' + freeCompilerArgs += [ + '-P', + 'plugin:androidx.compose.compiler.plugins.kotlin:intrinsicRemember=true' + ] } } } \ No newline at end of file diff --git a/Crane/buildSrc/src/main/java/com/example/crane/buildsrc/Dependencies.kt b/Crane/buildSrc/src/main/java/com/example/crane/buildsrc/Dependencies.kt index b48e95e7c2..dc2f62bbf9 100644 --- a/Crane/buildSrc/src/main/java/com/example/crane/buildsrc/Dependencies.kt +++ b/Crane/buildSrc/src/main/java/com/example/crane/buildsrc/Dependencies.kt @@ -21,12 +21,12 @@ object Versions { } object Libs { - const val androidGradlePlugin = "com.android.tools.build:gradle:4.2.0-alpha15" + const val androidGradlePlugin = "com.android.tools.build:gradle:4.2.0-alpha16" const val ktLint = "com.pinterest:ktlint:${Versions.ktLint}" const val googleMaps = "com.google.android.libraries.maps:maps:3.1.0-beta" object Accompanist { - private const val version = "0.3.2" + private const val version = "0.3.3.1" const val coil = "dev.chrisbanes.accompanist:accompanist-coil:$version" } @@ -37,7 +37,7 @@ object Libs { const val extensions = "org.jetbrains.kotlin:kotlin-android-extensions:$version" object Coroutines { - private const val version = "1.4.0-M1" + private const val version = "1.4.0" const val android = "org.jetbrains.kotlinx:kotlinx-coroutines-android:$version" const val test = "org.jetbrains.kotlinx:kotlinx-coroutines-test:$version" } @@ -46,7 +46,7 @@ object Libs { object AndroidX { object Compose { const val snapshot = "" - const val version = "1.0.0-alpha06" + const val version = "1.0.0-alpha07" const val runtime = "androidx.compose.runtime:runtime:$version" const val runtimeLivedata = "androidx.compose.runtime:runtime-livedata:$version" diff --git a/JetNews/app/build.gradle b/JetNews/app/build.gradle index ae89f0abcd..9b6c553779 100644 --- a/JetNews/app/build.gradle +++ b/JetNews/app/build.gradle @@ -68,6 +68,11 @@ android { kotlinCompilerVersion kotlin_version kotlinCompilerExtensionVersion compose_version } + + packagingOptions { + excludes += "/META-INF/AL2.0" + excludes += "/META-INF/LGPL2.1" + } } dependencies { diff --git a/JetNews/app/src/androidTest/java/com/example/jetnews/HomeScreenSnackbarTest.kt b/JetNews/app/src/androidTest/java/com/example/jetnews/HomeScreenSnackbarTest.kt index 1cbeecc096..b04675a3d2 100644 --- a/JetNews/app/src/androidTest/java/com/example/jetnews/HomeScreenSnackbarTest.kt +++ b/JetNews/app/src/androidTest/java/com/example/jetnews/HomeScreenSnackbarTest.kt @@ -21,10 +21,10 @@ import androidx.compose.material.SnackbarHostState import androidx.compose.material.rememberScaffoldState import androidx.compose.runtime.ExperimentalComposeApi import androidx.compose.runtime.snapshots.snapshotFlow +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.onNodeWithText import androidx.test.platform.app.InstrumentationRegistry -import androidx.ui.test.assertIsDisplayed import androidx.ui.test.createComposeRule -import androidx.ui.test.onNodeWithText import com.example.jetnews.ui.home.HomeScreen import com.example.jetnews.ui.state.UiState import kotlinx.coroutines.flow.filterNotNull diff --git a/JetNews/app/src/androidTest/java/com/example/jetnews/JetnewsUiTest.kt b/JetNews/app/src/androidTest/java/com/example/jetnews/JetnewsUiTest.kt index 5aceada18f..e6790eeea2 100644 --- a/JetNews/app/src/androidTest/java/com/example/jetnews/JetnewsUiTest.kt +++ b/JetNews/app/src/androidTest/java/com/example/jetnews/JetnewsUiTest.kt @@ -16,13 +16,13 @@ package com.example.jetnews +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.hasSubstring +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick import androidx.test.filters.MediumTest import androidx.test.platform.app.InstrumentationRegistry -import androidx.ui.test.assertIsDisplayed import androidx.ui.test.createComposeRule -import androidx.ui.test.hasSubstring -import androidx.ui.test.onNodeWithText -import androidx.ui.test.performClick import org.junit.Before import org.junit.Ignore import org.junit.Rule diff --git a/JetNews/app/src/androidTest/java/com/example/jetnews/TestHelper.kt b/JetNews/app/src/androidTest/java/com/example/jetnews/TestHelper.kt index 4dbc614267..e704c7bcb3 100644 --- a/JetNews/app/src/androidTest/java/com/example/jetnews/TestHelper.kt +++ b/JetNews/app/src/androidTest/java/com/example/jetnews/TestHelper.kt @@ -18,15 +18,15 @@ package com.example.jetnews import android.content.Context import androidx.compose.runtime.remember +import androidx.compose.ui.test.junit4.ComposeTestRule import androidx.lifecycle.SavedStateHandle -import androidx.ui.test.ComposeTestRuleJUnit import com.example.jetnews.ui.JetnewsApp import com.example.jetnews.ui.NavigationViewModel /** * Launches the app from a test context */ -fun ComposeTestRuleJUnit.launchJetNewsApp(context: Context) { +fun ComposeTestRule.launchJetNewsApp(context: Context) { setContent { JetnewsApp( TestAppContainer(context), diff --git a/JetNews/app/src/main/java/com/example/jetnews/ui/JetnewsApp.kt b/JetNews/app/src/main/java/com/example/jetnews/ui/JetnewsApp.kt index c718f42b94..aee94dc6b6 100644 --- a/JetNews/app/src/main/java/com/example/jetnews/ui/JetnewsApp.kt +++ b/JetNews/app/src/main/java/com/example/jetnews/ui/JetnewsApp.kt @@ -18,7 +18,6 @@ package com.example.jetnews.ui import androidx.compose.animation.Crossfade import androidx.compose.foundation.Image -import androidx.compose.foundation.Text import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -31,6 +30,7 @@ import androidx.compose.foundation.layout.preferredWidth import androidx.compose.material.Divider import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface +import androidx.compose.material.Text import androidx.compose.material.TextButton import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Home diff --git a/JetNews/app/src/main/java/com/example/jetnews/ui/article/ArticleScreen.kt b/JetNews/app/src/main/java/com/example/jetnews/ui/article/ArticleScreen.kt index 110ae4cac7..6f950e0c8c 100644 --- a/JetNews/app/src/main/java/com/example/jetnews/ui/article/ArticleScreen.kt +++ b/JetNews/app/src/main/java/com/example/jetnews/ui/article/ArticleScreen.kt @@ -18,19 +18,19 @@ package com.example.jetnews.ui.article import android.content.Context import android.content.Intent -import androidx.compose.foundation.AmbientContentColor -import androidx.compose.foundation.Text import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.preferredHeight import androidx.compose.material.AlertDialog +import androidx.compose.material.AmbientContentColor 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.Text import androidx.compose.material.TextButton import androidx.compose.material.TopAppBar import androidx.compose.material.icons.Icons diff --git a/JetNews/app/src/main/java/com/example/jetnews/ui/article/PostContent.kt b/JetNews/app/src/main/java/com/example/jetnews/ui/article/PostContent.kt index 605d8fc958..d07bf94f03 100644 --- a/JetNews/app/src/main/java/com/example/jetnews/ui/article/PostContent.kt +++ b/JetNews/app/src/main/java/com/example/jetnews/ui/article/PostContent.kt @@ -16,10 +16,8 @@ package com.example.jetnews.ui.article -import androidx.compose.foundation.AmbientContentColor import androidx.compose.foundation.Image import androidx.compose.foundation.ScrollableColumn -import androidx.compose.foundation.Text import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -32,21 +30,24 @@ import androidx.compose.foundation.layout.preferredHeight import androidx.compose.foundation.layout.preferredSize import androidx.compose.foundation.layout.preferredWidth import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.text.FirstBaseline -import androidx.compose.material.AmbientEmphasisLevels +import androidx.compose.material.AmbientContentAlpha +import androidx.compose.material.AmbientContentColor import androidx.compose.material.Colors +import androidx.compose.material.ContentAlpha import androidx.compose.material.MaterialTheme -import androidx.compose.material.ProvideEmphasis import androidx.compose.material.Surface +import androidx.compose.material.Text import androidx.compose.material.Typography import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AccountCircle import androidx.compose.runtime.Composable +import androidx.compose.runtime.Providers import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.layout.FirstBaseline import androidx.compose.ui.platform.DensityAmbient import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.ParagraphStyle @@ -82,7 +83,7 @@ fun PostContent(post: Post, modifier: Modifier = Modifier) { Text(text = post.title, style = MaterialTheme.typography.h4) Spacer(Modifier.preferredHeight(8.dp)) post.subtitle?.let { subtitle -> - ProvideEmphasis(AmbientEmphasisLevels.current.medium) { + Providers(AmbientContentAlpha provides ContentAlpha.medium) { Text( text = subtitle, style = MaterialTheme.typography.body2, @@ -122,14 +123,13 @@ private fun PostMetadata(metadata: Metadata) { ) Spacer(Modifier.preferredWidth(8.dp)) Column { - ProvideEmphasis(AmbientEmphasisLevels.current.high) { - Text( - text = metadata.author.name, - style = typography.caption, - modifier = Modifier.padding(top = 4.dp) - ) - } - ProvideEmphasis(AmbientEmphasisLevels.current.medium) { + Text( + text = metadata.author.name, + style = typography.caption, + modifier = Modifier.padding(top = 4.dp) + ) + + Providers(AmbientContentAlpha provides ContentAlpha.medium) { Text( text = "${metadata.date} • ${metadata.readTimeMinutes} min read", style = typography.caption diff --git a/JetNews/app/src/main/java/com/example/jetnews/ui/home/HomeScreen.kt b/JetNews/app/src/main/java/com/example/jetnews/ui/home/HomeScreen.kt index 2b63d478f8..8b9fabae99 100644 --- a/JetNews/app/src/main/java/com/example/jetnews/ui/home/HomeScreen.kt +++ b/JetNews/app/src/main/java/com/example/jetnews/ui/home/HomeScreen.kt @@ -18,7 +18,6 @@ package com.example.jetnews.ui.home import androidx.compose.foundation.ScrollableColumn import androidx.compose.foundation.ScrollableRow -import androidx.compose.foundation.Text import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -27,7 +26,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.preferredSize import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.AmbientEmphasisLevels import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.Divider import androidx.compose.material.DrawerValue @@ -35,17 +33,17 @@ import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme -import androidx.compose.material.ProvideEmphasis import androidx.compose.material.Scaffold import androidx.compose.material.ScaffoldState import androidx.compose.material.SnackbarResult import androidx.compose.material.Surface +import androidx.compose.material.Text import androidx.compose.material.TextButton import androidx.compose.material.TopAppBar import androidx.compose.material.rememberDrawerState import androidx.compose.material.rememberScaffoldState import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedTask +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope @@ -139,7 +137,7 @@ fun HomeScreen( // Show snackbar using a coroutine, when the coroutine is cancelled the snackbar will // automatically dismiss. This coroutine will cancel whenever posts.hasError changes, and // only start when posts.hasError is true (due to the above if-check). - LaunchedTask(posts.hasError) { + LaunchedEffect(posts.hasError) { val snackbarResult = scaffoldState.snackbarHostState.showSnackbar( message = errorMessage, actionLabel = retryMessage @@ -313,13 +311,11 @@ private fun FullScreenLoading() { */ @Composable private fun PostListTopSection(post: Post, navigateTo: (Screen) -> Unit) { - ProvideEmphasis(AmbientEmphasisLevels.current.high) { - Text( - modifier = Modifier.padding(start = 16.dp, top = 16.dp, end = 16.dp), - text = "Top stories for you", - style = MaterialTheme.typography.subtitle1 - ) - } + Text( + modifier = Modifier.padding(start = 16.dp, top = 16.dp, end = 16.dp), + text = "Top stories for you", + style = MaterialTheme.typography.subtitle1 + ) PostCardTop( post = post, modifier = Modifier.clickable(onClick = { navigateTo(Screen.Article(post.id)) }) @@ -365,13 +361,12 @@ private fun PostListPopularSection( navigateTo: (Screen) -> Unit ) { Column { - ProvideEmphasis(AmbientEmphasisLevels.current.high) { - Text( - modifier = Modifier.padding(16.dp), - text = "Popular on Jetnews", - style = MaterialTheme.typography.subtitle1 - ) - } + Text( + modifier = Modifier.padding(16.dp), + text = "Popular on Jetnews", + style = MaterialTheme.typography.subtitle1 + ) + ScrollableRow(modifier = Modifier.padding(end = 16.dp)) { posts.forEach { post -> PostCardPopular(post, navigateTo, Modifier.padding(start = 16.dp, bottom = 16.dp)) diff --git a/JetNews/app/src/main/java/com/example/jetnews/ui/home/PostCardTop.kt b/JetNews/app/src/main/java/com/example/jetnews/ui/home/PostCardTop.kt index 0f44728468..036b2fc7db 100644 --- a/JetNews/app/src/main/java/com/example/jetnews/ui/home/PostCardTop.kt +++ b/JetNews/app/src/main/java/com/example/jetnews/ui/home/PostCardTop.kt @@ -17,17 +17,18 @@ package com.example.jetnews.ui.home import androidx.compose.foundation.Image -import androidx.compose.foundation.Text import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.preferredHeight -import androidx.compose.material.AmbientEmphasisLevels +import androidx.compose.material.AmbientContentAlpha +import androidx.compose.material.ContentAlpha import androidx.compose.material.MaterialTheme -import androidx.compose.material.ProvideEmphasis +import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.Providers import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.ContentScale @@ -54,18 +55,16 @@ fun PostCardTop(post: Post, modifier: Modifier = Modifier) { } Spacer(Modifier.preferredHeight(16.dp)) - val emphasisLevels = AmbientEmphasisLevels.current - ProvideEmphasis(emphasisLevels.high) { - Text( - text = post.title, - style = typography.h6 - ) - Text( - text = post.metadata.author.name, - style = typography.body2 - ) - } - ProvideEmphasis(emphasisLevels.medium) { + Text( + text = post.title, + style = typography.h6 + ) + Text( + text = post.metadata.author.name, + style = typography.body2 + ) + + Providers(AmbientContentAlpha provides ContentAlpha.medium) { Text( text = "${post.metadata.date} - ${post.metadata.readTimeMinutes} min read", style = typography.body2 diff --git a/JetNews/app/src/main/java/com/example/jetnews/ui/home/PostCardYourNetwork.kt b/JetNews/app/src/main/java/com/example/jetnews/ui/home/PostCardYourNetwork.kt index c04c08cef1..7c71a43980 100644 --- a/JetNews/app/src/main/java/com/example/jetnews/ui/home/PostCardYourNetwork.kt +++ b/JetNews/app/src/main/java/com/example/jetnews/ui/home/PostCardYourNetwork.kt @@ -17,17 +17,15 @@ package com.example.jetnews.ui.home import androidx.compose.foundation.Image -import androidx.compose.foundation.Text import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.preferredHeight import androidx.compose.foundation.layout.preferredSize -import androidx.compose.material.AmbientEmphasisLevels import androidx.compose.material.Card import androidx.compose.material.MaterialTheme -import androidx.compose.material.ProvideEmphasis +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale @@ -62,28 +60,24 @@ fun PostCardPopular( .fillMaxWidth() ) Column(modifier = Modifier.padding(16.dp)) { - val emphasisLevels = AmbientEmphasisLevels.current - ProvideEmphasis(emphasisLevels.high) { - Text( - text = post.title, - style = MaterialTheme.typography.h6, - maxLines = 2, - overflow = TextOverflow.Ellipsis - ) - Text( - text = post.metadata.author.name, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.body2 - ) - } - ProvideEmphasis(emphasisLevels.high) { - Text( - text = "${post.metadata.date} - " + - "${post.metadata.readTimeMinutes} min read", - style = MaterialTheme.typography.body2 - ) - } + Text( + text = post.title, + style = MaterialTheme.typography.h6, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + Text( + text = post.metadata.author.name, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.body2 + ) + + Text( + text = "${post.metadata.date} - " + + "${post.metadata.readTimeMinutes} min read", + style = MaterialTheme.typography.body2 + ) } } } diff --git a/JetNews/app/src/main/java/com/example/jetnews/ui/home/PostCards.kt b/JetNews/app/src/main/java/com/example/jetnews/ui/home/PostCards.kt index f33c643d44..8d75d921cf 100644 --- a/JetNews/app/src/main/java/com/example/jetnews/ui/home/PostCards.kt +++ b/JetNews/app/src/main/java/com/example/jetnews/ui/home/PostCards.kt @@ -17,23 +17,24 @@ package com.example.jetnews.ui.home import androidx.compose.foundation.Image -import androidx.compose.foundation.Text import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.preferredSize -import androidx.compose.material.AmbientEmphasisLevels +import androidx.compose.material.AmbientContentAlpha +import androidx.compose.material.ContentAlpha import androidx.compose.material.Icon import androidx.compose.material.IconToggleButton import androidx.compose.material.MaterialTheme -import androidx.compose.material.ProvideEmphasis import androidx.compose.material.Surface +import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Bookmark import androidx.compose.material.icons.filled.BookmarkBorder import androidx.compose.material.icons.filled.MoreVert import androidx.compose.runtime.Composable +import androidx.compose.runtime.Providers import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.res.imageResource @@ -51,7 +52,7 @@ fun AuthorAndReadTime( modifier: Modifier = Modifier ) { Row(modifier) { - ProvideEmphasis(AmbientEmphasisLevels.current.medium) { + Providers(AmbientContentAlpha provides ContentAlpha.medium) { val textStyle = MaterialTheme.typography.body2 Text( text = post.metadata.author.name, @@ -78,9 +79,7 @@ fun PostImage(post: Post, modifier: Modifier = Modifier) { @Composable fun PostTitle(post: Post) { - ProvideEmphasis(AmbientEmphasisLevels.current.high) { - Text(post.title, style = MaterialTheme.typography.subtitle1) - } + Text(post.title, style = MaterialTheme.typography.subtitle1) } @Composable @@ -117,7 +116,7 @@ fun PostCardHistory(post: Post, navigateTo: (Screen) -> Unit) { modifier = Modifier.padding(end = 16.dp) ) Column(Modifier.weight(1f)) { - ProvideEmphasis(AmbientEmphasisLevels.current.medium) { + Providers(AmbientContentAlpha provides ContentAlpha.medium) { Text( text = "BASED ON YOUR HISTORY", style = MaterialTheme.typography.overline @@ -129,7 +128,7 @@ fun PostCardHistory(post: Post, navigateTo: (Screen) -> Unit) { modifier = Modifier.padding(top = 4.dp) ) } - ProvideEmphasis(AmbientEmphasisLevels.current.medium) { + Providers(AmbientContentAlpha provides ContentAlpha.medium) { Icon(Icons.Filled.MoreVert) } } diff --git a/JetNews/app/src/main/java/com/example/jetnews/ui/interests/InterestsScreen.kt b/JetNews/app/src/main/java/com/example/jetnews/ui/interests/InterestsScreen.kt index 313c6c59be..409e3b7987 100644 --- a/JetNews/app/src/main/java/com/example/jetnews/ui/interests/InterestsScreen.kt +++ b/JetNews/app/src/main/java/com/example/jetnews/ui/interests/InterestsScreen.kt @@ -18,7 +18,6 @@ package com.example.jetnews.ui.interests import androidx.compose.foundation.Image import androidx.compose.foundation.ScrollableColumn -import androidx.compose.foundation.Text import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -36,6 +35,7 @@ import androidx.compose.material.Scaffold import androidx.compose.material.ScaffoldState import androidx.compose.material.Tab import androidx.compose.material.TabRow +import androidx.compose.material.Text import androidx.compose.material.TopAppBar import androidx.compose.material.rememberDrawerState import androidx.compose.material.rememberScaffoldState diff --git a/JetNews/app/src/main/java/com/example/jetnews/ui/interests/SelectTopicButton.kt b/JetNews/app/src/main/java/com/example/jetnews/ui/interests/SelectTopicButton.kt index 9916dd5f30..b4271b2837 100644 --- a/JetNews/app/src/main/java/com/example/jetnews/ui/interests/SelectTopicButton.kt +++ b/JetNews/app/src/main/java/com/example/jetnews/ui/interests/SelectTopicButton.kt @@ -19,10 +19,8 @@ package com.example.jetnews.ui.interests import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.preferredSize import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.AmbientEmphasisLevels import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme -import androidx.compose.material.ProvideEmphasis import androidx.compose.material.Surface import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add @@ -49,9 +47,7 @@ fun SelectTopicButton( shape = CircleShape, modifier = modifier.preferredSize(36.dp, 36.dp) ) { - ProvideEmphasis(AmbientEmphasisLevels.current.high) { - Icon(icon) - } + Icon(icon) } } diff --git a/JetNews/build.gradle b/JetNews/build.gradle index ff6a558e49..f7cf7eefa1 100644 --- a/JetNews/build.gradle +++ b/JetNews/build.gradle @@ -16,7 +16,7 @@ buildscript { ext.kotlin_version = '1.4.10' - ext.compose_version = '1.0.0-alpha06' + ext.compose_version = '1.0.0-SNAPSHOT' repositories { google() @@ -24,7 +24,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:4.2.0-alpha15' + classpath 'com.android.tools.build:gradle:4.2.0-alpha16' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } @@ -35,6 +35,10 @@ plugins { subprojects { repositories { + maven { + def snapshot = "6961312" + url "https://androidx.dev/snapshots/builds/$snapshot/artifacts/repository/" + } google() jcenter() } diff --git a/Jetcaster/app/build.gradle b/Jetcaster/app/build.gradle index a3d24acdbb..f0e3b8bf28 100644 --- a/Jetcaster/app/build.gradle +++ b/Jetcaster/app/build.gradle @@ -109,6 +109,8 @@ dependencies { implementation Libs.Accompanist.coil + implementation Libs.Accompanist.insets + implementation Libs.OkHttp.okhttp implementation Libs.OkHttp.logging diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/MainActivity.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/MainActivity.kt index 9cceb6758b..881e241727 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/MainActivity.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/MainActivity.kt @@ -21,7 +21,7 @@ import androidx.activity.ComponentActivity import androidx.compose.ui.platform.setContent import androidx.core.view.WindowCompat import com.example.jetcaster.ui.theme.JetcasterTheme -import com.example.jetcaster.util.ProvideDisplayInsets +import dev.chrisbanes.accompanist.insets.ProvideWindowInsets class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -32,7 +32,7 @@ class MainActivity : ComponentActivity() { setContent { JetcasterTheme { - ProvideDisplayInsets { + ProvideWindowInsets { JetcasterApp() } } diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt index 9fc5610c9f..40aa9bfa29 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/Home.kt @@ -17,7 +17,6 @@ package com.example.jetcaster.ui.home import androidx.compose.foundation.Image -import androidx.compose.foundation.Text import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -33,22 +32,24 @@ import androidx.compose.foundation.layout.preferredHeight import androidx.compose.foundation.layout.preferredHeightIn import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.AmbientEmphasisLevels +import androidx.compose.material.AmbientContentAlpha +import androidx.compose.material.ContentAlpha import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme -import androidx.compose.material.ProvideEmphasis import androidx.compose.material.Surface import androidx.compose.material.Tab import androidx.compose.material.TabConstants.defaultTabIndicatorOffset import androidx.compose.material.TabPosition import androidx.compose.material.TabRow +import androidx.compose.material.Text import androidx.compose.material.TopAppBar import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AccountCircle import androidx.compose.material.icons.filled.Search import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedTask +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.Providers import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember @@ -76,9 +77,9 @@ import com.example.jetcaster.util.ToggleFollowPodcastIconButton import com.example.jetcaster.util.constrastAgainst import com.example.jetcaster.util.quantityStringResource import com.example.jetcaster.util.rememberDominantColorState -import com.example.jetcaster.util.statusBarsHeight import com.example.jetcaster.util.verticalGradientScrim import dev.chrisbanes.accompanist.coil.CoilImage +import dev.chrisbanes.accompanist.insets.statusBarsHeight import java.time.Duration import java.time.LocalDateTime import java.time.OffsetDateTime @@ -119,7 +120,7 @@ fun HomeAppBar( }, backgroundColor = backgroundColor, actions = { - ProvideEmphasis(AmbientEmphasisLevels.current.medium) { + Providers(AmbientContentAlpha provides ContentAlpha.medium) { IconButton( onClick = { /* TODO: Open search */ }, icon = { Icon(Icons.Filled.Search) } @@ -170,7 +171,7 @@ fun HomeContent( // When the selected image url changes, call updateColorsFromImageUrl() or reset() if (selectedImageUrl != null) { - LaunchedTask(selectedImageUrl) { + LaunchedEffect(selectedImageUrl) { dominantColorState.updateColorsFromImageUrl(selectedImageUrl) } } else { @@ -338,17 +339,15 @@ private fun FollowedPodcastCarouselItem( ) } - ProvideEmphasis(AmbientEmphasisLevels.current.high) { - ToggleFollowPodcastIconButton( - onClick = onUnfollowedClick, - isFollowed = true, /* All podcasts are followed in this feed */ - modifier = Modifier.align(Alignment.BottomEnd) - ) - } + ToggleFollowPodcastIconButton( + onClick = onUnfollowedClick, + isFollowed = true, /* All podcasts are followed in this feed */ + modifier = Modifier.align(Alignment.BottomEnd) + ) } if (lastEpisodeDate != null) { - ProvideEmphasis(AmbientEmphasisLevels.current.medium) { + Providers(AmbientContentAlpha provides ContentAlpha.medium) { Text( text = lastUpdated(lastEpisodeDate), style = MaterialTheme.typography.caption, diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/category/PodcastCategory.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/category/PodcastCategory.kt index be277cb0db..c30b226b7c 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/category/PodcastCategory.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/category/PodcastCategory.kt @@ -16,9 +16,7 @@ package com.example.jetcaster.ui.home.category -import androidx.compose.foundation.AmbientContentColor import androidx.compose.foundation.Image -import androidx.compose.foundation.Text import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -32,21 +30,23 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.preferredSize import androidx.compose.foundation.layout.preferredWidth -import androidx.compose.foundation.lazy.ExperimentalLazyDsl import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyRowForIndexed -import androidx.compose.material.AmbientEmphasisLevels +import androidx.compose.material.AmbientContentAlpha +import androidx.compose.material.AmbientContentColor +import androidx.compose.material.ContentAlpha import androidx.compose.material.Divider import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme -import androidx.compose.material.ProvideEmphasis +import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.PlaylistAdd import androidx.compose.material.icons.rounded.PlayCircleFilled import androidx.compose.material.ripple.RippleIndication import androidx.compose.runtime.Composable +import androidx.compose.runtime.Providers import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment @@ -73,7 +73,6 @@ import dev.chrisbanes.accompanist.coil.CoilImage import java.time.format.DateTimeFormatter import java.time.format.FormatStyle -@OptIn(ExperimentalLazyDsl::class) @Composable fun PodcastCategory( categoryId: Long, @@ -166,29 +165,27 @@ fun EpisodeListItem( ) } - ProvideEmphasis(AmbientEmphasisLevels.current.high) { - Text( - text = episode.title, - maxLines = 2, - style = MaterialTheme.typography.subtitle1, - modifier = Modifier.constrainAs(episodeTitle) { - linkTo( - start = parent.start, - end = image.start, - startMargin = Keyline1, - endMargin = 16.dp, - bias = 0f - ) - top.linkTo(parent.top, 16.dp) + Text( + text = episode.title, + maxLines = 2, + style = MaterialTheme.typography.subtitle1, + modifier = Modifier.constrainAs(episodeTitle) { + linkTo( + start = parent.start, + end = image.start, + startMargin = Keyline1, + endMargin = 16.dp, + bias = 0f + ) + top.linkTo(parent.top, 16.dp) - width = Dimension.preferredWrapContent - } - ) - } + width = Dimension.preferredWrapContent + } + ) val titleImageBarrier = createBottomBarrier(podcastTitle, image) - ProvideEmphasis(AmbientEmphasisLevels.current.medium) { + Providers(AmbientContentAlpha provides ContentAlpha.medium) { Text( text = podcast.title, maxLines = 2, @@ -208,25 +205,23 @@ fun EpisodeListItem( ) } - ProvideEmphasis(AmbientEmphasisLevels.current.high) { - Image( - asset = Icons.Rounded.PlayCircleFilled, - contentScale = ContentScale.Fit, - colorFilter = ColorFilter.tint(AmbientContentColor.current), - modifier = Modifier - .clickable(indication = RippleIndication(bounded = false, radius = 24.dp)) { - /* TODO */ - } - .preferredSize(36.dp) - .constrainAs(playIcon) { - start.linkTo(parent.start, Keyline1) - top.linkTo(titleImageBarrier, margin = 16.dp) - bottom.linkTo(parent.bottom, 16.dp) - } - ) - } + Image( + asset = Icons.Rounded.PlayCircleFilled, + contentScale = ContentScale.Fit, + colorFilter = ColorFilter.tint(AmbientContentColor.current), + modifier = Modifier + .clickable(indication = RippleIndication(bounded = false, radius = 24.dp)) { + /* TODO */ + } + .preferredSize(36.dp) + .constrainAs(playIcon) { + start.linkTo(parent.start, Keyline1) + top.linkTo(titleImageBarrier, margin = 16.dp) + bottom.linkTo(parent.bottom, 16.dp) + } + ) - ProvideEmphasis(AmbientEmphasisLevels.current.medium) { + Providers(AmbientContentAlpha provides ContentAlpha.medium) { Text( text = when { episode.duration != null -> { @@ -326,24 +321,20 @@ private fun TopPodcastRowItem( ) } - ProvideEmphasis(AmbientEmphasisLevels.current.high) { - ToggleFollowPodcastIconButton( - onClick = onToggleFollowClicked, - isFollowed = isFollowed, - modifier = Modifier.align(Alignment.BottomEnd) - ) - } - } - - ProvideEmphasis(AmbientEmphasisLevels.current.high) { - Text( - text = podcastTitle, - style = MaterialTheme.typography.body2, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.padding(top = 8.dp).fillMaxWidth() + ToggleFollowPodcastIconButton( + onClick = onToggleFollowClicked, + isFollowed = isFollowed, + modifier = Modifier.align(Alignment.BottomEnd) ) } + + Text( + text = podcastTitle, + style = MaterialTheme.typography.body2, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.padding(top = 8.dp).fillMaxWidth() + ) } } diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/discover/Discover.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/discover/Discover.kt index 29da31062a..802996cc47 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/discover/Discover.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/home/discover/Discover.kt @@ -22,8 +22,6 @@ import androidx.compose.animation.core.LinearOutSlowInEasing import androidx.compose.animation.core.TransitionDefinition import androidx.compose.animation.core.transitionDefinition import androidx.compose.animation.core.tween -import androidx.compose.foundation.Text -import androidx.compose.foundation.contentColor import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -35,6 +33,7 @@ import androidx.compose.material.ScrollableTabRow import androidx.compose.material.Surface import androidx.compose.material.Tab import androidx.compose.material.TabPosition +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.emptyContent diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/util/Buttons.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/util/Buttons.kt index 7de9a3843d..6f0685d496 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/util/Buttons.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/util/Buttons.kt @@ -17,10 +17,10 @@ package com.example.jetcaster.util import androidx.compose.animation.animate -import androidx.compose.foundation.AmbientContentColor import androidx.compose.foundation.background import androidx.compose.foundation.layout.padding -import androidx.compose.material.AmbientEmphasisLevels +import androidx.compose.material.AmbientContentColor +import androidx.compose.material.ContentAlpha import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme @@ -52,7 +52,7 @@ fun ToggleFollowPodcastIconButton( tint = animate( when { isFollowed -> AmbientContentColor.current - else -> AmbientEmphasisLevels.current.high.applyEmphasis(Color.Black) + else -> Color.Black.copy(alpha = ContentAlpha.high) } ), modifier = Modifier diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/util/Insets.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/util/Insets.kt deleted file mode 100644 index c75d9ee4f2..0000000000 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/util/Insets.kt +++ /dev/null @@ -1,610 +0,0 @@ -/* - * Copyright 2020 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. - */ - -@file:Suppress("NOTHING_TO_INLINE", "unused") - -package com.example.jetcaster.util - -import android.view.View -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.height -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Providers -import androidx.compose.runtime.Stable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.onCommit -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.runtime.staticAmbientOf -import androidx.compose.ui.LayoutModifier -import androidx.compose.ui.Measurable -import androidx.compose.ui.MeasureScope -import androidx.compose.ui.Modifier -import androidx.compose.ui.composed -import androidx.compose.ui.layout.IntrinsicMeasurable -import androidx.compose.ui.layout.IntrinsicMeasureScope -import androidx.compose.ui.platform.DensityAmbient -import androidx.compose.ui.platform.LayoutDirectionAmbient -import androidx.compose.ui.platform.ViewAmbient -import androidx.compose.ui.unit.Constraints -import androidx.compose.ui.unit.Density -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.LayoutDirection -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.offset -import androidx.core.view.ViewCompat -import androidx.core.view.WindowInsetsCompat -import androidx.core.view.WindowInsetsCompat.Type - -/** - * Taken from https://goo.gle/compose-insets. Requires androidx.core:core v1.5.0-alpha02+ - */ - -/** - * Main holder of our inset values. - */ -@Stable -class DisplayInsets { - /** - * Inset values which match [WindowInsetsCompat.Type.systemBars] - */ - val systemBars = Insets() - - /** - * Inset values which match [WindowInsetsCompat.Type.systemGestures] - */ - val systemGestures = Insets() - - /** - * Inset values which match [WindowInsetsCompat.Type.navigationBars] - */ - val navigationBars = Insets() - - /** - * Inset values which match [WindowInsetsCompat.Type.statusBars] - */ - val statusBars = Insets() - - /** - * Inset values which match [WindowInsetsCompat.Type.ime] - */ - val ime = Insets() -} - -@Stable -class Insets { - /** - * The left dimension of these insets in pixels. - */ - var left by mutableStateOf(0) - internal set - - /** - * The top dimension of these insets in pixels. - */ - var top by mutableStateOf(0) - internal set - - /** - * The right dimension of these insets in pixels. - */ - var right by mutableStateOf(0) - internal set - - /** - * The bottom dimension of these insets in pixels. - */ - var bottom by mutableStateOf(0) - internal set - - /** - * Whether the insets are currently visible. - */ - var isVisible by mutableStateOf(true) - internal set -} - -val InsetsAmbient = staticAmbientOf() - -/** - * Applies any [WindowInsetsCompat] values to [InsetsAmbient], which are then available - * within [content]. - * - * @param consumeWindowInsets Whether to consume any [WindowInsetsCompat]s which are dispatched to - * the host view. Defaults to `true`. - */ -@Composable -fun ProvideDisplayInsets( - consumeWindowInsets: Boolean = true, - content: @Composable () -> Unit -) { - val view = ViewAmbient.current - - val displayInsets = remember { DisplayInsets() } - - onCommit(view) { - ViewCompat.setOnApplyWindowInsetsListener(view) { _, windowInsets -> - displayInsets.systemBars.updateFrom(windowInsets, Type.systemBars()) - displayInsets.systemGestures.updateFrom(windowInsets, Type.systemGestures()) - displayInsets.statusBars.updateFrom(windowInsets, Type.statusBars()) - displayInsets.navigationBars.updateFrom(windowInsets, Type.navigationBars()) - displayInsets.ime.updateFrom(windowInsets, Type.ime()) - - if (consumeWindowInsets) WindowInsetsCompat.CONSUMED else windowInsets - } - - // Add an OnAttachStateChangeListener to request an inset pass each time we're attached - // to the window - val attachListener = object : View.OnAttachStateChangeListener { - override fun onViewAttachedToWindow(v: View) = v.requestApplyInsets() - override fun onViewDetachedFromWindow(v: View) = Unit - } - view.addOnAttachStateChangeListener(attachListener) - - if (view.isAttachedToWindow) { - // If the view is already attached, we can request an inset pass now - view.requestApplyInsets() - } - - onDispose { - view.removeOnAttachStateChangeListener(attachListener) - } - } - - Providers(InsetsAmbient provides displayInsets) { - content() - } -} - -/** - * Updates our mutable state backed [Insets] from an Android system insets. - */ -private fun Insets.updateFrom(windowInsets: WindowInsetsCompat, type: Int) { - val insets = windowInsets.getInsets(type) - left = insets.left - top = insets.top - right = insets.right - bottom = insets.bottom - - isVisible = windowInsets.isVisible(type) -} - -/** - * Selectively apply additional space which matches the width/height of any system bars present - * on the respective edges of the screen. - * - * @param enabled Whether to apply padding using the system bars dimensions on the respective edges. - * Defaults to `true`. - */ -fun Modifier.systemBarsPadding(enabled: Boolean = true) = composed { - insetsPadding( - insets = InsetsAmbient.current.systemBars, - left = enabled, - top = enabled, - right = enabled, - bottom = enabled - ) -} - -/** - * Apply additional space which matches the height of the status bars height along the top edge - * of the content. - */ -fun Modifier.statusBarsPadding() = composed { - insetsPadding(insets = InsetsAmbient.current.statusBars, top = true) -} - -/** - * Apply additional space which matches the height of the navigation bars height - * along the [bottom] edge of the content, and additional space which matches the width of - * the navigation bars on the respective [left] and [right] edges. - * - * @param bottom Whether to apply padding to the bottom edge, which matches the navigation bars - * height (if present) at the bottom edge of the screen. Defaults to `true`. - * @param left Whether to apply padding to the left edge, which matches the navigation bars width - * (if present) on the left edge of the screen. Defaults to `true`. - * @param right Whether to apply padding to the right edge, which matches the navigation bars width - * (if present) on the right edge of the screen. Defaults to `true`. - */ -fun Modifier.navigationBarsPadding( - bottom: Boolean = true, - left: Boolean = true, - right: Boolean = true -) = composed { - insetsPadding( - insets = InsetsAmbient.current.navigationBars, - left = left, - right = right, - bottom = bottom - ) -} - -/** - * Declare the height of the content to match the height of the status bars exactly. - * - * This is very handy when used with `Spacer` to push content below the status bars: - * ``` - * Column { - * Spacer(Modifier.statusBarHeight()) - * - * // Content to be drawn below status bars (y-axis) - * } - * ``` - * - * It's also useful when used to draw a scrim which matches the status bars: - * ``` - * Spacer( - * Modifier.statusBarHeight() - * .fillMaxWidth() - * .drawBackground(MaterialTheme.colors.background.copy(alpha = 0.3f) - * ) - * ``` - * - * Internally this matches the behavior of the [Modifier.height] modifier. - * - * @param additional Any additional height to add to the status bars size. - */ -fun Modifier.statusBarsHeight(additional: Dp = 0.dp) = composed { - InsetsSizeModifier( - insets = InsetsAmbient.current.statusBars, - heightSide = VerticalSide.Top, - additionalHeight = additional - ) -} - -/** - * Declare the height of the content to match the height of the status bars exactly. - * - * This is very handy when used with `Spacer` to push content below the status bars: - * ``` - * Column { - * Spacer(Modifier.statusBarHeight()) - * - * // Content to be drawn below status bars (y-axis) - * } - * ``` - * - * It's also useful when used to draw a scrim which matches the status bars: - * ``` - * Spacer( - * Modifier.statusBarHeight() - * .fillMaxWidth() - * .drawBackground(MaterialTheme.colors.background.copy(alpha = 0.3f) - * ) - * ``` - * - * Internally this matches the behavior of the [Modifier.height] modifier. - */ -inline fun Modifier.statusBarsHeight() = statusBarsHeightPlus(0.dp) - -/** - * Declare the height of the content to match the height of the status bars, plus some - * additional height passed in via [additional]. - * - * As an example, this could be used with `Spacer` to push content below the status bar - * and app bars: - * - * ``` - * Column { - * Spacer(Modifier.statusBarHeightPlus(56.dp)) - * - * // Content to be drawn below status bars and app bar (y-axis) - * } - * ``` - * - * Internally this matches the behavior of the [Modifier.height] modifier. - * - * @param additional Any additional height to add to the status bars size. - */ -fun Modifier.statusBarsHeightPlus(additional: Dp) = composed { - InsetsSizeModifier( - insets = InsetsAmbient.current.statusBars, - heightSide = VerticalSide.Top, - additionalHeight = additional - ) -} - -/** - * Declare the preferred height of the content to match the height of the navigation bars when - * present at the bottom of the screen. - * - * This is very handy when used with `Spacer` to push content below the navigation bars: - * ``` - * Column { - * // Content to be drawn above status bars (y-axis) - * Spacer(Modifier.navigationBarHeight()) - * } - * ``` - * - * It's also useful when used to draw a scrim which matches the navigation bars: - * ``` - * Spacer( - * Modifier.navigationBarHeight() - * .fillMaxWidth() - * .drawBackground(MaterialTheme.colors.background.copy(alpha = 0.3f) - * ) - * ``` - * - * Internally this matches the behavior of the [Modifier.height] modifier. - */ -inline fun Modifier.navigationBarsHeight() = navigationBarsHeightPlus(0.dp) - -/** - * Declare the height of the content to match the height of the navigation bars, plus some - * additional height passed in via [additional] - * - * As an example, this could be used with `Spacer` to push content above the navigation bar - * and bottom app bars: - * - * ``` - * Column { - * // Content to be drawn above navigation bars and bottom app bar (y-axis) - * - * Spacer(Modifier.statusBarHeightPlus(48.dp)) - * } - * ``` - * - * Internally this matches the behavior of the [Modifier.height] modifier. - * - * @param additional Any additional height to add to the status bars size. - */ -fun Modifier.navigationBarsHeightPlus(additional: Dp) = composed { - InsetsSizeModifier( - insets = InsetsAmbient.current.navigationBars, - heightSide = VerticalSide.Bottom, - additionalHeight = additional - ) -} - -enum class HorizontalSide { Left, Right } -enum class VerticalSide { Top, Bottom } - -/** - * Declare the preferred width of the content to match the width of the navigation bars, - * on the given [side]. - * - * This is very handy when used with `Spacer` to push content inside from any vertical - * navigation bars (typically when the device is in landscape): - * ``` - * Row { - * Spacer(Modifier.navigationBarWidth(HorizontalSide.Left)) - * - * // Content to be inside the navigation bars (x-axis) - * - * Spacer(Modifier.navigationBarWidth(HorizontalSide.Right)) - * } - * ``` - * - * It's also useful when used to draw a scrim which matches the navigation bars: - * ``` - * Spacer( - * Modifier.navigationBarWidth(HorizontalSide.Left) - * .fillMaxHeight() - * .drawBackground(MaterialTheme.colors.background.copy(alpha = 0.3f) - * ) - * ``` - * - * Internally this matches the behavior of the [Modifier.height] modifier. - * - * @param side The navigation bar side to use as the source for the width. - */ -inline fun Modifier.navigationBarWidth(side: HorizontalSide) = navigationBarsWidthPlus(side, 0.dp) - -/** - * Declare the preferred width of the content to match the width of the navigation bars, - * on the given [side], plus additional width passed in via [additional]. - * - * Internally this matches the behavior of the [Modifier.height] modifier. - * - * @param side The navigation bar side to use as the source for the width. - * @param additional Any additional width to add to the status bars size. - */ -fun Modifier.navigationBarsWidthPlus( - side: HorizontalSide, - additional: Dp -) = composed { - InsetsSizeModifier( - insets = InsetsAmbient.current.navigationBars, - widthSide = side, - additionalWidth = additional - ) -} - -/** - * Returns the current insets converted into a [InnerPadding]. - * - * @param start Whether to apply the inset on the start dimension. - * @param top Whether to apply the inset on the top dimension. - * @param end Whether to apply the inset on the end dimension. - * @param bottom Whether to apply the inset on the bottom dimension. - */ -@Composable -fun Insets.toInnerPadding( - start: Boolean = true, - top: Boolean = true, - end: Boolean = true, - bottom: Boolean = true -): PaddingValues = with(DensityAmbient.current) { - val layoutDirection = LayoutDirectionAmbient.current - PaddingValues( - start = when { - start && layoutDirection == LayoutDirection.Ltr -> this@toInnerPadding.left.toDp() - start && layoutDirection == LayoutDirection.Rtl -> this@toInnerPadding.right.toDp() - else -> 0.dp - }, - top = when { - top -> this@toInnerPadding.top.toDp() - else -> 0.dp - }, - end = when { - end && layoutDirection == LayoutDirection.Ltr -> this@toInnerPadding.right.toDp() - end && layoutDirection == LayoutDirection.Rtl -> this@toInnerPadding.left.toDp() - else -> 0.dp - }, - bottom = when { - bottom -> this@toInnerPadding.bottom.toDp() - else -> 0.dp - } - ) -} - -/** - * Allows conditional setting of [insets] on each dimension. - */ -private inline fun Modifier.insetsPadding( - insets: Insets, - left: Boolean = false, - top: Boolean = false, - right: Boolean = false, - bottom: Boolean = false -) = this then InsetsPaddingModifier(insets, left, top, right, bottom) - -private data class InsetsPaddingModifier( - private val insets: Insets, - private val applyLeft: Boolean = false, - private val applyTop: Boolean = false, - private val applyRight: Boolean = false, - private val applyBottom: Boolean = false -) : LayoutModifier { - override fun MeasureScope.measure( - measurable: Measurable, - constraints: Constraints - ): MeasureScope.MeasureResult { - val left = if (applyLeft) insets.left else 0 - val top = if (applyTop) insets.top else 0 - val right = if (applyRight) insets.right else 0 - val bottom = if (applyBottom) insets.bottom else 0 - val horizontal = left + right - val vertical = top + bottom - - val placeable = measurable.measure(constraints.offset(-horizontal, -vertical)) - - val width = (placeable.width + horizontal) - .coerceIn(constraints.minWidth, constraints.maxWidth) - val height = (placeable.height + vertical) - .coerceIn(constraints.minHeight, constraints.maxHeight) - return layout(width, height) { - placeable.place(left, top) - } - } -} - -private data class InsetsSizeModifier( - private val insets: Insets, - private val widthSide: HorizontalSide? = null, - private val additionalWidth: Dp = 0.dp, - private val heightSide: VerticalSide? = null, - private val additionalHeight: Dp = 0.dp -) : LayoutModifier { - private val Density.targetConstraints: Constraints - get() { - val additionalWidthPx = additionalWidth.toIntPx() - val additionalHeightPx = additionalHeight.toIntPx() - return Constraints( - minWidth = additionalWidthPx + when (widthSide) { - HorizontalSide.Left -> insets.left - HorizontalSide.Right -> insets.right - null -> 0 - }, - minHeight = additionalHeightPx + when (heightSide) { - VerticalSide.Top -> insets.top - VerticalSide.Bottom -> insets.bottom - null -> 0 - }, - maxWidth = when (widthSide) { - HorizontalSide.Left -> insets.left + additionalWidthPx - HorizontalSide.Right -> insets.right + additionalWidthPx - null -> Constraints.Infinity - }, - maxHeight = when (heightSide) { - VerticalSide.Top -> insets.top + additionalHeightPx - VerticalSide.Bottom -> insets.bottom + additionalHeightPx - null -> Constraints.Infinity - } - ) - } - - override fun MeasureScope.measure( - measurable: Measurable, - constraints: Constraints - ): MeasureScope.MeasureResult { - val wrappedConstraints = targetConstraints.let { targetConstraints -> - val resolvedMinWidth = if (widthSide != null) { - targetConstraints.minWidth - } else { - constraints.minWidth.coerceAtMost(targetConstraints.maxWidth) - } - val resolvedMaxWidth = if (widthSide != null) { - targetConstraints.maxWidth - } else { - constraints.maxWidth.coerceAtLeast(targetConstraints.minWidth) - } - val resolvedMinHeight = if (heightSide != null) { - targetConstraints.minHeight - } else { - constraints.minHeight.coerceAtMost(targetConstraints.maxHeight) - } - val resolvedMaxHeight = if (heightSide != null) { - targetConstraints.maxHeight - } else { - constraints.maxHeight.coerceAtLeast(targetConstraints.minHeight) - } - Constraints( - resolvedMinWidth, - resolvedMaxWidth, - resolvedMinHeight, - resolvedMaxHeight - ) - } - val placeable = measurable.measure(wrappedConstraints) - return layout(placeable.width, placeable.height) { - placeable.place(0, 0) - } - } - - override fun IntrinsicMeasureScope.minIntrinsicWidth( - measurable: IntrinsicMeasurable, - height: Int - ) = measurable.minIntrinsicWidth(height).let { - val constraints = targetConstraints - it.coerceIn(constraints.minWidth, constraints.maxWidth) - } - - override fun IntrinsicMeasureScope.maxIntrinsicWidth( - measurable: IntrinsicMeasurable, - height: Int - ) = measurable.maxIntrinsicWidth(height).let { - val constraints = targetConstraints - it.coerceIn(constraints.minWidth, constraints.maxWidth) - } - - override fun IntrinsicMeasureScope.minIntrinsicHeight( - measurable: IntrinsicMeasurable, - width: Int - ) = measurable.minIntrinsicHeight(width).let { - val constraints = targetConstraints - it.coerceIn(constraints.minHeight, constraints.maxHeight) - } - - override fun IntrinsicMeasureScope.maxIntrinsicHeight( - measurable: IntrinsicMeasurable, - width: Int - ) = measurable.maxIntrinsicHeight(width).let { - val constraints = targetConstraints - it.coerceIn(constraints.minHeight, constraints.maxHeight) - } -} diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/util/Pager.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/util/Pager.kt index c2cf1d42c6..08a1fd72a8 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/util/Pager.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/util/Pager.kt @@ -31,11 +31,11 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.runtime.structuralEqualityPolicy import androidx.compose.ui.Alignment -import androidx.compose.ui.Layout -import androidx.compose.ui.Measurable import androidx.compose.ui.Modifier -import androidx.compose.ui.ParentDataModifier import androidx.compose.ui.gesture.scrollorientationlocking.Orientation +import androidx.compose.ui.layout.Layout +import androidx.compose.ui.layout.Measurable +import androidx.compose.ui.layout.ParentDataModifier import androidx.compose.ui.unit.Density import kotlin.math.roundToInt diff --git a/Jetcaster/buildSrc/src/main/java/com/example/jetcaster/buildsrc/dependencies.kt b/Jetcaster/buildSrc/src/main/java/com/example/jetcaster/buildsrc/dependencies.kt index dc674ad645..ad5e16599a 100644 --- a/Jetcaster/buildSrc/src/main/java/com/example/jetcaster/buildsrc/dependencies.kt +++ b/Jetcaster/buildSrc/src/main/java/com/example/jetcaster/buildsrc/dependencies.kt @@ -29,8 +29,9 @@ object Libs { const val material = "com.google.android.material:material:1.1.0" object Accompanist { - private const val version = "0.3.2" + private const val version = "0.3.3.1" const val coil = "dev.chrisbanes.accompanist:accompanist-coil:$version" + const val insets = "dev.chrisbanes.accompanist:accompanist-insets:$version" } object Kotlin { @@ -41,7 +42,7 @@ object Libs { } object Coroutines { - private const val version = "1.3.9" + private const val version = "1.4.0" const val core = "org.jetbrains.kotlinx:kotlinx-coroutines-core:$version" const val android = "org.jetbrains.kotlinx:kotlinx-coroutines-android:$version" const val test = "org.jetbrains.kotlinx:kotlinx-coroutines-test:$version" @@ -54,7 +55,7 @@ object Libs { } object AndroidX { - const val appcompat = "androidx.appcompat:appcompat:1.2.0-rc01" + const val appcompat = "androidx.appcompat:appcompat:1.2.0" const val palette = "androidx.palette:palette:1.0.0" const val core = "androidx.core:core:1.5.0-alpha04" @@ -62,7 +63,7 @@ object Libs { object Compose { private const val snapshot = "" - const val version = "1.0.0-alpha06" + const val version = "1.0.0-alpha07" @get:JvmStatic val snapshotUrl: String @@ -77,7 +78,6 @@ object Libs { const val materialIconsExtended = "androidx.compose.material:material-icons-extended:${version}" const val tooling = "androidx.ui:ui-tooling:${version}" - const val test = "androidx.ui:ui-test:${version}" } object Test { diff --git a/Jetchat/app/build.gradle b/Jetchat/app/build.gradle index 94ea28d959..e29b6ea069 100644 --- a/Jetchat/app/build.gradle +++ b/Jetchat/app/build.gradle @@ -81,6 +81,11 @@ android { kotlinCompilerExtensionVersion Libs.AndroidX.Compose.version } + packagingOptions { + exclude "META-INF/licenses/**" + exclude "META-INF/AL2.0" + exclude "META-INF/LGPL2.1" + } } dependencies { diff --git a/Jetchat/app/src/androidTest/java/com/example/compose/jetchat/ConversationTest.kt b/Jetchat/app/src/androidTest/java/com/example/compose/jetchat/ConversationTest.kt index 65ff28d642..82e1510c86 100644 --- a/Jetchat/app/src/androidTest/java/com/example/compose/jetchat/ConversationTest.kt +++ b/Jetchat/app/src/androidTest/java/com/example/compose/jetchat/ConversationTest.kt @@ -20,16 +20,16 @@ import androidx.activity.ComponentActivity import androidx.compose.runtime.Providers import androidx.compose.runtime.collectAsState import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.center +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithLabel +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performGesture +import androidx.compose.ui.test.swipe import androidx.compose.ui.unit.milliseconds -import androidx.ui.test.assertIsDisplayed -import androidx.ui.test.center -import androidx.ui.test.createAndroidComposeRule -import androidx.ui.test.onNodeWithLabel -import androidx.ui.test.onNodeWithTag -import androidx.ui.test.onNodeWithText -import androidx.ui.test.performClick -import androidx.ui.test.performGesture -import androidx.ui.test.swipe import com.example.compose.jetchat.conversation.BackPressedDispatcherAmbient import com.example.compose.jetchat.conversation.ConversationContent import com.example.compose.jetchat.conversation.ConversationTestTag diff --git a/Jetchat/app/src/androidTest/java/com/example/compose/jetchat/NavigationTest.kt b/Jetchat/app/src/androidTest/java/com/example/compose/jetchat/NavigationTest.kt index 76254051b3..4bff981445 100644 --- a/Jetchat/app/src/androidTest/java/com/example/compose/jetchat/NavigationTest.kt +++ b/Jetchat/app/src/androidTest/java/com/example/compose/jetchat/NavigationTest.kt @@ -19,12 +19,12 @@ package com.example.compose.jetchat import android.view.View import androidx.activity.ComponentActivity import androidx.compose.runtime.Providers +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithText import androidx.navigation.NavController import androidx.navigation.Navigation import androidx.test.espresso.Espresso -import androidx.ui.test.assertIsDisplayed -import androidx.ui.test.createAndroidComposeRule -import androidx.ui.test.onNodeWithText import com.example.compose.jetchat.conversation.BackPressedDispatcherAmbient import com.example.compose.jetchat.conversation.ConversationContent import com.example.compose.jetchat.data.exampleUiState diff --git a/Jetchat/app/src/androidTest/java/com/example/compose/jetchat/UserInputTest.kt b/Jetchat/app/src/androidTest/java/com/example/compose/jetchat/UserInputTest.kt index 505ae383ae..ffb8dc7ea1 100644 --- a/Jetchat/app/src/androidTest/java/com/example/compose/jetchat/UserInputTest.kt +++ b/Jetchat/app/src/androidTest/java/com/example/compose/jetchat/UserInputTest.kt @@ -18,20 +18,20 @@ package com.example.compose.jetchat import androidx.activity.ComponentActivity import androidx.compose.runtime.Providers +import androidx.compose.ui.test.SemanticsMatcher +import androidx.compose.ui.test.SemanticsNodeInteraction +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.assertIsEnabled +import androidx.compose.ui.test.assertIsNotEnabled +import androidx.compose.ui.test.hasAnyAncestor +import androidx.compose.ui.test.hasInputMethodsSupport +import androidx.compose.ui.test.hasLabel +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithLabel +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performTextInput import androidx.test.espresso.Espresso -import androidx.ui.test.SemanticsMatcher -import androidx.ui.test.SemanticsNodeInteraction -import androidx.ui.test.assertIsDisplayed -import androidx.ui.test.assertIsEnabled -import androidx.ui.test.assertIsNotEnabled -import androidx.ui.test.createAndroidComposeRule -import androidx.ui.test.hasAnyAncestor -import androidx.ui.test.hasInputMethodsSupport -import androidx.ui.test.hasLabel -import androidx.ui.test.onNodeWithLabel -import androidx.ui.test.onNodeWithText -import androidx.ui.test.performClick -import androidx.ui.test.performTextInput import com.example.compose.jetchat.conversation.BackPressedDispatcherAmbient import com.example.compose.jetchat.conversation.ConversationContent import com.example.compose.jetchat.conversation.KeyboardShownKey diff --git a/Jetchat/app/src/androidTest/java/com/example/compose/jetchat/Utils.kt b/Jetchat/app/src/androidTest/java/com/example/compose/jetchat/Utils.kt index 6aaac6c9c0..9ae5ac39d1 100644 --- a/Jetchat/app/src/androidTest/java/com/example/compose/jetchat/Utils.kt +++ b/Jetchat/app/src/androidTest/java/com/example/compose/jetchat/Utils.kt @@ -16,14 +16,12 @@ package com.example.compose.jetchat -import androidx.ui.test.ComposeTestRule -import androidx.ui.test.onRoot -import androidx.ui.test.printToLog +import androidx.compose.ui.test.junit4.ComposeTestRule +import androidx.compose.ui.test.onRoot +import androidx.compose.ui.test.printToLog /** * Used to debug the semantic tree. - * - * TODO: Replace with aosp/1355659 */ fun ComposeTestRule.dumpSemanticNodes() { this.onRoot().printToLog(tag = "JetchatLog") diff --git a/Jetchat/app/src/main/java/com/example/compose/jetchat/UiExtras.kt b/Jetchat/app/src/main/java/com/example/compose/jetchat/UiExtras.kt index eab7464336..ff3ae21393 100644 --- a/Jetchat/app/src/main/java/com/example/compose/jetchat/UiExtras.kt +++ b/Jetchat/app/src/main/java/com/example/compose/jetchat/UiExtras.kt @@ -16,9 +16,9 @@ package com.example.compose.jetchat -import androidx.compose.foundation.Text import androidx.compose.material.AlertDialog import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text import androidx.compose.material.TextButton import androidx.compose.runtime.Composable diff --git a/Jetchat/app/src/main/java/com/example/compose/jetchat/components/AnimatingFabContent.kt b/Jetchat/app/src/main/java/com/example/compose/jetchat/components/AnimatingFabContent.kt index a261b78912..2fe3abec88 100644 --- a/Jetchat/app/src/main/java/com/example/compose/jetchat/components/AnimatingFabContent.kt +++ b/Jetchat/app/src/main/java/com/example/compose/jetchat/components/AnimatingFabContent.kt @@ -25,9 +25,9 @@ import androidx.compose.animation.transition import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable import androidx.compose.runtime.remember -import androidx.compose.ui.Layout import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawOpacity +import androidx.compose.ui.layout.Layout import androidx.compose.ui.util.lerp import kotlin.math.roundToInt diff --git a/Jetchat/app/src/main/java/com/example/compose/jetchat/components/BaseLineHeightModifier.kt b/Jetchat/app/src/main/java/com/example/compose/jetchat/components/BaseLineHeightModifier.kt index 6f645df20b..e15486fc19 100644 --- a/Jetchat/app/src/main/java/com/example/compose/jetchat/components/BaseLineHeightModifier.kt +++ b/Jetchat/app/src/main/java/com/example/compose/jetchat/components/BaseLineHeightModifier.kt @@ -16,12 +16,13 @@ package com.example.compose.jetchat.components -import androidx.compose.foundation.text.FirstBaseline -import androidx.compose.foundation.text.LastBaseline -import androidx.compose.ui.LayoutModifier -import androidx.compose.ui.Measurable -import androidx.compose.ui.MeasureScope import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.FirstBaseline +import androidx.compose.ui.layout.LastBaseline +import androidx.compose.ui.layout.LayoutModifier +import androidx.compose.ui.layout.Measurable +import androidx.compose.ui.layout.MeasureResult +import androidx.compose.ui.layout.MeasureScope import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Dp @@ -45,7 +46,7 @@ data class BaselineHeightModifier( override fun MeasureScope.measure( measurable: Measurable, constraints: Constraints - ): MeasureScope.MeasureResult { + ): MeasureResult { val textPlaceable = measurable.measure(constraints) val firstBaseline = textPlaceable[FirstBaseline] diff --git a/Jetchat/app/src/main/java/com/example/compose/jetchat/components/JetchatAppBar.kt b/Jetchat/app/src/main/java/com/example/compose/jetchat/components/JetchatAppBar.kt index 9e5157e6b2..13c61488ca 100644 --- a/Jetchat/app/src/main/java/com/example/compose/jetchat/components/JetchatAppBar.kt +++ b/Jetchat/app/src/main/java/com/example/compose/jetchat/components/JetchatAppBar.kt @@ -17,7 +17,6 @@ package com.example.compose.jetchat.components import androidx.compose.foundation.Image -import androidx.compose.foundation.Text import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -25,6 +24,7 @@ import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.padding import androidx.compose.material.Divider import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text import androidx.compose.material.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier diff --git a/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/Conversation.kt b/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/Conversation.kt index 60ef17c249..0bad5e2ebd 100644 --- a/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/Conversation.kt +++ b/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/Conversation.kt @@ -20,7 +20,6 @@ import androidx.compose.foundation.ClickableText import androidx.compose.foundation.Image import androidx.compose.foundation.ScrollState import androidx.compose.foundation.ScrollableColumn -import androidx.compose.foundation.Text import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box @@ -32,29 +31,32 @@ 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.paddingFrom import androidx.compose.foundation.layout.preferredHeight import androidx.compose.foundation.layout.preferredSize import androidx.compose.foundation.layout.preferredWidth -import androidx.compose.foundation.layout.relativePaddingFrom import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.LastBaseline -import androidx.compose.material.AmbientEmphasisLevels +import androidx.compose.material.AmbientContentAlpha +import androidx.compose.material.AmbientContentColor +import androidx.compose.material.ContentAlpha import androidx.compose.material.Divider import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme -import androidx.compose.material.ProvideEmphasis import androidx.compose.material.Surface +import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Info import androidx.compose.material.icons.outlined.Search import androidx.compose.runtime.Composable +import androidx.compose.runtime.Providers import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.layout.LastBaseline import androidx.compose.ui.platform.DensityAmbient import androidx.compose.ui.platform.UriHandlerAmbient import androidx.compose.ui.platform.testTag @@ -137,8 +139,7 @@ fun ChannelNameBar( style = MaterialTheme.typography.subtitle1 ) // Number of members - // TODO: Multiple emphasis layers - https://issuetracker.google.com/159017896 - ProvideEmphasis(emphasis = AmbientEmphasisLevels.current.medium) { + Providers(AmbientContentAlpha provides ContentAlpha.medium) { Text( text = stringResource(R.string.members, channelMembers), style = MaterialTheme.typography.caption, @@ -148,7 +149,7 @@ fun ChannelNameBar( } }, actions = { - ProvideEmphasis(emphasis = AmbientEmphasisLevels.current.medium) { + Providers(AmbientContentAlpha provides ContentAlpha.medium) { // Search icon Icon( asset = Icons.Outlined.Search, @@ -311,17 +312,15 @@ fun AuthorAndTextMessage( private fun AuthorNameTimestamp(msg: Message) { // Combine author and timestamp for a11y. Row(modifier = Modifier.semantics(mergeAllDescendants = true) {}) { - ProvideEmphasis(emphasis = AmbientEmphasisLevels.current.high) { - Text( - text = msg.author, - style = MaterialTheme.typography.subtitle1, - modifier = Modifier - .alignBy(LastBaseline) - .relativePaddingFrom(LastBaseline, after = 8.dp) // Space to 1st bubble - ) - } + Text( + text = msg.author, + style = MaterialTheme.typography.subtitle1, + modifier = Modifier + .alignBy(LastBaseline) + .paddingFrom(LastBaseline, after = 8.dp) // Space to 1st bubble + ) Spacer(modifier = Modifier.preferredWidth(8.dp)) - ProvideEmphasis(emphasis = AmbientEmphasisLevels.current.medium) { + Providers(AmbientContentAlpha provides ContentAlpha.medium) { Text( text = msg.timestamp, style = MaterialTheme.typography.caption, @@ -338,7 +337,7 @@ private val LastChatBubbleShape = RoundedCornerShape(0.dp, 8.dp, 8.dp, 8.dp) fun DayHeader(dayString: String) { Row(modifier = Modifier.padding(vertical = 8.dp, horizontal = 16.dp).preferredHeight(16.dp)) { DayHeaderLine() - ProvideEmphasis(emphasis = AmbientEmphasisLevels.current.medium) { + Providers(AmbientContentAlpha provides ContentAlpha.medium) { Text( text = dayString, modifier = Modifier.padding(horizontal = 16.dp), @@ -373,11 +372,9 @@ fun ChatItemBubble( val bubbleShape = if (lastMessageByAuthor) LastChatBubbleShape else ChatBubbleShape Column { Surface(color = backgroundBubbleColor, shape = bubbleShape) { - ProvideEmphasis(emphasis = AmbientEmphasisLevels.current.high) { - ClickableMessage( - message = message - ) - } + ClickableMessage( + message = message + ) } message.image?.let { @@ -401,7 +398,7 @@ fun ClickableMessage(message: Message) { ClickableText( text = styledMessage, - style = MaterialTheme.typography.body1, + style = MaterialTheme.typography.body1.copy(color = AmbientContentColor.current), modifier = Modifier.padding(8.dp), onClick = { styledMessage diff --git a/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/JumpToBottom.kt b/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/JumpToBottom.kt index 9e124c196a..668469f4d1 100644 --- a/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/JumpToBottom.kt +++ b/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/JumpToBottom.kt @@ -19,12 +19,12 @@ package com.example.compose.jetchat.conversation import androidx.compose.animation.DpPropKey import androidx.compose.animation.core.transitionDefinition import androidx.compose.animation.transition -import androidx.compose.foundation.Text import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.preferredHeight import androidx.compose.material.ExtendedFloatingActionButton import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowDownward import androidx.compose.runtime.Composable diff --git a/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/UserInput.kt b/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/UserInput.kt index ad8646ff7a..7739c6e2ab 100644 --- a/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/UserInput.kt +++ b/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/UserInput.kt @@ -19,14 +19,10 @@ package com.example.compose.jetchat.conversation import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.fadeIn -import androidx.compose.foundation.AmbientContentColor -import androidx.compose.foundation.AmbientTextStyle -import androidx.compose.foundation.BaseTextField import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ScrollState import androidx.compose.foundation.ScrollableRow -import androidx.compose.foundation.Text import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -36,22 +32,26 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.paddingFrom import androidx.compose.foundation.layout.preferredHeight import androidx.compose.foundation.layout.preferredSize -import androidx.compose.foundation.layout.relativePaddingFrom import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.text.FirstBaseline -import androidx.compose.material.AmbientEmphasisLevels +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.AmbientContentAlpha +import androidx.compose.material.AmbientContentColor +import androidx.compose.material.AmbientTextStyle import androidx.compose.material.Button import androidx.compose.material.ButtonConstants +import androidx.compose.material.ContentAlpha import androidx.compose.material.Divider import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme -import androidx.compose.material.ProvideEmphasis import androidx.compose.material.Surface +import androidx.compose.material.Text import androidx.compose.material.TextButton import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.AlternateEmail @@ -60,6 +60,7 @@ import androidx.compose.material.icons.outlined.InsertPhoto import androidx.compose.material.icons.outlined.Mood import androidx.compose.material.icons.outlined.Place import androidx.compose.runtime.Composable +import androidx.compose.runtime.Providers import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.onCommit @@ -76,6 +77,7 @@ import androidx.compose.ui.focusObserver import androidx.compose.ui.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.VectorAsset +import androidx.compose.ui.layout.FirstBaseline import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.SemanticsPropertyKey import androidx.compose.ui.semantics.SemanticsPropertyReceiver @@ -225,16 +227,14 @@ fun FunctionalityNotAvailablePanel() { verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, ) { - ProvideEmphasis(emphasis = AmbientEmphasisLevels.current.high) { - Text( - text = stringResource(id = R.string.not_available), - style = MaterialTheme.typography.subtitle1 - ) - } - ProvideEmphasis(emphasis = AmbientEmphasisLevels.current.medium) { + Text( + text = stringResource(id = R.string.not_available), + style = MaterialTheme.typography.subtitle1 + ) + Providers(AmbientContentAlpha provides ContentAlpha.medium) { Text( text = stringResource(id = R.string.not_available_subtitle), - modifier = Modifier.relativePaddingFrom(FirstBaseline, before = 32.dp), + modifier = Modifier.paddingFrom(FirstBaseline, before = 32.dp), style = MaterialTheme.typography.body2 ) } @@ -308,7 +308,7 @@ private fun UserInputSelector( Spacer(modifier = Modifier.weight(1f)) val disabledContentColor = - AmbientEmphasisLevels.current.disabled.applyEmphasis(MaterialTheme.colors.onSurface) + MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled) val buttonColors = ButtonConstants.defaultButtonColors( disabledBackgroundColor = MaterialTheme.colors.surface, @@ -346,7 +346,7 @@ private fun InputSelectorButton( onClick = onClick, modifier = Modifier.semantics { accessibilityLabel = description } ) { - ProvideEmphasis(emphasis = AmbientEmphasisLevels.current.medium) { + Providers(AmbientContentAlpha provides ContentAlpha.medium) { val tint = if (selected) MaterialTheme.colors.primary else AmbientContentColor.current Icon( icon, @@ -397,38 +397,44 @@ private fun UserInputText( }, horizontalArrangement = Arrangement.End ) { - Box( - modifier = Modifier.preferredHeight(48.dp).weight(1f).align(Alignment.Bottom) - ) { - var lastFocusState by remember { mutableStateOf(FocusState.Inactive) } - BaseTextField( - value = textFieldValue, - onValueChange = { onTextChanged(it) }, - modifier = Modifier - .fillMaxWidth() - .padding(start = 16.dp) - .align(Alignment.CenterStart) - .focusObserver { state -> - if (lastFocusState != state) { - onTextFieldFocused(state == FocusState.Active) - } - lastFocusState = state - }, - keyboardType = keyboardType, - imeAction = ImeAction.Send, - onTextInputStarted = { controller -> keyboardController = controller } - ) - - val disableContentColor = - AmbientEmphasisLevels.current.disabled.applyEmphasis(MaterialTheme.colors.onSurface) - if (textFieldValue.text.isEmpty() && !focusState) { - Text( + Surface { + Box( + modifier = Modifier.preferredHeight(48.dp).weight(1f).align(Alignment.Bottom) + ) { + var lastFocusState by remember { mutableStateOf(FocusState.Inactive) } + BasicTextField( + value = textFieldValue, + onValueChange = { onTextChanged(it) }, modifier = Modifier + .fillMaxWidth() + .padding(start = 16.dp) .align(Alignment.CenterStart) - .padding(start = 16.dp), - text = stringResource(id = R.string.textfield_hint), - style = MaterialTheme.typography.body1.copy(color = disableContentColor) + .focusObserver { state -> + if (lastFocusState != state) { + onTextFieldFocused(state == FocusState.Active) + } + lastFocusState = state + }, + keyboardOptions = KeyboardOptions( + keyboardType = keyboardType, + imeAction = ImeAction.Send + ), + maxLines = 1, + cursorColor = AmbientContentColor.current, + textStyle = AmbientTextStyle.current.copy(color = AmbientContentColor.current) ) + + val disableContentColor = + MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled) + if (textFieldValue.text.isEmpty() && !focusState) { + Text( + modifier = Modifier + .align(Alignment.CenterStart) + .padding(start = 16.dp), + text = stringResource(id = R.string.textfield_hint), + style = MaterialTheme.typography.body1.copy(color = disableContentColor) + ) + } } } } diff --git a/Jetchat/app/src/main/java/com/example/compose/jetchat/profile/Profile.kt b/Jetchat/app/src/main/java/com/example/compose/jetchat/profile/Profile.kt index 67c65bf2d1..a546eeff89 100644 --- a/Jetchat/app/src/main/java/com/example/compose/jetchat/profile/Profile.kt +++ b/Jetchat/app/src/main/java/com/example/compose/jetchat/profile/Profile.kt @@ -19,8 +19,6 @@ package com.example.compose.jetchat.profile import androidx.compose.foundation.Image import androidx.compose.foundation.ScrollState import androidx.compose.foundation.ScrollableColumn -import androidx.compose.foundation.Text -import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -33,24 +31,25 @@ import androidx.compose.foundation.layout.preferredHeight import androidx.compose.foundation.layout.preferredHeightIn import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.rememberScrollState -import androidx.compose.material.AmbientEmphasisLevels +import androidx.compose.material.AmbientContentAlpha +import androidx.compose.material.ContentAlpha import androidx.compose.material.Divider import androidx.compose.material.FloatingActionButton import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme -import androidx.compose.material.ProvideEmphasis import androidx.compose.material.Surface +import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Chat import androidx.compose.material.icons.outlined.Create import androidx.compose.material.icons.outlined.MoreVert import androidx.compose.runtime.Composable +import androidx.compose.runtime.Providers import androidx.compose.runtime.key import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.WithConstraints -import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.layout.WithConstraints import androidx.compose.ui.platform.DensityAmbient import androidx.compose.ui.res.imageResource import androidx.compose.ui.res.stringResource @@ -61,7 +60,7 @@ import com.example.compose.jetchat.R import com.example.compose.jetchat.components.AnimatingFabContent import com.example.compose.jetchat.components.JetchatAppBar import com.example.compose.jetchat.components.baselineHeight -import com.example.compose.jetchat.data.colleagueProfile +import com.example.compose.jetchat.data.meProfile import com.example.compose.jetchat.theme.JetchatTheme @Composable @@ -75,7 +74,7 @@ fun ProfileScreen(userData: ProfileScreenState, onNavIconPressed: () -> Unit = { onNavIconPressed = onNavIconPressed, title = { }, actions = { - ProvideEmphasis(emphasis = AmbientEmphasisLevels.current.medium) { + Providers(AmbientContentAlpha provides ContentAlpha.medium) { // More icon Icon( asset = Icons.Outlined.MoreVert, @@ -152,18 +151,16 @@ private fun NameAndPosition( @Composable private fun Name(userData: ProfileScreenState, modifier: Modifier = Modifier) { - ProvideEmphasis(emphasis = AmbientEmphasisLevels.current.high) { - Text( - text = userData.name, - modifier = modifier, - style = MaterialTheme.typography.h5 - ) - } + Text( + text = userData.name, + modifier = modifier, + style = MaterialTheme.typography.h5 + ) } @Composable private fun Position(userData: ProfileScreenState, modifier: Modifier = Modifier) { - ProvideEmphasis(emphasis = AmbientEmphasisLevels.current.medium) { + Providers(AmbientContentAlpha provides ContentAlpha.medium) { Text( text = userData.position, modifier = modifier, @@ -182,24 +179,17 @@ private fun ProfileHeader( data.photo?.let { val asset = imageResource(id = it) - val ratioAsset = asset.width / asset.height.toFloat() + val ratioAsset = (asset.width / asset.height.toFloat()).coerceAtLeast(1f) - Box( + // TODO: Fix landscape + Image( modifier = Modifier - .fillMaxWidth() - // Allow for landscape and portrait ratios - .preferredHeightIn(max = 320.dp) .aspectRatio(ratioAsset) - .background(Color.LightGray) - ) { - Image( - modifier = Modifier - .fillMaxSize() - .padding(top = offsetDp), - asset = asset, - contentScale = ContentScale.Crop - ) - } + .preferredHeightIn(max = 320.dp) + .padding(top = offsetDp), + asset = asset, + contentScale = ContentScale.FillWidth + ) } } @@ -207,7 +197,7 @@ private fun ProfileHeader( fun ProfileProperty(label: String, value: String, isLink: Boolean = false) { Column(modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 16.dp)) { Divider() - ProvideEmphasis(emphasis = AmbientEmphasisLevels.current.medium) { + Providers(AmbientContentAlpha provides ContentAlpha.medium) { Text( text = label, modifier = Modifier.baselineHeight(24.dp), @@ -219,13 +209,11 @@ fun ProfileProperty(label: String, value: String, isLink: Boolean = false) { } else { MaterialTheme.typography.body1 } - ProvideEmphasis(emphasis = AmbientEmphasisLevels.current.high) { - Text( - text = value, - modifier = Modifier.baselineHeight(24.dp), - style = style - ) - } + Text( + text = value, + modifier = Modifier.baselineHeight(24.dp), + style = style + ) } } @@ -270,7 +258,7 @@ fun ProfileFab(extended: Boolean, userIsMe: Boolean, modifier: Modifier = Modifi @Composable fun ConvPreview480MeDefault() { JetchatTheme { - ProfileScreen(colleagueProfile) + ProfileScreen(meProfile) } } diff --git a/Jetchat/buildSrc/src/main/java/com/example/compose/jetchat/buildsrc/dependencies.kt b/Jetchat/buildSrc/src/main/java/com/example/compose/jetchat/buildsrc/dependencies.kt index 6f71928609..2ee69d97f4 100644 --- a/Jetchat/buildSrc/src/main/java/com/example/compose/jetchat/buildsrc/dependencies.kt +++ b/Jetchat/buildSrc/src/main/java/com/example/compose/jetchat/buildsrc/dependencies.kt @@ -21,7 +21,7 @@ object Versions { } object Libs { - const val androidGradlePlugin = "com.android.tools.build:gradle:4.2.0-alpha15" + const val androidGradlePlugin = "com.android.tools.build:gradle:4.2.0-alpha16" const val jdkDesugar = "com.android.tools:desugar_jdk_libs:1.0.9" const val junit = "junit:junit:4.13" @@ -48,7 +48,7 @@ object Libs { object Compose { const val snapshot = "" - const val version = "1.0.0-alpha06" + const val version = "1.0.0-alpha07" const val foundation = "androidx.compose.foundation:foundation:$version" const val layout = "androidx.compose.foundation:foundation-layout:$version" diff --git a/Jetsnack/app/build.gradle b/Jetsnack/app/build.gradle index 7dfdd70fb7..6e4f500c12 100644 --- a/Jetsnack/app/build.gradle +++ b/Jetsnack/app/build.gradle @@ -80,6 +80,8 @@ dependencies { implementation Libs.Kotlin.stdlib + implementation Libs.Coroutines.core + implementation Libs.AndroidX.coreKtx implementation Libs.AndroidX.Compose.runtime diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/model/Snack.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/model/Snack.kt index 46baaa6fe1..4fc8a6d9e6 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/model/Snack.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/model/Snack.kt @@ -23,6 +23,7 @@ data class Snack( val id: Long, val name: String, val imageUrl: String, + val price: Long, val tagline: String = "", val tags: Set = emptySet() ) @@ -36,162 +37,190 @@ val snacks = listOf( id = 1L, name = "Cupcake", tagline = "A tag line", - imageUrl = "https://source.unsplash.com/pGM4sjt_BdQ" + imageUrl = "https://source.unsplash.com/pGM4sjt_BdQ", + price = 299 ), Snack( id = 2L, name = "Donut", tagline = "A tag line", - imageUrl = "https://source.unsplash.com/Yc5sL-ejk6U" + imageUrl = "https://source.unsplash.com/Yc5sL-ejk6U", + price = 299 ), Snack( id = 3L, name = "Eclair", tagline = "A tag line", - imageUrl = "https://source.unsplash.com/-LojFX9NfPY" + imageUrl = "https://source.unsplash.com/-LojFX9NfPY", + price = 299 ), Snack( id = 4L, name = "Froyo", tagline = "A tag line", - imageUrl = "https://source.unsplash.com/3U2V5WqK1PQ" + imageUrl = "https://source.unsplash.com/3U2V5WqK1PQ", + price = 299 ), Snack( id = 5L, name = "Gingerbread", tagline = "A tag line", - imageUrl = "https://source.unsplash.com/Y4YR9OjdIMk" + imageUrl = "https://source.unsplash.com/Y4YR9OjdIMk", + price = 499 ), Snack( id = 6L, name = "Honeycomb", tagline = "A tag line", - imageUrl = "https://source.unsplash.com/bELvIg_KZGU" + imageUrl = "https://source.unsplash.com/bELvIg_KZGU", + price = 299 ), Snack( id = 7L, name = "Ice Cream Sandwich", tagline = "A tag line", - imageUrl = "https://source.unsplash.com/AqorcpZIKnU" + imageUrl = "https://source.unsplash.com/AqorcpZIKnU", + price = 1299 ), Snack( id = 8L, name = "Jellybean", tagline = "A tag line", - imageUrl = "https://source.unsplash.com/0u_vbeOkMpk" + imageUrl = "https://source.unsplash.com/0u_vbeOkMpk", + price = 299 ), Snack( id = 9L, name = "KitKat", tagline = "A tag line", - imageUrl = "https://source.unsplash.com/yb16pT5F_jE" + imageUrl = "https://source.unsplash.com/yb16pT5F_jE", + price = 549 ), Snack( id = 10L, name = "Lollipop", tagline = "A tag line", - imageUrl = "https://source.unsplash.com/AHF_ZktTL6Q" + imageUrl = "https://source.unsplash.com/AHF_ZktTL6Q", + price = 299 ), Snack( id = 11L, name = "Marshmallow", tagline = "A tag line", - imageUrl = "https://source.unsplash.com/rqFm0IgMVYY" + imageUrl = "https://source.unsplash.com/rqFm0IgMVYY", + price = 299 ), Snack( id = 12L, name = "Nougat", tagline = "A tag line", - imageUrl = "https://source.unsplash.com/qRE_OpbVPR8" + imageUrl = "https://source.unsplash.com/qRE_OpbVPR8", + price = 299 ), Snack( id = 13L, name = "Oreo", tagline = "A tag line", - imageUrl = "https://source.unsplash.com/33fWPnyN6tU" + imageUrl = "https://source.unsplash.com/33fWPnyN6tU", + price = 299 ), Snack( id = 14L, name = "Pie", tagline = "A tag line", - imageUrl = "https://source.unsplash.com/aX_ljOOyWJY" + imageUrl = "https://source.unsplash.com/aX_ljOOyWJY", + price = 299 ), Snack( id = 15L, name = "Chips", - imageUrl = "https://source.unsplash.com/UsSdMZ78Q3E" + imageUrl = "https://source.unsplash.com/UsSdMZ78Q3E", + price = 299 ), Snack( id = 16L, name = "Pretzels", - imageUrl = "https://source.unsplash.com/7meCnGCJ5Ms" + imageUrl = "https://source.unsplash.com/7meCnGCJ5Ms", + price = 299 ), Snack( id = 17L, name = "Smoothies", - imageUrl = "https://source.unsplash.com/m741tj4Cz7M" + imageUrl = "https://source.unsplash.com/m741tj4Cz7M", + price = 299 ), Snack( id = 18L, name = "Popcorn", - imageUrl = "https://source.unsplash.com/iuwMdNq0-s4" + imageUrl = "https://source.unsplash.com/iuwMdNq0-s4", + price = 299 ), Snack( id = 19L, name = "Almonds", - imageUrl = "https://source.unsplash.com/qgWWQU1SzqM" + imageUrl = "https://source.unsplash.com/qgWWQU1SzqM", + price = 299 ), Snack( id = 20L, name = "Cheese", - imageUrl = "https://source.unsplash.com/9MzCd76xLGk" + imageUrl = "https://source.unsplash.com/9MzCd76xLGk", + price = 299 ), Snack( id = 21L, name = "Apples", tagline = "A tag line", - imageUrl = "https://source.unsplash.com/1d9xXWMtQzQ" + imageUrl = "https://source.unsplash.com/1d9xXWMtQzQ", + price = 299 ), Snack( id = 22L, name = "Apple sauce", tagline = "A tag line", - imageUrl = "https://source.unsplash.com/wZxpOw84QTU" + imageUrl = "https://source.unsplash.com/wZxpOw84QTU", + price = 299 ), Snack( id = 23L, name = "Apple chips", tagline = "A tag line", - imageUrl = "https://source.unsplash.com/okzeRxm_GPo" + imageUrl = "https://source.unsplash.com/okzeRxm_GPo", + price = 299 ), Snack( id = 24L, name = "Apple juice", tagline = "A tag line", - imageUrl = "https://source.unsplash.com/l7imGdupuhU" + imageUrl = "https://source.unsplash.com/l7imGdupuhU", + price = 299 ), Snack( id = 25L, name = "Apple pie", tagline = "A tag line", - imageUrl = "https://source.unsplash.com/bkXzABDt08Q" + imageUrl = "https://source.unsplash.com/bkXzABDt08Q", + price = 299 ), Snack( id = 26L, name = "Grapes", tagline = "A tag line", - imageUrl = "https://source.unsplash.com/y2MeW00BdBo" + imageUrl = "https://source.unsplash.com/y2MeW00BdBo", + price = 299 ), Snack( id = 27L, name = "Kiwi", tagline = "A tag line", - imageUrl = "https://source.unsplash.com/1oMGgHn-M8k" + imageUrl = "https://source.unsplash.com/1oMGgHn-M8k", + price = 299 ), Snack( id = 28L, name = "Mango", tagline = "A tag line", - imageUrl = "https://source.unsplash.com/TIGDsyy0TK4" + imageUrl = "https://source.unsplash.com/TIGDsyy0TK4", + price = 299 ) ) diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/model/SnackCollection.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/model/SnackCollection.kt index f0fb1817e5..77da39594b 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/model/SnackCollection.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/model/SnackCollection.kt @@ -95,4 +95,14 @@ private val related = listOf( popular ) -private val cart = snacks.subList(4, 7) +private val cart = listOf( + OrderLine(snacks[4], 2), + OrderLine(snacks[6], 3), + OrderLine(snacks[8], 1) +) + +@Immutable +data class OrderLine( + val snack: Snack, + val count: Int +) diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Button.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Button.kt index 62b758036f..3d4ec06e55 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Button.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Button.kt @@ -17,7 +17,6 @@ package com.example.jetsnack.ui.components import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.ProvideTextStyle import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues @@ -29,6 +28,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.ButtonConstants import androidx.compose.material.MaterialTheme +import androidx.compose.material.ProvideTextStyle import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Filters.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Filters.kt index 1dd037692b..f157d6fb89 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Filters.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Filters.kt @@ -18,7 +18,6 @@ package com.example.jetsnack.ui.components import androidx.compose.animation.animate import androidx.compose.foundation.ScrollableRow -import androidx.compose.foundation.Text import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.padding @@ -30,6 +29,7 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.FilterList import androidx.compose.runtime.Composable diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Gradient.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Gradient.kt index d2e022efb5..5da2984f73 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Gradient.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Gradient.kt @@ -34,7 +34,7 @@ import androidx.compose.ui.graphics.HorizontalGradient import androidx.compose.ui.graphics.LinearGradient import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.TileMode -import androidx.compose.ui.onSizeChanged +import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp @@ -48,7 +48,7 @@ fun Modifier.horizontalGradientBackground( endX = size.width, colors = colors ) - onDraw { + onDrawBehind { drawRect(brush = gradient) } } diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Grid.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Grid.kt index b451c14c13..5e9acfa6c1 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Grid.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Grid.kt @@ -17,8 +17,8 @@ package com.example.jetsnack.ui.components import androidx.compose.runtime.Composable -import androidx.compose.ui.Layout import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.Layout /** * A simple grid which lays elements out vertically in evenly sized [columns]. diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/QuantitySelector.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/QuantitySelector.kt index 7a89a74919..97cb977a58 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/QuantitySelector.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/QuantitySelector.kt @@ -16,17 +16,19 @@ package com.example.jetsnack.ui.components -import androidx.compose.foundation.Text +import androidx.compose.animation.Crossfade import androidx.compose.foundation.layout.ChainStyle import androidx.compose.foundation.layout.ConstraintLayout import androidx.compose.foundation.layout.preferredWidthIn -import androidx.compose.material.AmbientEmphasisLevels +import androidx.compose.material.AmbientContentAlpha +import androidx.compose.material.ContentAlpha import androidx.compose.material.MaterialTheme -import androidx.compose.material.ProvideEmphasis +import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.AddCircleOutline import androidx.compose.material.icons.outlined.RemoveCircleOutline import androidx.compose.runtime.Composable +import androidx.compose.runtime.Providers import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign @@ -46,7 +48,7 @@ fun QuantitySelector( ConstraintLayout(modifier = modifier) { val (qty, minus, quantity, plus) = createRefs() createHorizontalChain(qty, minus, quantity, plus, chainStyle = ChainStyle.Packed) - ProvideEmphasis(emphasis = AmbientEmphasisLevels.current.medium) { + Providers(AmbientContentAlpha provides ContentAlpha.medium) { Text( text = stringResource(R.string.quantity), style = MaterialTheme.typography.subtitle1, @@ -65,16 +67,18 @@ fun QuantitySelector( linkTo(top = parent.top, bottom = parent.bottom) } ) - ProvideEmphasis(emphasis = AmbientEmphasisLevels.current.high) { + Crossfade( + current = count, + modifier = Modifier + .constrainAs(quantity) { baseline.linkTo(qty.baseline) } + ) { Text( - text = "$count", + text = "$it", style = MaterialTheme.typography.subtitle2, fontSize = 18.sp, color = JetsnackTheme.colors.textPrimary, textAlign = TextAlign.Center, - modifier = Modifier.preferredWidthIn(min = 24.dp).constrainAs(quantity) { - baseline.linkTo(qty.baseline) - } + modifier = Modifier.preferredWidthIn(min = 24.dp) ) } JetsnackGradientTintedIconButton( diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Snacks.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Snacks.kt index 07debb35f1..75bfbb28a4 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Snacks.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Snacks.kt @@ -17,7 +17,6 @@ package com.example.jetsnack.ui.components import androidx.compose.foundation.ScrollableRow -import androidx.compose.foundation.Text import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -38,6 +37,7 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ArrowForward import androidx.compose.runtime.Composable diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Surface.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Surface.kt index bfc6172047..8f1af71be9 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Surface.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/components/Surface.kt @@ -16,11 +16,11 @@ package com.example.jetsnack.ui.components -import androidx.compose.foundation.AmbientContentColor import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Box +import androidx.compose.material.AmbientContentColor import androidx.compose.runtime.Composable import androidx.compose.runtime.Providers import androidx.compose.ui.Modifier diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/DestinationBar.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/DestinationBar.kt index eeafa68b34..e822d6a9c5 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/DestinationBar.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/DestinationBar.kt @@ -16,11 +16,11 @@ package com.example.jetsnack.ui.home -import androidx.compose.foundation.Text import androidx.compose.foundation.layout.Column import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text import androidx.compose.material.TopAppBar import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ExpandMore diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/Feed.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/Feed.kt index 2b83ea5dd1..1e8328323a 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/Feed.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/Feed.kt @@ -19,7 +19,6 @@ package com.example.jetsnack.ui.home import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.lazy.ExperimentalLazyDsl import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.runtime.Composable import androidx.compose.runtime.remember @@ -66,7 +65,6 @@ private fun Feed( } } -@OptIn(ExperimentalLazyDsl::class) @Composable private fun SnackCollectionList( snackCollections: List, diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/Home.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/Home.kt index 796a96f877..3b97a55e4d 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/Home.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/Home.kt @@ -24,7 +24,6 @@ import androidx.compose.animation.animate import androidx.compose.animation.animatedFloat import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.SpringSpec -import androidx.compose.foundation.Text import androidx.compose.foundation.border import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope @@ -36,6 +35,7 @@ import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.AccountCircle import androidx.compose.material.icons.outlined.Home @@ -46,16 +46,17 @@ import androidx.compose.runtime.onCommit import androidx.compose.runtime.remember import androidx.compose.runtime.savedinstancestate.savedInstanceState import androidx.compose.ui.Alignment -import androidx.compose.ui.Layout -import androidx.compose.ui.MeasureScope import androidx.compose.ui.Modifier -import androidx.compose.ui.Placeable import androidx.compose.ui.TransformOrigin import androidx.compose.ui.draw.clip import androidx.compose.ui.drawLayer import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.vector.VectorAsset +import androidx.compose.ui.layout.Layout +import androidx.compose.ui.layout.MeasureResult +import androidx.compose.ui.layout.MeasureScope +import androidx.compose.ui.layout.Placeable import androidx.compose.ui.layout.id import androidx.compose.ui.layout.layoutId import androidx.compose.ui.platform.AnimationClockAmbient @@ -69,6 +70,7 @@ import androidx.ui.tooling.preview.Preview import com.example.jetsnack.R import com.example.jetsnack.ui.components.JetsnackScaffold import com.example.jetsnack.ui.components.JetsnackSurface +import com.example.jetsnack.ui.home.cart.Cart import com.example.jetsnack.ui.home.search.Search import com.example.jetsnack.ui.theme.JetsnackTheme import com.example.jetsnack.ui.utils.navigationBarsPadding @@ -315,7 +317,7 @@ private fun MeasureScope.placeTextAndIcon( width: Int, height: Int, @FloatRange(from = 0.0, to = 1.0) animationProgress: Float -): MeasureScope.MeasureResult { +): MeasureResult { val iconY = (height - iconPlaceable.height) / 2 val textY = (height - textPlaceable.height) / 2 diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/Profile.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/Profile.kt index 1ebd8d3e43..63aba7ab96 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/Profile.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/Profile.kt @@ -16,9 +16,9 @@ package com.example.jetsnack.ui.home -import androidx.compose.foundation.Text import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/Cart.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/cart/Cart.kt similarity index 76% rename from Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/Cart.kt rename to Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/cart/Cart.kt index 3827cc7f8a..a5bf484eb8 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/Cart.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/cart/Cart.kt @@ -14,9 +14,8 @@ * limitations under the License. */ -package com.example.jetsnack.ui.home +package com.example.jetsnack.ui.home.cart -import androidx.compose.foundation.Text import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box @@ -34,26 +33,29 @@ import androidx.compose.foundation.layout.preferredHeightIn import androidx.compose.foundation.layout.preferredSize import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentWidth -import androidx.compose.foundation.lazy.ExperimentalLazyDsl import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.text.LastBaseline import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.layout.LastBaseline +import androidx.compose.ui.platform.ContextAmbient import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.viewModel import androidx.ui.tooling.preview.Preview import com.example.jetsnack.R -import com.example.jetsnack.model.Snack +import com.example.jetsnack.model.OrderLine import com.example.jetsnack.model.SnackCollection import com.example.jetsnack.model.SnackRepo import com.example.jetsnack.ui.components.JetsnackButton @@ -62,8 +64,10 @@ import com.example.jetsnack.ui.components.JetsnackSurface import com.example.jetsnack.ui.components.QuantitySelector import com.example.jetsnack.ui.components.SnackCollection import com.example.jetsnack.ui.components.SnackImage +import com.example.jetsnack.ui.home.DestinationBar import com.example.jetsnack.ui.theme.AlphaNearOpaque import com.example.jetsnack.ui.theme.JetsnackTheme +import com.example.jetsnack.ui.utils.formatPrice import com.example.jetsnack.ui.utils.statusBarsHeight @Composable @@ -71,14 +75,26 @@ fun Cart( onSnackClick: (Long) -> Unit, modifier: Modifier = Modifier ) { - val cartSnacks = remember { SnackRepo.getCart() } + val viewModel: CartViewModel = viewModel() + val orderLines by viewModel.orderLines.collectAsState() val inspiredByCart = remember { SnackRepo.getInspiredByCart() } - Cart(cartSnacks, inspiredByCart, onSnackClick, modifier) + Cart( + orderLines = orderLines, + removeSnack = viewModel::removeSnack, + increaseItemCount = viewModel::increaseSnackCount, + decreaseItemCount = viewModel::decreaseSnackCount, + inspiredByCart = inspiredByCart, + onSnackClick = onSnackClick, + modifier = modifier + ) } @Composable fun Cart( - cartSnacks: List, + orderLines: List, + removeSnack: (Long) -> Unit, + increaseItemCount: (Long) -> Unit, + decreaseItemCount: (Long) -> Unit, inspiredByCart: SnackCollection, onSnackClick: (Long) -> Unit, modifier: Modifier = Modifier @@ -86,7 +102,10 @@ fun Cart( JetsnackSurface(modifier = modifier.fillMaxSize()) { Box { CartContent( - cartSnacks = cartSnacks, + orderLines = orderLines, + removeSnack = removeSnack, + increaseItemCount = increaseItemCount, + decreaseItemCount = decreaseItemCount, inspiredByCart = inspiredByCart, onSnackClick = onSnackClick, modifier = Modifier.align(Alignment.TopCenter) @@ -97,19 +116,28 @@ fun Cart( } } -@OptIn(ExperimentalLazyDsl::class) @Composable private fun CartContent( - cartSnacks: List, + orderLines: List, + removeSnack: (Long) -> Unit, + increaseItemCount: (Long) -> Unit, + decreaseItemCount: (Long) -> Unit, inspiredByCart: SnackCollection, onSnackClick: (Long) -> Unit, modifier: Modifier = Modifier ) { + val resources = ContextAmbient.current.resources + val snackCountFormattedString = remember(orderLines.size, resources) { + resources.getQuantityString( + R.plurals.cart_order_count, + orderLines.size, orderLines.size + ) + } LazyColumn(modifier) { item { Spacer(Modifier.statusBarsHeight(additional = 56.dp)) Text( - text = stringResource(R.string.cart_order_header, cartSnacks.size), + text = stringResource(R.string.cart_order_header, snackCountFormattedString), style = MaterialTheme.typography.h6, color = JetsnackTheme.colors.brand, maxLines = 1, @@ -120,11 +148,20 @@ private fun CartContent( .wrapContentHeight() ) } - items(cartSnacks) { cartSnack -> - CartItem(cartSnack, onSnackClick) + items(orderLines) { orderLine -> + CartItem( + orderLine = orderLine, + removeSnack = removeSnack, + increaseItemCount = increaseItemCount, + decreaseItemCount = decreaseItemCount, + onSnackClick = onSnackClick + ) } item { - SummaryItem() + SummaryItem( + subtotal = orderLines.map { it.snack.price * it.count }.sum(), + shippingCosts = 369 + ) } item { SnackCollection( @@ -139,11 +176,14 @@ private fun CartContent( @Composable fun CartItem( - snack: Snack, + orderLine: OrderLine, + removeSnack: (Long) -> Unit, + increaseItemCount: (Long) -> Unit, + decreaseItemCount: (Long) -> Unit, onSnackClick: (Long) -> Unit, modifier: Modifier = Modifier ) { - val (count, updateCount) = remember { mutableStateOf(1) } + val snack = orderLine.snack ConstraintLayout( modifier = modifier .fillMaxWidth() @@ -177,13 +217,13 @@ fun CartItem( } ) IconButton( - onClick = { /* todo */ }, + onClick = { removeSnack(snack.id) }, modifier = Modifier.constrainAs(remove) { top.linkTo(parent.top) end.linkTo(parent.end) }.padding(top = 12.dp) ) { - Icon(asset = Icons.Filled.Close, tint = JetsnackTheme.colors.brand) + Icon(asset = Icons.Filled.Close, tint = JetsnackTheme.colors.iconSecondary) } Text( text = snack.tagline, @@ -207,7 +247,7 @@ fun CartItem( } ) Text( - text = "$12.99", + text = formatPrice(snack.price), style = MaterialTheme.typography.subtitle1, color = JetsnackTheme.colors.textPrimary, modifier = Modifier.constrainAs(price) { @@ -221,9 +261,9 @@ fun CartItem( } ) QuantitySelector( - count = count, - decreaseItemCount = { if (count > 0) updateCount(count - 1) }, - increaseItemCount = { updateCount(count + 1) }, + count = orderLine.count, + decreaseItemCount = { decreaseItemCount(snack.id) }, + increaseItemCount = { increaseItemCount(snack.id) }, modifier = Modifier.constrainAs(quantity) { baseline.linkTo(price.baseline) end.linkTo(parent.end) @@ -238,9 +278,12 @@ fun CartItem( } } -// TODO: Hoist state instead of using hard-coded total price @Composable -fun SummaryItem(modifier: Modifier = Modifier) { +fun SummaryItem( + subtotal: Long, + shippingCosts: Long, + modifier: Modifier = Modifier +) { Column(modifier) { Text( text = stringResource(R.string.cart_summary_header), @@ -262,7 +305,7 @@ fun SummaryItem(modifier: Modifier = Modifier) { .alignBy(LastBaseline) ) Text( - text = "\$27.47", + text = formatPrice(subtotal), style = MaterialTheme.typography.body1, modifier = Modifier.alignBy(LastBaseline) ) @@ -276,7 +319,7 @@ fun SummaryItem(modifier: Modifier = Modifier) { .alignBy(LastBaseline) ) Text( - text = "\$3.69", + text = formatPrice(shippingCosts), style = MaterialTheme.typography.body1, modifier = Modifier.alignBy(LastBaseline) ) @@ -294,7 +337,7 @@ fun SummaryItem(modifier: Modifier = Modifier) { .alignBy(LastBaseline) ) Text( - text = "\$31.16", + text = formatPrice(subtotal + shippingCosts), style = MaterialTheme.typography.subtitle1, modifier = Modifier.alignBy(LastBaseline) ) @@ -332,7 +375,14 @@ private fun CheckoutBar(modifier: Modifier = Modifier) { @Composable fun CartPreview() { JetsnackTheme { - Cart(onSnackClick = { }) + Cart( + orderLines = SnackRepo.getCart(), + removeSnack = {}, + increaseItemCount = {}, + decreaseItemCount = {}, + inspiredByCart = SnackRepo.getInspiredByCart(), + onSnackClick = {} + ) } } @@ -340,6 +390,13 @@ fun CartPreview() { @Composable fun CartDarkPreview() { JetsnackTheme(darkTheme = true) { - Cart(onSnackClick = { }) + Cart( + orderLines = SnackRepo.getCart(), + removeSnack = {}, + increaseItemCount = {}, + decreaseItemCount = {}, + inspiredByCart = SnackRepo.getInspiredByCart(), + onSnackClick = { } + ) } } diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/cart/CartViewModel.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/cart/CartViewModel.kt new file mode 100644 index 0000000000..2a35de730d --- /dev/null +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/cart/CartViewModel.kt @@ -0,0 +1,63 @@ +/* + * Copyright 2020 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.example.jetsnack.ui.home.cart + +import androidx.lifecycle.ViewModel +import com.example.jetsnack.model.OrderLine +import com.example.jetsnack.model.SnackRepo +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +/** + * Holds the contents of the cart and allows changes to it. + * + * TODO: Move data to Repository so it can be displayed and changed consistently throughout the app. + */ +class CartViewModel : ViewModel() { + private val _orderLines: MutableStateFlow> = MutableStateFlow(SnackRepo.getCart()) + val orderLines: StateFlow> get() = _orderLines + + fun removeSnack(snackId: Long) { + _orderLines.value = _orderLines.value.filter { it.snack.id != snackId } + } + + fun increaseSnackCount(snackId: Long) { + val currentCount = _orderLines.value.first { it.snack.id == snackId }.count + updateSnackCount(snackId, currentCount + 1) + } + + fun decreaseSnackCount(snackId: Long) { + val currentCount = _orderLines.value.first { it.snack.id == snackId }.count + if (currentCount == 1) { + // remove snack from cart + removeSnack(snackId) + } else { + // update quantity in cart + updateSnackCount(snackId, currentCount - 1) + } + } + + private fun updateSnackCount(snackId: Long, count: Int) { + _orderLines.value = _orderLines.value.map { + if (it.snack.id == snackId) { + it.copy(count = count) + } else { + it + } + } + } +} diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/search/Categories.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/search/Categories.kt index b44ee09697..0adacd45c2 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/search/Categories.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/search/Categories.kt @@ -16,7 +16,6 @@ package com.example.jetsnack.ui.home.search -import androidx.compose.foundation.Text import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -29,12 +28,13 @@ import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.lazy.LazyColumnForIndexed import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.Layout import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawShadow import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.Layout import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.dp import androidx.ui.tooling.preview.Preview diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/search/Results.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/search/Results.kt index e0d650d780..883af27957 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/search/Results.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/search/Results.kt @@ -17,7 +17,6 @@ package com.example.jetsnack.ui.home.search import androidx.compose.foundation.Image -import androidx.compose.foundation.Text import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.ChainStyle import androidx.compose.foundation.layout.Column @@ -34,6 +33,7 @@ import androidx.compose.foundation.lazy.LazyColumnForIndexed import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Add import androidx.compose.runtime.Composable @@ -54,6 +54,7 @@ import com.example.jetsnack.ui.components.JetsnackDivider import com.example.jetsnack.ui.components.JetsnackSurface import com.example.jetsnack.ui.components.SnackImage import com.example.jetsnack.ui.theme.JetsnackTheme +import com.example.jetsnack.ui.utils.formatPrice @Composable fun SearchResults( @@ -148,7 +149,7 @@ private fun SearchResult( } ) Text( - text = "$12.99", + text = formatPrice(snack.price), style = MaterialTheme.typography.subtitle1, color = JetsnackTheme.colors.textPrimary, modifier = Modifier.constrainAs(price) { diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/search/Search.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/search/Search.kt index 0b1f883612..57d2be139b 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/search/Search.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/search/Search.kt @@ -16,10 +16,7 @@ package com.example.jetsnack.ui.home.search -import androidx.compose.foundation.BaseTextField import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.Text -import androidx.compose.foundation.contentColor import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -32,15 +29,17 @@ import androidx.compose.foundation.layout.preferredSize import androidx.compose.foundation.layout.preferredWidth import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.text.BasicTextField import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ArrowBack import androidx.compose.material.icons.outlined.Search import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedTask +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -52,7 +51,6 @@ import androidx.compose.ui.focus.ExperimentalFocus import androidx.compose.ui.focus.isFocused import androidx.compose.ui.focusObserver import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp import androidx.ui.tooling.preview.Preview @@ -87,7 +85,7 @@ fun Search( ) JetsnackDivider() - LaunchedTask(state.query.text) { + LaunchedEffect(state.query.text) { state.searching = true state.searchResults = SearchRepo.search(state.query.text) state.searching = false @@ -200,12 +198,9 @@ private fun SearchBar( ) } } - BaseTextField( + BasicTextField( value = query, onValueChange = onQueryChange, - imeAction = ImeAction.Search, - onImeActionPerformed = { /* todo */ }, - cursorColor = JetsnackTheme.colors.textPrimary, modifier = Modifier .weight(1f) .focusObserver { diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/search/Suggestions.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/search/Suggestions.kt index 1caa70fbf8..7c06c50157 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/search/Suggestions.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/search/Suggestions.kt @@ -16,7 +16,6 @@ package com.example.jetsnack.ui.home.search -import androidx.compose.foundation.Text import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.padding @@ -24,9 +23,9 @@ import androidx.compose.foundation.layout.preferredHeight import androidx.compose.foundation.layout.preferredHeightIn import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentSize -import androidx.compose.foundation.lazy.ExperimentalLazyDsl import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -37,7 +36,6 @@ import com.example.jetsnack.model.SearchSuggestionGroup import com.example.jetsnack.ui.components.JetsnackSurface import com.example.jetsnack.ui.theme.JetsnackTheme -@OptIn(ExperimentalLazyDsl::class) @Composable fun SearchSuggestions( suggestions: List, diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/snackdetail/SnackDetail.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/snackdetail/SnackDetail.kt index b9fe76cb5f..da0bf5ac6d 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/snackdetail/SnackDetail.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/snackdetail/SnackDetail.kt @@ -18,7 +18,6 @@ package com.example.jetsnack.ui.snackdetail import androidx.compose.foundation.ScrollState import androidx.compose.foundation.ScrollableColumn -import androidx.compose.foundation.Text import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -37,6 +36,7 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ArrowBack import androidx.compose.runtime.Composable @@ -44,9 +44,9 @@ import androidx.compose.runtime.key import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment -import androidx.compose.ui.Layout import androidx.compose.ui.Modifier import androidx.compose.ui.drawLayer +import androidx.compose.ui.layout.Layout import androidx.compose.ui.platform.DensityAmbient import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Constraints @@ -68,6 +68,7 @@ import com.example.jetsnack.ui.components.SnackImage import com.example.jetsnack.ui.components.horizontalGradientBackground import com.example.jetsnack.ui.theme.JetsnackTheme import com.example.jetsnack.ui.theme.Neutral8 +import com.example.jetsnack.ui.utils.formatPrice import com.example.jetsnack.ui.utils.navigationBarsPadding import com.example.jetsnack.ui.utils.statusBarsPadding import kotlin.math.max @@ -236,7 +237,7 @@ private fun Title(snack: Snack, scroll: Float) { ) Spacer(Modifier.preferredHeight(4.dp)) Text( - text = "$12.99", + text = formatPrice(snack.price), style = MaterialTheme.typography.h6, color = JetsnackTheme.colors.textPrimary, modifier = HzPadding diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/Currency.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/Currency.kt new file mode 100644 index 0000000000..10c243434d --- /dev/null +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/Currency.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2020 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.example.jetsnack.ui.utils + +import java.math.BigDecimal +import java.text.NumberFormat + +fun formatPrice(price: Long): String { + return NumberFormat.getCurrencyInstance().format( + BigDecimal(price).movePointLeft(2) + ) +} diff --git a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/Insets.kt b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/Insets.kt index 72397b34a9..7bda84887c 100644 --- a/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/Insets.kt +++ b/Jetsnack/app/src/main/java/com/example/jetsnack/ui/utils/Insets.kt @@ -30,13 +30,14 @@ import androidx.compose.runtime.onCommit import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.runtime.staticAmbientOf -import androidx.compose.ui.LayoutModifier -import androidx.compose.ui.Measurable -import androidx.compose.ui.MeasureScope import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.layout.IntrinsicMeasurable import androidx.compose.ui.layout.IntrinsicMeasureScope +import androidx.compose.ui.layout.LayoutModifier +import androidx.compose.ui.layout.Measurable +import androidx.compose.ui.layout.MeasureResult +import androidx.compose.ui.layout.MeasureScope import androidx.compose.ui.platform.DensityAmbient import androidx.compose.ui.platform.LayoutDirectionAmbient import androidx.compose.ui.platform.ViewAmbient @@ -483,7 +484,7 @@ private data class InsetsPaddingModifier( override fun MeasureScope.measure( measurable: Measurable, constraints: Constraints - ): MeasureScope.MeasureResult { + ): MeasureResult { val left = if (applyLeft) insets.left else 0 val top = if (applyTop) insets.top else 0 val right = if (applyRight) insets.right else 0 @@ -541,7 +542,7 @@ private data class InsetsSizeModifier( override fun MeasureScope.measure( measurable: Measurable, constraints: Constraints - ): MeasureScope.MeasureResult { + ): MeasureResult { val wrappedConstraints = targetConstraints.let { targetConstraints -> val resolvedMinWidth = if (widthSide != null) { targetConstraints.minWidth diff --git a/Jetsnack/app/src/main/res/values/strings.xml b/Jetsnack/app/src/main/res/values/strings.xml index 0186a5b24a..0350109f60 100644 --- a/Jetsnack/app/src/main/res/values/strings.xml +++ b/Jetsnack/app/src/main/res/values/strings.xml @@ -36,7 +36,11 @@ ADD TO CART - Order (%1d items) + Order (%1s) + + %1d item + %1d items + Summary Subtotal Shipping & Handling diff --git a/Jetsnack/build.gradle b/Jetsnack/build.gradle index da3492a695..4cb422b32e 100644 --- a/Jetsnack/build.gradle +++ b/Jetsnack/build.gradle @@ -23,9 +23,6 @@ buildscript { jcenter() } dependencies { - // Downgrade R8 due to b/169095082 - // TODO remove this - classpath 'com.android.tools:r8:2.1.66' classpath Libs.androidGradlePlugin classpath Libs.Kotlin.gradlePlugin } @@ -56,6 +53,8 @@ subprojects { freeCompilerArgs += '-Xallow-jvm-ir-dependencies' // Opt-in to experimental compose APIs freeCompilerArgs += '-Xopt-in=kotlin.RequiresOptIn' + // Enable experimental coroutines APIs, including collectAsState() + freeCompilerArgs += '-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi' } } apply plugin: 'com.diffplug.spotless' diff --git a/Jetsnack/buildSrc/src/main/java/com/example/jetsnack/buildsrc/Dependencies.kt b/Jetsnack/buildSrc/src/main/java/com/example/jetsnack/buildsrc/Dependencies.kt index fd6824b3ea..ac507e6379 100644 --- a/Jetsnack/buildSrc/src/main/java/com/example/jetsnack/buildsrc/Dependencies.kt +++ b/Jetsnack/buildSrc/src/main/java/com/example/jetsnack/buildsrc/Dependencies.kt @@ -21,11 +21,11 @@ object Versions { } object Libs { - const val androidGradlePlugin = "com.android.tools.build:gradle:4.2.0-alpha15" + const val androidGradlePlugin = "com.android.tools.build:gradle:4.2.0-alpha16" const val junit = "junit:junit:4.13" object Accompanist { - private const val version = "0.3.2" + private const val version = "0.3.3.1" const val coil = "dev.chrisbanes.accompanist:accompanist-coil:$version" } @@ -37,7 +37,7 @@ object Libs { } object Coroutines { - private const val version = "1.3.9" + private const val version = "1.4.0" const val core = "org.jetbrains.kotlinx:kotlinx-coroutines-core:$version" const val android = "org.jetbrains.kotlinx:kotlinx-coroutines-android:$version" const val test = "org.jetbrains.kotlinx:kotlinx-coroutines-test:$version" @@ -48,12 +48,12 @@ object Libs { object Compose { const val snapshot = "" - const val version = "1.0.0-alpha06" + const val version = "1.0.0-alpha07" - const val runtime = "androidx.compose.runtime:runtime:$version" const val foundation = "androidx.compose.foundation:foundation:${version}" const val layout = "androidx.compose.foundation:foundation-layout:${version}" const val ui = "androidx.compose.ui:ui:${version}" + const val runtime = "androidx.compose.runtime:runtime:${version}" const val material = "androidx.compose.material:material:${version}" const val animation = "androidx.compose.animation:animation:${version}" const val tooling = "androidx.ui:ui-tooling:${version}" diff --git a/Jetsurvey/app/build.gradle b/Jetsurvey/app/build.gradle index fb712e6938..c5a801e5b9 100644 --- a/Jetsurvey/app/build.gradle +++ b/Jetsurvey/app/build.gradle @@ -102,6 +102,8 @@ dependencies { implementation Libs.AndroidX.Compose.runtime implementation Libs.AndroidX.Compose.runtimeLivedata + implementation Libs.Accompanist.coil + androidTestImplementation Libs.junit androidTestImplementation Libs.AndroidX.Test.core androidTestImplementation Libs.AndroidX.Test.espressoCore diff --git a/Jetsurvey/app/src/main/AndroidManifest.xml b/Jetsurvey/app/src/main/AndroidManifest.xml index e509f04fbe..1688a9162e 100644 --- a/Jetsurvey/app/src/main/AndroidManifest.xml +++ b/Jetsurvey/app/src/main/AndroidManifest.xml @@ -17,6 +17,9 @@ + + if (action == ImeAction.Done) { softKeyboardController?.hideSoftwareKeyboard() @@ -170,7 +172,7 @@ fun Password( }, textStyle = MaterialTheme.typography.body2, label = { - ProvideEmphasis(emphasis = AmbientEmphasisLevels.current.medium) { + Providers(AmbientContentAlpha provides ContentAlpha.medium) { Text( text = label, style = MaterialTheme.typography.body2 @@ -194,7 +196,7 @@ fun Password( PasswordVisualTransformation() }, isErrorValue = passwordState.showErrors(), - imeAction = imeAction, + keyboardOptions = KeyboardOptions.Default.copy(imeAction = imeAction), onImeActionPerformed = { action, softKeyboardController -> if (action == ImeAction.Done) { softKeyboardController?.hideSoftwareKeyboard() @@ -231,7 +233,7 @@ fun OrSignInAsGuest( horizontalAlignment = Alignment.CenterHorizontally ) { Surface { - ProvideEmphasis(emphasis = AmbientEmphasisLevels.current.medium) { + Providers(AmbientContentAlpha provides ContentAlpha.medium) { Text( text = stringResource(id = R.string.or), style = MaterialTheme.typography.subtitle2 diff --git a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/signinsignup/SignUpScreen.kt b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/signinsignup/SignUpScreen.kt index 3b043fd158..a7094fdbff 100644 --- a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/signinsignup/SignUpScreen.kt +++ b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/signinsignup/SignUpScreen.kt @@ -16,17 +16,18 @@ package com.example.compose.jetsurvey.signinsignup -import androidx.compose.foundation.Text import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.preferredHeight -import androidx.compose.material.AmbientEmphasisLevels +import androidx.compose.material.AmbientContentAlpha import androidx.compose.material.Button +import androidx.compose.material.ContentAlpha import androidx.compose.material.MaterialTheme -import androidx.compose.material.ProvideEmphasis import androidx.compose.material.Scaffold +import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.Providers import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.focus.ExperimentalFocus @@ -103,7 +104,7 @@ fun SignUpContent( ) Spacer(modifier = Modifier.preferredHeight(16.dp)) - ProvideEmphasis(emphasis = AmbientEmphasisLevels.current.medium) { + Providers(AmbientContentAlpha provides ContentAlpha.medium) { Text( text = stringResource(id = R.string.terms_and_conditions), style = MaterialTheme.typography.caption @@ -117,9 +118,7 @@ fun SignUpContent( enabled = emailState.isValid && passwordState.isValid && confirmPasswordState.isValid ) { - Text( - text = stringResource(id = R.string.create_account) - ) + Text(text = stringResource(id = R.string.create_account)) } } } diff --git a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/signinsignup/WelcomeScreen.kt b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/signinsignup/WelcomeScreen.kt index b1518b236a..e92ad56683 100644 --- a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/signinsignup/WelcomeScreen.kt +++ b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/signinsignup/WelcomeScreen.kt @@ -18,7 +18,6 @@ package com.example.compose.jetsurvey.signinsignup import androidx.compose.animation.animate import androidx.compose.foundation.Image -import androidx.compose.foundation.Text import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -26,12 +25,14 @@ import androidx.compose.foundation.layout.offsetPx import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.preferredHeight import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.material.AmbientEmphasisLevels +import androidx.compose.material.AmbientContentAlpha import androidx.compose.material.Button +import androidx.compose.material.ContentAlpha import androidx.compose.material.MaterialTheme -import androidx.compose.material.ProvideEmphasis import androidx.compose.material.Surface +import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.Providers import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -39,8 +40,8 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.boundsInParent -import androidx.compose.ui.onGloballyPositioned -import androidx.compose.ui.onSizeChanged +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.DensityAmbient import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource @@ -113,14 +114,12 @@ private fun Branding(modifier: Modifier = Modifier) { modifier = modifier.wrapContentHeight(align = Alignment.CenterVertically) ) { Logo(modifier = Modifier.align(Alignment.CenterHorizontally).padding(horizontal = 76.dp)) - ProvideEmphasis(emphasis = AmbientEmphasisLevels.current.high) { - Text( - text = stringResource(id = R.string.app_tagline), - style = MaterialTheme.typography.subtitle1, - textAlign = TextAlign.Center, - modifier = Modifier.padding(top = 24.dp).fillMaxWidth() - ) - } + Text( + text = stringResource(id = R.string.app_tagline), + style = MaterialTheme.typography.subtitle1, + textAlign = TextAlign.Center, + modifier = Modifier.padding(top = 24.dp).fillMaxWidth() + ) } } @@ -148,7 +147,7 @@ private fun SignInCreateAccount( ) { val emailState = remember { EmailState() } Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) { - ProvideEmphasis(emphasis = AmbientEmphasisLevels.current.medium) { + Providers(AmbientContentAlpha provides ContentAlpha.medium) { Text( text = stringResource(id = R.string.sign_in_create_account), style = MaterialTheme.typography.subtitle2, diff --git a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/PhotoUriManager.kt b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/PhotoUriManager.kt new file mode 100644 index 0000000000..8ae71d8205 --- /dev/null +++ b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/PhotoUriManager.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2020 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.example.compose.jetsurvey.survey + +import android.content.ContentValues +import android.content.Context +import android.provider.MediaStore + +/** + * Manages the creation of photo Uris. The Uri is used to store the photos taken with camera. + */ +class PhotoUriManager(private val appContext: Context) { + + private val photoCollection by lazy { + MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) + } + + private val resolver by lazy { appContext.contentResolver } + + fun buildNewUri() = resolver.insert(photoCollection, buildPhotoDetails()) + + private fun buildPhotoDetails() = ContentValues().apply { + put(MediaStore.Images.Media.DISPLAY_NAME, generateFilename()) + put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg") + } + + /** + * Create a unique file name based on the time the photo is taken + */ + private fun generateFilename() = "selfie-${System.currentTimeMillis()}.jpg" +} diff --git a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/Survey.kt b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/Survey.kt index 7325df2b83..8605d1835e 100644 --- a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/Survey.kt +++ b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/Survey.kt @@ -16,7 +16,7 @@ package com.example.compose.jetsurvey.survey -import android.graphics.Bitmap +import android.net.Uri import androidx.annotation.StringRes data class SurveyResult( @@ -33,7 +33,8 @@ data class Survey( data class Question( val id: Int, @StringRes val questionText: Int, - val answer: PossibleAnswer + val answer: PossibleAnswer, + @StringRes val description: Int? = null ) /** @@ -43,7 +44,7 @@ enum class SurveyActionType { PICK_DATE, TAKE_PHOTO, SELECT_CONTACT } sealed class SurveyActionResult { data class Date(val date: String) : SurveyActionResult() - data class Photo(val bitmap: Bitmap) : SurveyActionResult() + data class Photo(val uri: Uri) : SurveyActionResult() data class Contact(val contact: String) : SurveyActionResult() } diff --git a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyFragment.kt b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyFragment.kt index 7046f59797..fbb34f82ed 100644 --- a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyFragment.kt +++ b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyFragment.kt @@ -20,6 +20,7 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.activity.result.contract.ActivityResultContracts.TakePicture import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.platform.ComposeView import androidx.fragment.app.Fragment @@ -30,7 +31,15 @@ import com.google.android.material.datepicker.MaterialDatePicker class SurveyFragment : Fragment() { - private val viewModel: SurveyViewModel by viewModels { SurveyViewModelFactory() } + private val viewModel: SurveyViewModel by viewModels { + SurveyViewModelFactory(PhotoUriManager(requireContext().applicationContext)) + } + + private val takePicture = registerForActivityResult(TakePicture()) { photoSaved -> + if (photoSaved) { + viewModel.onImageSaved() + } + } override fun onCreateView( inflater: LayoutInflater, @@ -71,7 +80,7 @@ class SurveyFragment : Fragment() { private fun handleSurveyAction(questionId: Int, actionType: SurveyActionType) { when (actionType) { SurveyActionType.PICK_DATE -> showDatePicker(questionId) - SurveyActionType.TAKE_PHOTO -> takeAPhoto(questionId) + SurveyActionType.TAKE_PHOTO -> takeAPhoto() SurveyActionType.SELECT_CONTACT -> selectContact(questionId) } } @@ -86,9 +95,8 @@ class SurveyFragment : Fragment() { } } - @Suppress("UNUSED_PARAMETER") - private fun takeAPhoto(questionId: Int) { - // TODO: unsupported for now + private fun takeAPhoto() { + takePicture.launch(viewModel.getUriToSaveImage()) } @Suppress("UNUSED_PARAMETER") diff --git a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyQuestions.kt b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyQuestions.kt index 8d687f0a6a..959d90b3dc 100644 --- a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyQuestions.kt +++ b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyQuestions.kt @@ -16,28 +16,42 @@ package com.example.compose.jetsurvey.survey +import androidx.annotation.StringRes +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Image import androidx.compose.foundation.ScrollableColumn -import androidx.compose.foundation.Text import androidx.compose.foundation.background import androidx.compose.foundation.clickable +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.Spacer +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.preferredHeight +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.selection.selectable -import androidx.compose.material.AmbientEmphasisLevels +import androidx.compose.material.AmbientContentAlpha import androidx.compose.material.Button import androidx.compose.material.Checkbox import androidx.compose.material.CheckboxConstants +import androidx.compose.material.ContentAlpha +import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme -import androidx.compose.material.ProvideEmphasis +import androidx.compose.material.OutlinedButton import androidx.compose.material.RadioButton import androidx.compose.material.RadioButtonConstants import androidx.compose.material.Slider +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.AddAPhoto +import androidx.compose.material.icons.filled.SwapHoriz import androidx.compose.runtime.Composable +import androidx.compose.runtime.Providers import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -45,31 +59,12 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource import androidx.compose.ui.unit.dp import androidx.ui.tooling.preview.Preview import com.example.compose.jetsurvey.R import com.example.compose.jetsurvey.theme.JetsurveyTheme -import com.example.compose.jetsurvey.theme.questionBackground - -@Preview -@Composable -fun QuestionPreview() { - val question = Question( - id = 2, - questionText = R.string.pick_superhero, - answer = PossibleAnswer.SingleChoice( - optionsStringRes = listOf( - R.string.spiderman, - R.string.ironman, - R.string.unikitty, - R.string.captain_planet - ) - ) - ) - JetsurveyTheme { - Question(question = question, answer = null, onAnswer = {}, onAction = { _, _ -> }) - } -} +import dev.chrisbanes.accompanist.coil.CoilImage @Composable fun Question( @@ -84,21 +79,35 @@ fun Question( contentPadding = PaddingValues(start = 20.dp, end = 20.dp) ) { Spacer(modifier = Modifier.preferredHeight(44.dp)) + val backgroundColor = if (MaterialTheme.colors.isLight) { + MaterialTheme.colors.onSurface.copy(alpha = 0.04f) + } else { + MaterialTheme.colors.onSurface.copy(alpha = 0.06f) + } Row( modifier = Modifier.fillMaxWidth().background( - color = MaterialTheme.colors.questionBackground, + color = backgroundColor, shape = MaterialTheme.shapes.small ) ) { - ProvideEmphasis(emphasis = AmbientEmphasisLevels.current.high) { + Text( + text = stringResource(id = question.questionText), + style = MaterialTheme.typography.subtitle1, + modifier = Modifier.fillMaxWidth().padding(vertical = 24.dp, horizontal = 16.dp) + ) + } + Spacer(modifier = Modifier.preferredHeight(24.dp)) + if (question.description != null) { + Providers(AmbientContentAlpha provides ContentAlpha.medium) { Text( - text = stringResource(id = question.questionText), - style = MaterialTheme.typography.subtitle1, - modifier = Modifier.fillMaxWidth().padding(vertical = 24.dp, horizontal = 16.dp) + text = stringResource(id = question.description), + style = MaterialTheme.typography.caption, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 24.dp, start = 8.dp, end = 8.dp) ) } } - Spacer(modifier = Modifier.preferredHeight(24.dp)) when (question.answer) { is PossibleAnswer.SingleChoice -> SingleChoiceQuestion( possibleAnswer = question.answer, @@ -164,28 +173,37 @@ private fun SingleChoiceQuestion( Unit } val optionSelected = text == selectedOption - Row( - modifier = Modifier - .fillMaxWidth() - .selectable( - selected = optionSelected, - onClick = onClickHandle - ) - .padding(vertical = 4.dp), - verticalAlignment = Alignment.CenterVertically + Surface( + shape = MaterialTheme.shapes.small, + border = BorderStroke( + width = 1.dp, + color = MaterialTheme.colors.onSurface.copy(alpha = 0.12f) + ), + modifier = Modifier.padding(vertical = 8.dp) ) { - RadioButton( - selected = optionSelected, - onClick = onClickHandle, - colors = RadioButtonConstants.defaultColors( - selectedColor = MaterialTheme.colors.primary + Row( + modifier = Modifier + .fillMaxWidth() + .selectable( + selected = optionSelected, + onClick = onClickHandle + ) + .padding(vertical = 16.dp, horizontal = 24.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = text ) - ) - Text( - text = text, - modifier = Modifier.padding(horizontal = 16.dp) - ) + RadioButton( + selected = optionSelected, + onClick = onClickHandle, + colors = RadioButtonConstants.defaultColors( + selectedColor = MaterialTheme.colors.primary + ) + ) + } } } } @@ -205,30 +223,119 @@ private fun MultipleChoiceQuestion( val selectedOption = answer?.answersStringRes?.contains(option.value) mutableStateOf(selectedOption ?: false) } + Surface( + shape = MaterialTheme.shapes.small, + border = BorderStroke( + width = 1.dp, + color = MaterialTheme.colors.onSurface.copy(alpha = 0.12f) + ), + modifier = Modifier.padding(vertical = 4.dp) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable( + onClick = { + checkedState = !checkedState + onAnswerSelected(option.value, checkedState) + } + ) + .padding(vertical = 16.dp, horizontal = 24.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text(text = option.key) + + Checkbox( + checked = checkedState, + onCheckedChange = { selected -> + checkedState = selected + onAnswerSelected(option.value, selected) + }, + colors = CheckboxConstants.defaultColors( + checkedColor = MaterialTheme.colors.primary + ), + ) + } + } + } + } +} + +@Composable +private fun ActionQuestion( + questionId: Int, + possibleAnswer: PossibleAnswer.Action, + answer: Answer.Action?, + onAction: (Int, SurveyActionType) -> Unit, + modifier: Modifier = Modifier +) { + when (possibleAnswer.actionType) { + SurveyActionType.PICK_DATE -> { + DateQuestion( + questionId = questionId, + answerLabel = possibleAnswer.label, + answer = answer, + onAction = onAction, + modifier = modifier + ) + } + SurveyActionType.TAKE_PHOTO -> { + PhotoQuestion( + questionId = questionId, + answer = answer, + onAction = onAction, + modifier = modifier + ) + } + SurveyActionType.SELECT_CONTACT -> TODO() + } +} + +@Composable +private fun PhotoQuestion( + questionId: Int, + answer: Answer.Action?, + onAction: (Int, SurveyActionType) -> Unit, + modifier: Modifier = Modifier +) { + val resource = if (answer != null) { + Icons.Filled.SwapHoriz + } else { + Icons.Filled.AddAPhoto + } + OutlinedButton( + onClick = { onAction(questionId, SurveyActionType.TAKE_PHOTO) }, + modifier = modifier, + contentPadding = PaddingValues() + ) { + Column { + if (answer != null && answer.result is SurveyActionResult.Photo) { + CoilImage( + data = answer.result.uri, + modifier = Modifier.fillMaxSize(), + fadeIn = true + ) + } else { + PhotoDefaultImage(modifier = Modifier.padding(horizontal = 86.dp, vertical = 74.dp)) + } Row( modifier = Modifier .fillMaxWidth() - .padding(vertical = 4.dp) - .clickable( - onClick = { - checkedState = !checkedState - onAnswerSelected(option.value, checkedState) - } - ) + .wrapContentSize(Alignment.BottomCenter) + .padding(vertical = 26.dp), + verticalAlignment = Alignment.CenterVertically ) { - Checkbox( - checked = checkedState, - onCheckedChange = { selected -> - checkedState = selected - onAnswerSelected(option.value, selected) - }, - colors = CheckboxConstants.defaultColors( - checkedColor = MaterialTheme.colors.primary - ) - ) + Icon(resource) + Spacer(modifier = Modifier.width(8.dp)) Text( - text = option.key, - modifier = Modifier.padding(horizontal = 16.dp) + text = stringResource( + id = if (answer != null) { + R.string.retake_photo + } else { + R.string.add_photo + } + ) ) } } @@ -236,34 +343,45 @@ private fun MultipleChoiceQuestion( } @Composable -private fun ActionQuestion( +private fun DateQuestion( questionId: Int, - possibleAnswer: PossibleAnswer.Action, + @StringRes answerLabel: Int, answer: Answer.Action?, onAction: (Int, SurveyActionType) -> Unit, modifier: Modifier = Modifier ) { Button( - onClick = { onAction(questionId, possibleAnswer.actionType) }, + onClick = { onAction(questionId, SurveyActionType.PICK_DATE) }, modifier = modifier.padding(vertical = 20.dp) ) { - Text(text = stringResource(id = possibleAnswer.label)) + Text(text = stringResource(id = answerLabel)) } - if (answer != null) { - when (answer.result) { - is SurveyActionResult.Date -> { - Text( - text = stringResource(R.string.selected_date, answer.result.date), - style = MaterialTheme.typography.h4, - modifier = Modifier.padding(vertical = 20.dp) - ) - } - is SurveyActionResult.Photo -> TODO() - is SurveyActionResult.Contact -> TODO() - } + + if (answer != null && answer.result is SurveyActionResult.Date) { + Text( + text = stringResource(R.string.selected_date, answer.result.date), + style = MaterialTheme.typography.h4, + modifier = Modifier.padding(vertical = 20.dp) + ) } } +@Composable +private fun PhotoDefaultImage( + lightTheme: Boolean = MaterialTheme.colors.isLight, + modifier: Modifier = Modifier +) { + val assetId = if (lightTheme) { + R.drawable.ic_selfie_light + } else { + R.drawable.ic_selfie_dark + } + Image( + asset = vectorResource(id = assetId), + modifier = modifier + ) +} + @Composable private fun SliderQuestion( possibleAnswer: PossibleAnswer.Slider, @@ -295,3 +413,24 @@ private fun SliderQuestion( ) } } + +@Preview +@Composable +fun QuestionPreview() { + val question = Question( + id = 2, + questionText = R.string.pick_superhero, + answer = PossibleAnswer.SingleChoice( + optionsStringRes = listOf( + R.string.spiderman, + R.string.ironman, + R.string.unikitty, + R.string.captain_planet + ) + ), + description = R.string.select_one + ) + JetsurveyTheme { + Question(question = question, answer = null, onAnswer = {}, onAction = { _, _ -> }) + } +} diff --git a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyRepository.kt b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyRepository.kt index 3fb7968878..c5e314866b 100644 --- a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyRepository.kt +++ b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyRepository.kt @@ -16,14 +16,16 @@ package com.example.compose.jetsurvey.survey +import android.os.Build import com.example.compose.jetsurvey.R import com.example.compose.jetsurvey.survey.PossibleAnswer.Action import com.example.compose.jetsurvey.survey.PossibleAnswer.MultipleChoice import com.example.compose.jetsurvey.survey.PossibleAnswer.SingleChoice import com.example.compose.jetsurvey.survey.SurveyActionType.PICK_DATE +import com.example.compose.jetsurvey.survey.SurveyActionType.TAKE_PHOTO // Static data of questions -private val jetpackQuestions = listOf( +private val jetpackQuestions = mutableListOf( Question( id = 1, questionText = R.string.in_my_free_time, @@ -36,7 +38,8 @@ private val jetpackQuestions = listOf( R.string.dance, R.string.watch_movies ) - ) + ), + description = R.string.select_all ), Question( id = 2, @@ -48,7 +51,8 @@ private val jetpackQuestions = listOf( R.string.unikitty, R.string.captain_planet ) - ) + ), + description = R.string.select_one ), Question( id = 7, @@ -60,12 +64,14 @@ private val jetpackQuestions = listOf( R.string.back_to_future, R.string.outbreak ) - ) + ), + description = R.string.select_one ), Question( id = 3, questionText = R.string.takeaway, - answer = Action(label = R.string.pick_date, actionType = PICK_DATE) + answer = Action(label = R.string.pick_date, actionType = PICK_DATE), + description = R.string.select_date ), Question( id = 4, @@ -77,7 +83,19 @@ private val jetpackQuestions = listOf( endText = R.string.selfie_max ) ) -) +).apply { + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) { + // Add the camera feature only for devices 29+ + add( + Question( + id = 975, + questionText = R.string.selfie_skills, + answer = Action(label = R.string.add_photo, actionType = TAKE_PHOTO) + ) + ) + } +}.toList() + private val jetpackSurvey = Survey( title = R.string.which_jetpack_library, questions = jetpackQuestions diff --git a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyScreen.kt b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyScreen.kt index a524e76304..d58c29b7bb 100644 --- a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyScreen.kt +++ b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyScreen.kt @@ -17,7 +17,6 @@ package com.example.compose.jetsurvey.survey import androidx.compose.foundation.ScrollableColumn -import androidx.compose.foundation.Text import androidx.compose.foundation.layout.ConstraintLayout import androidx.compose.foundation.layout.ExperimentalLayout import androidx.compose.foundation.layout.Row @@ -27,25 +26,27 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.preferredHeight import androidx.compose.foundation.layout.width -import androidx.compose.material.AmbientEmphasisLevels +import androidx.compose.material.AmbientContentAlpha import androidx.compose.material.Button +import androidx.compose.material.ContentAlpha import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.LinearProgressIndicator import androidx.compose.material.MaterialTheme import androidx.compose.material.OutlinedButton -import androidx.compose.material.ProvideEmphasis import androidx.compose.material.Scaffold import androidx.compose.material.Surface +import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue +import androidx.compose.runtime.Providers import androidx.compose.runtime.remember -import androidx.compose.runtime.savedinstancestate.savedInstanceState -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.annotatedString +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp import com.example.compose.jetsurvey.R import com.example.compose.jetsurvey.theme.progressIndicatorBackground @@ -57,9 +58,9 @@ fun SurveyQuestionsScreen( onDonePressed: () -> Unit, onBackPressed: () -> Unit ) { - var currentQuestionIndex by savedInstanceState { 0 } - val questionState = - remember(currentQuestionIndex) { questions.questionsState[currentQuestionIndex] } + val questionState = remember(questions.currentQuestionIndex) { + questions.questionsState[questions.currentQuestionIndex] + } Surface(modifier = Modifier.fillMaxSize()) { Scaffold( @@ -87,8 +88,8 @@ fun SurveyQuestionsScreen( bottomBar = { SurveyBottomBar( questionState = questionState, - onPreviousPressed = { currentQuestionIndex-- }, - onNextPressed = { currentQuestionIndex++ }, + onPreviousPressed = { questions.currentQuestionIndex-- }, + onNextPressed = { questions.currentQuestionIndex++ }, onDonePressed = onDonePressed ) } @@ -144,6 +145,31 @@ private fun SurveyResult(result: SurveyState.Result, modifier: Modifier = Modifi } } +@Composable +private fun TopAppBarTitle( + questionIndex: Int, + totalQuestionsCount: Int, + modifier: Modifier = Modifier +) { + val indexStyle = MaterialTheme.typography.caption.toSpanStyle().copy( + fontWeight = FontWeight.Bold + ) + val totalStyle = MaterialTheme.typography.caption.toSpanStyle() + val text = annotatedString { + withStyle(style = indexStyle) { + append("${questionIndex + 1}") + } + withStyle(style = totalStyle) { + append(stringResource(R.string.question_count, totalQuestionsCount)) + } + } + Text( + text = text, + style = MaterialTheme.typography.caption, + modifier = modifier + ) +} + @OptIn(ExperimentalLayout::class) @Composable private fun SurveyTopAppBar( @@ -153,19 +179,15 @@ private fun SurveyTopAppBar( ) { ConstraintLayout(modifier = Modifier.fillMaxWidth()) { val (button, text, progress) = createRefs() - Text( - text = stringResource( - R.string.question_count, - questionIndex + 1, - totalQuestionsCount - ), - style = MaterialTheme.typography.caption, + TopAppBarTitle( + questionIndex = questionIndex, + totalQuestionsCount = totalQuestionsCount, modifier = Modifier.padding(vertical = 20.dp).constrainAs(text) { centerHorizontallyTo(parent) } ) - ProvideEmphasis(emphasis = AmbientEmphasisLevels.current.medium) { + Providers(AmbientContentAlpha provides ContentAlpha.medium) { IconButton( onClick = onBackPressed, modifier = Modifier.padding(horizontal = 12.dp).constrainAs(button) { diff --git a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyState.kt b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyState.kt index c457901ccb..b9b92ba40a 100644 --- a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyState.kt +++ b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyState.kt @@ -38,7 +38,9 @@ sealed class SurveyState { data class Questions( @StringRes val surveyTitle: Int, val questionsState: List - ) : SurveyState() + ) : SurveyState() { + var currentQuestionIndex by mutableStateOf(0) + } data class Result( @StringRes val surveyTitle: Int, diff --git a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyViewModel.kt b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyViewModel.kt index 944c32dacc..f5e86aa747 100644 --- a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyViewModel.kt +++ b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/survey/SurveyViewModel.kt @@ -16,6 +16,7 @@ package com.example.compose.jetsurvey.survey +import android.net.Uri import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel @@ -23,7 +24,10 @@ import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import kotlinx.coroutines.launch -class SurveyViewModel(private val surveyRepository: SurveyRepository) : ViewModel() { +class SurveyViewModel( + private val surveyRepository: SurveyRepository, + private val photoUriManager: PhotoUriManager +) : ViewModel() { private val _uiState = MutableLiveData() val uiState: LiveData @@ -31,6 +35,9 @@ class SurveyViewModel(private val surveyRepository: SurveyRepository) : ViewMode private lateinit var surveyInitialState: SurveyState + // Uri used to save photos taken with the camera + private var uri: Uri? = null + init { viewModelScope.launch { val survey = surveyRepository.getSurvey() @@ -62,6 +69,19 @@ class SurveyViewModel(private val surveyRepository: SurveyRepository) : ViewMode updateStateWithActionResult(questionId, SurveyActionResult.Date(date)) } + fun getUriToSaveImage(): Uri? { + uri = photoUriManager.buildNewUri() + return uri + } + + fun onImageSaved() { + uri?.let { uri -> + getLatestQuestionId()?.let { questionId -> + updateStateWithActionResult(questionId, SurveyActionResult.Photo(uri)) + } + } + } + private fun updateStateWithActionResult(questionId: Int, result: SurveyActionResult) { val latestState = _uiState.value if (latestState != null && latestState is SurveyState.Questions) { @@ -73,13 +93,23 @@ class SurveyViewModel(private val surveyRepository: SurveyRepository) : ViewMode question.enableNext = true } } + + private fun getLatestQuestionId(): Int? { + val latestState = _uiState.value + if (latestState != null && latestState is SurveyState.Questions) { + return latestState.questionsState[latestState.currentQuestionIndex].question.id + } + return null + } } -class SurveyViewModelFactory : ViewModelProvider.Factory { +class SurveyViewModelFactory( + private val photoUriManager: PhotoUriManager +) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T { if (modelClass.isAssignableFrom(SurveyViewModel::class.java)) { - return SurveyViewModel(SurveyRepository) as T + return SurveyViewModel(SurveyRepository, photoUriManager) as T } throw IllegalArgumentException("Unknown ViewModel class") } diff --git a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/theme/Theme.kt b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/theme/Theme.kt index 748aa869e8..f91c82fee4 100644 --- a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/theme/Theme.kt +++ b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/theme/Theme.kt @@ -60,10 +60,6 @@ val Colors.snackbarAction: Color val Colors.progressIndicatorBackground: Color get() = if (isLight) Color.Black.copy(alpha = 0.12f) else Color.Black.copy(alpha = 0.24f) -@Composable -val Colors.questionBackground: Color - get() = if (isLight) Gray100 else Gray900 - @Composable fun JetsurveyTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable() () -> Unit) { val colors = if (darkTheme) { diff --git a/Jetsurvey/app/src/main/res/drawable/ic_selfie_dark.xml b/Jetsurvey/app/src/main/res/drawable/ic_selfie_dark.xml new file mode 100644 index 0000000000..504306467e --- /dev/null +++ b/Jetsurvey/app/src/main/res/drawable/ic_selfie_dark.xml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Jetsurvey/app/src/main/res/drawable/ic_selfie_light.xml b/Jetsurvey/app/src/main/res/drawable/ic_selfie_light.xml new file mode 100644 index 0000000000..bd7c7fa611 --- /dev/null +++ b/Jetsurvey/app/src/main/res/drawable/ic_selfie_light.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Jetsurvey/app/src/main/res/values/strings.xml b/Jetsurvey/app/src/main/res/values/strings.xml index 368cc33b99..2417fde8d0 100644 --- a/Jetsurvey/app/src/main/res/values/strings.xml +++ b/Jetsurvey/app/src/main/res/values/strings.xml @@ -38,7 +38,10 @@ Which Jetpack library are you? - %1$d of %2$d + \u00A0of %d + Select one. + Select all that apply. + Select date. In my free time I like to … @@ -67,8 +70,9 @@ 🤩️ - Show us your selfie skills! - Take a selfie! + Show off your selfie skills! + ADD PHOTO + RETAKE PHOTO Who\'s your best friend? diff --git a/Jetsurvey/build.gradle b/Jetsurvey/build.gradle index 21f2f12519..d19b7138ae 100644 --- a/Jetsurvey/build.gradle +++ b/Jetsurvey/build.gradle @@ -41,6 +41,8 @@ subprojects { if (Libs.AndroidX.Compose.version.endsWith("SNAPSHOT")) { maven { url Libs.AndroidX.Compose.snapshotUrl } } + + maven { url 'https://oss.sonatype.org/content/repositories/snapshots' } } apply plugin: 'com.diffplug.spotless' diff --git a/Jetsurvey/buildSrc/src/main/java/com/example/compose/jetsurvey/buildsrc/dependencies.kt b/Jetsurvey/buildSrc/src/main/java/com/example/compose/jetsurvey/buildsrc/dependencies.kt index d7ae38de39..80d9b83b53 100644 --- a/Jetsurvey/buildSrc/src/main/java/com/example/compose/jetsurvey/buildsrc/dependencies.kt +++ b/Jetsurvey/buildSrc/src/main/java/com/example/compose/jetsurvey/buildsrc/dependencies.kt @@ -21,13 +21,18 @@ object Versions { } object Libs { - const val androidGradlePlugin = "com.android.tools.build:gradle:4.2.0-alpha15" + const val androidGradlePlugin = "com.android.tools.build:gradle:4.2.0-alpha16" const val jdkDesugar = "com.android.tools:desugar_jdk_libs:1.0.9" const val junit = "junit:junit:4.13" const val material = "com.google.android.material:material:1.1.0" + object Accompanist { + private const val version = "0.3.3.1" + const val coil = "dev.chrisbanes.accompanist:accompanist-coil:$version" + } + object Kotlin { private const val version = "1.4.10" const val stdlib = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$version" @@ -41,7 +46,7 @@ object Libs { object Compose { const val snapshot = "" - const val version = "1.0.0-alpha06" + const val version = "1.0.0-alpha07" @get:JvmStatic val snapshotUrl: String diff --git a/Owl/app/build.gradle b/Owl/app/build.gradle index 98a6ec6a70..8472fcd065 100644 --- a/Owl/app/build.gradle +++ b/Owl/app/build.gradle @@ -91,4 +91,5 @@ dependencies { implementation Libs.AndroidX.Compose.tooling implementation Libs.Accompanist.coil + implementation Libs.Accompanist.insets } diff --git a/Owl/app/src/main/java/com/example/owl/ui/OwlApp.kt b/Owl/app/src/main/java/com/example/owl/ui/OwlApp.kt index cc6b3e8a5f..a8cd068dd9 100644 --- a/Owl/app/src/main/java/com/example/owl/ui/OwlApp.kt +++ b/Owl/app/src/main/java/com/example/owl/ui/OwlApp.kt @@ -27,8 +27,8 @@ import com.example.owl.ui.courses.Courses import com.example.owl.ui.onboarding.Onboarding import com.example.owl.ui.utils.AmbientBackDispatcher import com.example.owl.ui.utils.Navigator -import com.example.owl.ui.utils.ProvideDisplayInsets import com.example.owl.ui.utils.ProvideImageLoader +import dev.chrisbanes.accompanist.insets.ProvideWindowInsets @Composable fun OwlApp(backDispatcher: OnBackPressedDispatcher) { @@ -41,7 +41,7 @@ fun OwlApp(backDispatcher: OnBackPressedDispatcher) { val actions = remember(navigator) { Actions(navigator) } Providers(AmbientBackDispatcher provides backDispatcher) { - ProvideDisplayInsets { + ProvideWindowInsets { ProvideImageLoader { Crossfade(navigator.current) { destination -> when (destination) { diff --git a/Owl/app/src/main/java/com/example/owl/ui/common/CourseListItem.kt b/Owl/app/src/main/java/com/example/owl/ui/common/CourseListItem.kt index a3de83fbb6..f888df13ee 100644 --- a/Owl/app/src/main/java/com/example/owl/ui/common/CourseListItem.kt +++ b/Owl/app/src/main/java/com/example/owl/ui/common/CourseListItem.kt @@ -16,7 +16,6 @@ package com.example.owl.ui.common -import androidx.compose.foundation.Text import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -30,6 +29,7 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface +import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.OndemandVideo import androidx.compose.runtime.Composable diff --git a/Owl/app/src/main/java/com/example/owl/ui/course/CourseDetails.kt b/Owl/app/src/main/java/com/example/owl/ui/course/CourseDetails.kt index 1046aa011d..586ae8f41f 100644 --- a/Owl/app/src/main/java/com/example/owl/ui/course/CourseDetails.kt +++ b/Owl/app/src/main/java/com/example/owl/ui/course/CourseDetails.kt @@ -20,7 +20,6 @@ import androidx.compose.animation.animate import androidx.compose.foundation.Image import androidx.compose.foundation.ScrollableColumn import androidx.compose.foundation.ScrollableRow -import androidx.compose.foundation.Text import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -36,15 +35,16 @@ import androidx.compose.foundation.layout.preferredHeight import androidx.compose.foundation.layout.preferredSize import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.AmbientEmphasisLevels +import androidx.compose.material.AmbientContentAlpha +import androidx.compose.material.ContentAlpha import androidx.compose.material.Divider import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.FractionalThreshold import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme -import androidx.compose.material.ProvideEmphasis import androidx.compose.material.Surface +import androidx.compose.material.Text import androidx.compose.material.TopAppBar import androidx.compose.material.contentColorFor import androidx.compose.material.icons.Icons @@ -56,13 +56,14 @@ import androidx.compose.material.primarySurface import androidx.compose.material.rememberSwipeableState import androidx.compose.material.swipeable import androidx.compose.runtime.Composable +import androidx.compose.runtime.Providers import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.WithConstraints import androidx.compose.ui.drawLayer import androidx.compose.ui.gesture.scrollorientationlocking.Orientation import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.WithConstraints import androidx.compose.ui.platform.DensityAmbient import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource @@ -81,14 +82,14 @@ import com.example.owl.ui.common.OutlinedAvatar import com.example.owl.ui.theme.BlueTheme import com.example.owl.ui.theme.PinkTheme import com.example.owl.ui.theme.pink500 -import com.example.owl.ui.utils.AmbientInsets import com.example.owl.ui.utils.NetworkImage import com.example.owl.ui.utils.backHandler import com.example.owl.ui.utils.lerp -import com.example.owl.ui.utils.navigationBarsPadding import com.example.owl.ui.utils.scrim -import com.example.owl.ui.utils.statusBarsPadding -import com.example.owl.ui.utils.toPaddingValues +import dev.chrisbanes.accompanist.insets.AmbientWindowInsets +import dev.chrisbanes.accompanist.insets.navigationBarsPadding +import dev.chrisbanes.accompanist.insets.statusBarsPadding +import dev.chrisbanes.accompanist.insets.toPaddingValues private val FabSize = 56.dp private const val ExpandedSheetAlpha = 0.96f @@ -227,7 +228,7 @@ private fun CourseDescriptionBody(course: Course) { .padding(horizontal = 16.dp) ) Spacer(modifier = Modifier.preferredHeight(16.dp)) - ProvideEmphasis(AmbientEmphasisLevels.current.medium) { + Providers(AmbientContentAlpha provides ContentAlpha.medium) { Text( text = stringResource(id = R.string.course_desc), style = MaterialTheme.typography.body1, @@ -246,7 +247,7 @@ private fun CourseDescriptionBody(course: Course) { .fillMaxWidth() .padding(16.dp) ) - ProvideEmphasis(AmbientEmphasisLevels.current.medium) { + Providers(AmbientContentAlpha provides ContentAlpha.medium) { Text( text = stringResource(id = R.string.needs), style = MaterialTheme.typography.body1, @@ -320,7 +321,7 @@ private fun LessonsSheet( ) { // Use the fraction that the sheet is open to drive the transformation from FAB -> Sheet val fabSize = with(DensityAmbient.current) { FabSize.toPx() } - val fabSheetHeight = fabSize + AmbientInsets.current.systemBars.bottom + val fabSheetHeight = fabSize + AmbientWindowInsets.current.systemBars.bottom val offsetX = lerp(width - fabSize, 0f, 0f, 0.15f, openFraction) val offsetY = lerp(height - fabSheetHeight, 0f, openFraction) val tlCorner = lerp(fabSize, 0f, 0f, 0.15f, openFraction) @@ -388,7 +389,7 @@ private fun Lessons( } ScrollableColumn( scrollState = scroll, - contentPadding = AmbientInsets.current.systemBars.toPaddingValues( + contentPadding = AmbientWindowInsets.current.systemBars.toPaddingValues( top = false ) ) { @@ -442,7 +443,7 @@ private fun Lesson(lesson: Lesson) { maxLines = 2, overflow = TextOverflow.Ellipsis ) - ProvideEmphasis(AmbientEmphasisLevels.current.medium) { + Providers(AmbientContentAlpha provides ContentAlpha.medium) { Row( modifier = Modifier.padding(top = 4.dp), verticalAlignment = Alignment.CenterVertically diff --git a/Owl/app/src/main/java/com/example/owl/ui/courses/Courses.kt b/Owl/app/src/main/java/com/example/owl/ui/courses/Courses.kt index e8c92b7eae..3c4c2093ef 100644 --- a/Owl/app/src/main/java/com/example/owl/ui/courses/Courses.kt +++ b/Owl/app/src/main/java/com/example/owl/ui/courses/Courses.kt @@ -18,17 +18,17 @@ package com.example.owl.ui.courses import androidx.annotation.DrawableRes import androidx.annotation.StringRes -import androidx.compose.foundation.AmbientContentColor import androidx.compose.foundation.Image -import androidx.compose.foundation.Text import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.preferredHeight +import androidx.compose.material.AmbientContentColor import androidx.compose.material.BottomNavigation import androidx.compose.material.BottomNavigationItem import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme import androidx.compose.material.Scaffold +import androidx.compose.material.Text import androidx.compose.material.TopAppBar import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AccountCircle @@ -45,8 +45,8 @@ import com.example.owl.R import com.example.owl.model.courses import com.example.owl.model.topics import com.example.owl.ui.theme.BlueTheme -import com.example.owl.ui.utils.navigationBarsHeightPlus -import com.example.owl.ui.utils.navigationBarsPadding +import dev.chrisbanes.accompanist.insets.navigationBarsHeight +import dev.chrisbanes.accompanist.insets.navigationBarsPadding @Composable fun Courses(selectCourse: (Long) -> Unit) { @@ -57,7 +57,7 @@ fun Courses(selectCourse: (Long) -> Unit) { backgroundColor = MaterialTheme.colors.primarySurface, bottomBar = { BottomNavigation( - Modifier.navigationBarsHeightPlus(56.dp) + Modifier.navigationBarsHeight(additional = 56.dp) ) { tabs.forEach { tab -> BottomNavigationItem( diff --git a/Owl/app/src/main/java/com/example/owl/ui/courses/FeaturedCourses.kt b/Owl/app/src/main/java/com/example/owl/ui/courses/FeaturedCourses.kt index 215797d4cc..4eb0469f21 100644 --- a/Owl/app/src/main/java/com/example/owl/ui/courses/FeaturedCourses.kt +++ b/Owl/app/src/main/java/com/example/owl/ui/courses/FeaturedCourses.kt @@ -17,7 +17,6 @@ package com.example.owl.ui.courses import androidx.compose.foundation.ScrollableColumn -import androidx.compose.foundation.Text import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.ConstraintLayout import androidx.compose.foundation.layout.ExperimentalLayout @@ -28,11 +27,12 @@ import androidx.compose.material.AmbientElevationOverlay import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface +import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.OndemandVideo import androidx.compose.runtime.Composable -import androidx.compose.ui.Layout import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.Layout import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp @@ -43,7 +43,7 @@ import com.example.owl.ui.common.OutlinedAvatar import com.example.owl.ui.theme.BlueTheme import com.example.owl.ui.theme.OwlTheme import com.example.owl.ui.utils.NetworkImage -import com.example.owl.ui.utils.statusBarsPadding +import dev.chrisbanes.accompanist.insets.statusBarsPadding import kotlin.math.ceil @Composable diff --git a/Owl/app/src/main/java/com/example/owl/ui/courses/MyCourses.kt b/Owl/app/src/main/java/com/example/owl/ui/courses/MyCourses.kt index f816e6ef47..b5ec2cdf56 100644 --- a/Owl/app/src/main/java/com/example/owl/ui/courses/MyCourses.kt +++ b/Owl/app/src/main/java/com/example/owl/ui/courses/MyCourses.kt @@ -21,7 +21,6 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.preferredHeight import androidx.compose.foundation.layout.preferredWidth -import androidx.compose.foundation.lazy.ExperimentalLazyDsl import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable @@ -32,9 +31,8 @@ import com.example.owl.model.Course import com.example.owl.model.courses import com.example.owl.ui.common.CourseListItem import com.example.owl.ui.theme.BlueTheme -import com.example.owl.ui.utils.statusBarsHeight +import dev.chrisbanes.accompanist.insets.statusBarsHeight -@OptIn(ExperimentalLazyDsl::class) @Composable fun MyCourses( courses: List, diff --git a/Owl/app/src/main/java/com/example/owl/ui/courses/SearchCourses.kt b/Owl/app/src/main/java/com/example/owl/ui/courses/SearchCourses.kt index 450edaaa3f..33b7c06dde 100644 --- a/Owl/app/src/main/java/com/example/owl/ui/courses/SearchCourses.kt +++ b/Owl/app/src/main/java/com/example/owl/ui/courses/SearchCourses.kt @@ -16,18 +16,19 @@ package com.example.owl.ui.courses -import androidx.compose.foundation.BaseTextField import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image import androidx.compose.foundation.ScrollableColumn -import androidx.compose.foundation.Text import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.material.AmbientContentColor import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text import androidx.compose.material.TopAppBar import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AccountCircle @@ -44,7 +45,7 @@ import com.example.owl.R import com.example.owl.model.Topic import com.example.owl.model.topics import com.example.owl.ui.theme.BlueTheme -import com.example.owl.ui.utils.statusBarsPadding +import dev.chrisbanes.accompanist.insets.statusBarsPadding @Composable fun SearchCourses( @@ -102,10 +103,14 @@ private fun AppBar( .align(Alignment.CenterVertically) ) // TODO hint - BaseTextField( + BasicTextField( value = searchTerm, onValueChange = updateSearchTerm, - textStyle = MaterialTheme.typography.subtitle1, + textStyle = MaterialTheme.typography.subtitle1.copy( + color = AmbientContentColor.current + ), + maxLines = 1, + cursorColor = AmbientContentColor.current, modifier = Modifier .weight(1f) .align(Alignment.CenterVertically) diff --git a/Owl/app/src/main/java/com/example/owl/ui/onboarding/Onboarding.kt b/Owl/app/src/main/java/com/example/owl/ui/onboarding/Onboarding.kt index f1e7925754..084eb209a2 100644 --- a/Owl/app/src/main/java/com/example/owl/ui/onboarding/Onboarding.kt +++ b/Owl/app/src/main/java/com/example/owl/ui/onboarding/Onboarding.kt @@ -21,7 +21,6 @@ import androidx.compose.animation.core.FloatPropKey import androidx.compose.animation.core.transitionDefinition import androidx.compose.animation.transition import androidx.compose.foundation.Image -import androidx.compose.foundation.Text import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -37,26 +36,28 @@ import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.selection.toggleable import androidx.compose.foundation.shape.CornerSize -import androidx.compose.material.AmbientEmphasisLevels +import androidx.compose.material.AmbientContentAlpha +import androidx.compose.material.ContentAlpha import androidx.compose.material.FloatingActionButton import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme -import androidx.compose.material.ProvideEmphasis import androidx.compose.material.Scaffold import androidx.compose.material.Surface +import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Done import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.rounded.Explore import androidx.compose.material.primarySurface import androidx.compose.runtime.Composable +import androidx.compose.runtime.Providers import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment -import androidx.compose.ui.Layout import androidx.compose.ui.Modifier import androidx.compose.ui.drawLayer +import androidx.compose.ui.layout.Layout import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.style.TextAlign @@ -69,8 +70,8 @@ import com.example.owl.ui.theme.OwlTheme import com.example.owl.ui.theme.YellowTheme import com.example.owl.ui.theme.pink500 import com.example.owl.ui.utils.NetworkImage -import com.example.owl.ui.utils.navigationBarsPadding -import com.example.owl.ui.utils.statusBarsPadding +import dev.chrisbanes.accompanist.insets.navigationBarsPadding +import dev.chrisbanes.accompanist.insets.statusBarsPadding import kotlin.math.max @Composable @@ -94,17 +95,15 @@ fun Onboarding(onboardingComplete: () -> Unit) { .navigationBarsPadding() .padding(innerPadding) ) { - ProvideEmphasis(AmbientEmphasisLevels.current.high) { - Text( - text = stringResource(R.string.choose_topics_that_interest_you), - style = MaterialTheme.typography.h4, - textAlign = TextAlign.End, - modifier = Modifier.padding( - horizontal = 16.dp, - vertical = 32.dp - ) + Text( + text = stringResource(R.string.choose_topics_that_interest_you), + style = MaterialTheme.typography.h4, + textAlign = TextAlign.End, + modifier = Modifier.padding( + horizontal = 16.dp, + vertical = 32.dp ) - } + ) TopicsGrid( modifier = Modifier .weight(1f) @@ -129,13 +128,11 @@ private fun AppBar() { asset = vectorResource(id = OwlTheme.images.lockupLogo), modifier = Modifier.padding(16.dp) ) - ProvideEmphasis(emphasis = AmbientEmphasisLevels.current.high) { - IconButton( - modifier = Modifier.padding(16.dp), - onClick = { /* todo */ } - ) { - Icon(Icons.Filled.Settings) - } + IconButton( + modifier = Modifier.padding(16.dp), + onClick = { /* todo */ } + ) { + Icon(Icons.Filled.Settings) } } } @@ -221,7 +218,7 @@ private fun TopicChip(topic: Topic) { ) ) Row(verticalAlignment = Alignment.CenterVertically) { - ProvideEmphasis(emphasis = AmbientEmphasisLevels.current.medium) { + Providers(AmbientContentAlpha provides ContentAlpha.medium) { Icon( asset = vectorResource(R.drawable.ic_grain), modifier = Modifier diff --git a/Owl/app/src/main/java/com/example/owl/ui/utils/Insets.kt b/Owl/app/src/main/java/com/example/owl/ui/utils/Insets.kt deleted file mode 100644 index 81f232fd35..0000000000 --- a/Owl/app/src/main/java/com/example/owl/ui/utils/Insets.kt +++ /dev/null @@ -1,610 +0,0 @@ -/* - * Copyright 2020 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. - */ - -@file:Suppress("NOTHING_TO_INLINE", "unused") - -package com.example.owl.ui.utils - -import android.view.View -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.height -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Providers -import androidx.compose.runtime.Stable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.onCommit -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.runtime.staticAmbientOf -import androidx.compose.ui.LayoutModifier -import androidx.compose.ui.Measurable -import androidx.compose.ui.MeasureScope -import androidx.compose.ui.Modifier -import androidx.compose.ui.composed -import androidx.compose.ui.layout.IntrinsicMeasurable -import androidx.compose.ui.layout.IntrinsicMeasureScope -import androidx.compose.ui.platform.DensityAmbient -import androidx.compose.ui.platform.LayoutDirectionAmbient -import androidx.compose.ui.platform.ViewAmbient -import androidx.compose.ui.unit.Constraints -import androidx.compose.ui.unit.Density -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.LayoutDirection -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.offset -import androidx.core.view.ViewCompat -import androidx.core.view.WindowInsetsCompat -import androidx.core.view.WindowInsetsCompat.Type - -/** - * Taken from https://goo.gle/compose-insets. Requires androidx.core:core v1.5.0-alpha02+ - */ - -/** - * Main holder of our inset values. - */ -@Stable -class DisplayInsets { - /** - * Inset values which match [WindowInsetsCompat.Type.systemBars] - */ - val systemBars = Insets() - - /** - * Inset values which match [WindowInsetsCompat.Type.systemGestures] - */ - val systemGestures = Insets() - - /** - * Inset values which match [WindowInsetsCompat.Type.navigationBars] - */ - val navigationBars = Insets() - - /** - * Inset values which match [WindowInsetsCompat.Type.statusBars] - */ - val statusBars = Insets() - - /** - * Inset values which match [WindowInsetsCompat.Type.ime] - */ - val ime = Insets() -} - -@Stable -class Insets { - /** - * The left dimension of these insets in pixels. - */ - var left by mutableStateOf(0) - internal set - - /** - * The top dimension of these insets in pixels. - */ - var top by mutableStateOf(0) - internal set - - /** - * The right dimension of these insets in pixels. - */ - var right by mutableStateOf(0) - internal set - - /** - * The bottom dimension of these insets in pixels. - */ - var bottom by mutableStateOf(0) - internal set - - /** - * Whether the insets are currently visible. - */ - var isVisible by mutableStateOf(true) - internal set -} - -val AmbientInsets = staticAmbientOf { DisplayInsets() } - -/** - * Applies any [WindowInsetsCompat] values to [AmbientInsets], which are then available - * within [content]. - * - * @param consumeWindowInsets Whether to consume any [WindowInsetsCompat]s which are dispatched to - * the host view. Defaults to `true`. - */ -@Composable -fun ProvideDisplayInsets( - consumeWindowInsets: Boolean = true, - content: @Composable () -> Unit -) { - val view = ViewAmbient.current - - val displayInsets = remember { DisplayInsets() } - - onCommit(view) { - ViewCompat.setOnApplyWindowInsetsListener(view) { _, windowInsets -> - displayInsets.systemBars.updateFrom(windowInsets, Type.systemBars()) - displayInsets.systemGestures.updateFrom(windowInsets, Type.systemGestures()) - displayInsets.statusBars.updateFrom(windowInsets, Type.statusBars()) - displayInsets.navigationBars.updateFrom(windowInsets, Type.navigationBars()) - displayInsets.ime.updateFrom(windowInsets, Type.ime()) - - if (consumeWindowInsets) WindowInsetsCompat.CONSUMED else windowInsets - } - - // Add an OnAttachStateChangeListener to request an inset pass each time we're attached - // to the window - val attachListener = object : View.OnAttachStateChangeListener { - override fun onViewAttachedToWindow(v: View) = v.requestApplyInsets() - override fun onViewDetachedFromWindow(v: View) = Unit - } - view.addOnAttachStateChangeListener(attachListener) - - if (view.isAttachedToWindow) { - // If the view is already attached, we can request an inset pass now - view.requestApplyInsets() - } - - onDispose { - view.removeOnAttachStateChangeListener(attachListener) - } - } - - Providers(AmbientInsets provides displayInsets) { - content() - } -} - -/** - * Updates our mutable state backed [Insets] from an Android system insets. - */ -private fun Insets.updateFrom(windowInsets: WindowInsetsCompat, type: Int) { - val insets = windowInsets.getInsets(type) - left = insets.left - top = insets.top - right = insets.right - bottom = insets.bottom - - isVisible = windowInsets.isVisible(type) -} - -/** - * Selectively apply additional space which matches the width/height of any system bars present - * on the respective edges of the screen. - * - * @param enabled Whether to apply padding using the system bars dimensions on the respective edges. - * Defaults to `true`. - */ -fun Modifier.systemBarsPadding(enabled: Boolean = true) = composed { - insetsPadding( - insets = AmbientInsets.current.systemBars, - left = enabled, - top = enabled, - right = enabled, - bottom = enabled - ) -} - -/** - * Apply additional space which matches the height of the status bars height along the top edge - * of the content. - */ -fun Modifier.statusBarsPadding() = composed { - insetsPadding(insets = AmbientInsets.current.statusBars, top = true) -} - -/** - * Apply additional space which matches the height of the navigation bars height - * along the [bottom] edge of the content, and additional space which matches the width of - * the navigation bars on the respective [left] and [right] edges. - * - * @param bottom Whether to apply padding to the bottom edge, which matches the navigation bars - * height (if present) at the bottom edge of the screen. Defaults to `true`. - * @param left Whether to apply padding to the left edge, which matches the navigation bars width - * (if present) on the left edge of the screen. Defaults to `true`. - * @param right Whether to apply padding to the right edge, which matches the navigation bars width - * (if present) on the right edge of the screen. Defaults to `true`. - */ -fun Modifier.navigationBarsPadding( - bottom: Boolean = true, - left: Boolean = true, - right: Boolean = true -) = composed { - insetsPadding( - insets = AmbientInsets.current.navigationBars, - left = left, - right = right, - bottom = bottom - ) -} - -/** - * Declare the height of the content to match the height of the status bars exactly. - * - * This is very handy when used with `Spacer` to push content below the status bars: - * ``` - * Column { - * Spacer(Modifier.statusBarHeight()) - * - * // Content to be drawn below status bars (y-axis) - * } - * ``` - * - * It's also useful when used to draw a scrim which matches the status bars: - * ``` - * Spacer( - * Modifier.statusBarHeight() - * .fillMaxWidth() - * .drawBackground(MaterialTheme.colors.background.copy(alpha = 0.3f) - * ) - * ``` - * - * Internally this matches the behavior of the [Modifier.height] modifier. - * - * @param additional Any additional height to add to the status bars size. - */ -fun Modifier.statusBarsHeight(additional: Dp = 0.dp) = composed { - InsetsSizeModifier( - insets = AmbientInsets.current.statusBars, - heightSide = VerticalSide.Top, - additionalHeight = additional - ) -} - -/** - * Declare the height of the content to match the height of the status bars exactly. - * - * This is very handy when used with `Spacer` to push content below the status bars: - * ``` - * Column { - * Spacer(Modifier.statusBarHeight()) - * - * // Content to be drawn below status bars (y-axis) - * } - * ``` - * - * It's also useful when used to draw a scrim which matches the status bars: - * ``` - * Spacer( - * Modifier.statusBarHeight() - * .fillMaxWidth() - * .drawBackground(MaterialTheme.colors.background.copy(alpha = 0.3f) - * ) - * ``` - * - * Internally this matches the behavior of the [Modifier.height] modifier. - */ -inline fun Modifier.statusBarsHeight() = statusBarsHeightPlus(0.dp) - -/** - * Declare the height of the content to match the height of the status bars, plus some - * additional height passed in via [additional]. - * - * As an example, this could be used with `Spacer` to push content below the status bar - * and app bars: - * - * ``` - * Column { - * Spacer(Modifier.statusBarHeightPlus(56.dp)) - * - * // Content to be drawn below status bars and app bar (y-axis) - * } - * ``` - * - * Internally this matches the behavior of the [Modifier.height] modifier. - * - * @param additional Any additional height to add to the status bars size. - */ -fun Modifier.statusBarsHeightPlus(additional: Dp) = composed { - InsetsSizeModifier( - insets = AmbientInsets.current.statusBars, - heightSide = VerticalSide.Top, - additionalHeight = additional - ) -} - -/** - * Declare the preferred height of the content to match the height of the navigation bars when - * present at the bottom of the screen. - * - * This is very handy when used with `Spacer` to push content below the navigation bars: - * ``` - * Column { - * // Content to be drawn above status bars (y-axis) - * Spacer(Modifier.navigationBarHeight()) - * } - * ``` - * - * It's also useful when used to draw a scrim which matches the navigation bars: - * ``` - * Spacer( - * Modifier.navigationBarHeight() - * .fillMaxWidth() - * .drawBackground(MaterialTheme.colors.background.copy(alpha = 0.3f) - * ) - * ``` - * - * Internally this matches the behavior of the [Modifier.height] modifier. - */ -inline fun Modifier.navigationBarsHeight() = navigationBarsHeightPlus(0.dp) - -/** - * Declare the height of the content to match the height of the navigation bars, plus some - * additional height passed in via [additional] - * - * As an example, this could be used with `Spacer` to push content above the navigation bar - * and bottom app bars: - * - * ``` - * Column { - * // Content to be drawn above navigation bars and bottom app bar (y-axis) - * - * Spacer(Modifier.statusBarHeightPlus(48.dp)) - * } - * ``` - * - * Internally this matches the behavior of the [Modifier.height] modifier. - * - * @param additional Any additional height to add to the status bars size. - */ -fun Modifier.navigationBarsHeightPlus(additional: Dp) = composed { - InsetsSizeModifier( - insets = AmbientInsets.current.navigationBars, - heightSide = VerticalSide.Bottom, - additionalHeight = additional - ) -} - -enum class HorizontalSide { Left, Right } -enum class VerticalSide { Top, Bottom } - -/** - * Declare the preferred width of the content to match the width of the navigation bars, - * on the given [side]. - * - * This is very handy when used with `Spacer` to push content inside from any vertical - * navigation bars (typically when the device is in landscape): - * ``` - * Row { - * Spacer(Modifier.navigationBarWidth(HorizontalSide.Left)) - * - * // Content to be inside the navigation bars (x-axis) - * - * Spacer(Modifier.navigationBarWidth(HorizontalSide.Right)) - * } - * ``` - * - * It's also useful when used to draw a scrim which matches the navigation bars: - * ``` - * Spacer( - * Modifier.navigationBarWidth(HorizontalSide.Left) - * .fillMaxHeight() - * .drawBackground(MaterialTheme.colors.background.copy(alpha = 0.3f) - * ) - * ``` - * - * Internally this matches the behavior of the [Modifier.height] modifier. - * - * @param side The navigation bar side to use as the source for the width. - */ -inline fun Modifier.navigationBarWidth(side: HorizontalSide) = navigationBarsWidthPlus(side, 0.dp) - -/** - * Declare the preferred width of the content to match the width of the navigation bars, - * on the given [side], plus additional width passed in via [additional]. - * - * Internally this matches the behavior of the [Modifier.height] modifier. - * - * @param side The navigation bar side to use as the source for the width. - * @param additional Any additional width to add to the status bars size. - */ -fun Modifier.navigationBarsWidthPlus( - side: HorizontalSide, - additional: Dp -) = composed { - InsetsSizeModifier( - insets = AmbientInsets.current.navigationBars, - widthSide = side, - additionalWidth = additional - ) -} - -/** - * Returns the current insets converted into [PaddingValues]. - * - * @param start Whether to apply the inset on the start dimension. - * @param top Whether to apply the inset on the top dimension. - * @param end Whether to apply the inset on the end dimension. - * @param bottom Whether to apply the inset on the bottom dimension. - */ -@Composable -fun Insets.toPaddingValues( - start: Boolean = true, - top: Boolean = true, - end: Boolean = true, - bottom: Boolean = true -): PaddingValues = with(DensityAmbient.current) { - val layoutDirection = LayoutDirectionAmbient.current - PaddingValues( - start = when { - start && layoutDirection == LayoutDirection.Ltr -> this@toPaddingValues.left.toDp() - start && layoutDirection == LayoutDirection.Rtl -> this@toPaddingValues.right.toDp() - else -> 0.dp - }, - top = when { - top -> this@toPaddingValues.top.toDp() - else -> 0.dp - }, - end = when { - end && layoutDirection == LayoutDirection.Ltr -> this@toPaddingValues.right.toDp() - end && layoutDirection == LayoutDirection.Rtl -> this@toPaddingValues.left.toDp() - else -> 0.dp - }, - bottom = when { - bottom -> this@toPaddingValues.bottom.toDp() - else -> 0.dp - } - ) -} - -/** - * Allows conditional setting of [insets] on each dimension. - */ -private inline fun Modifier.insetsPadding( - insets: Insets, - left: Boolean = false, - top: Boolean = false, - right: Boolean = false, - bottom: Boolean = false -) = this then InsetsPaddingModifier(insets, left, top, right, bottom) - -private data class InsetsPaddingModifier( - private val insets: Insets, - private val applyLeft: Boolean = false, - private val applyTop: Boolean = false, - private val applyRight: Boolean = false, - private val applyBottom: Boolean = false -) : LayoutModifier { - override fun MeasureScope.measure( - measurable: Measurable, - constraints: Constraints - ): MeasureScope.MeasureResult { - val left = if (applyLeft) insets.left else 0 - val top = if (applyTop) insets.top else 0 - val right = if (applyRight) insets.right else 0 - val bottom = if (applyBottom) insets.bottom else 0 - val horizontal = left + right - val vertical = top + bottom - - val placeable = measurable.measure(constraints.offset(-horizontal, -vertical)) - - val width = (placeable.width + horizontal) - .coerceIn(constraints.minWidth, constraints.maxWidth) - val height = (placeable.height + vertical) - .coerceIn(constraints.minHeight, constraints.maxHeight) - return layout(width, height) { - placeable.place(left, top) - } - } -} - -private data class InsetsSizeModifier( - private val insets: Insets, - private val widthSide: HorizontalSide? = null, - private val additionalWidth: Dp = 0.dp, - private val heightSide: VerticalSide? = null, - private val additionalHeight: Dp = 0.dp -) : LayoutModifier { - private val Density.targetConstraints: Constraints - get() { - val additionalWidthPx = additionalWidth.toIntPx() - val additionalHeightPx = additionalHeight.toIntPx() - return Constraints( - minWidth = additionalWidthPx + when (widthSide) { - HorizontalSide.Left -> insets.left - HorizontalSide.Right -> insets.right - null -> 0 - }, - minHeight = additionalHeightPx + when (heightSide) { - VerticalSide.Top -> insets.top - VerticalSide.Bottom -> insets.bottom - null -> 0 - }, - maxWidth = when (widthSide) { - HorizontalSide.Left -> insets.left + additionalWidthPx - HorizontalSide.Right -> insets.right + additionalWidthPx - null -> Constraints.Infinity - }, - maxHeight = when (heightSide) { - VerticalSide.Top -> insets.top + additionalHeightPx - VerticalSide.Bottom -> insets.bottom + additionalHeightPx - null -> Constraints.Infinity - } - ) - } - - override fun MeasureScope.measure( - measurable: Measurable, - constraints: Constraints - ): MeasureScope.MeasureResult { - val wrappedConstraints = targetConstraints.let { targetConstraints -> - val resolvedMinWidth = if (widthSide != null) { - targetConstraints.minWidth - } else { - constraints.minWidth.coerceAtMost(targetConstraints.maxWidth) - } - val resolvedMaxWidth = if (widthSide != null) { - targetConstraints.maxWidth - } else { - constraints.maxWidth.coerceAtLeast(targetConstraints.minWidth) - } - val resolvedMinHeight = if (heightSide != null) { - targetConstraints.minHeight - } else { - constraints.minHeight.coerceAtMost(targetConstraints.maxHeight) - } - val resolvedMaxHeight = if (heightSide != null) { - targetConstraints.maxHeight - } else { - constraints.maxHeight.coerceAtLeast(targetConstraints.minHeight) - } - Constraints( - resolvedMinWidth, - resolvedMaxWidth, - resolvedMinHeight, - resolvedMaxHeight - ) - } - val placeable = measurable.measure(wrappedConstraints) - return layout(placeable.width, placeable.height) { - placeable.place(0, 0) - } - } - - override fun IntrinsicMeasureScope.minIntrinsicWidth( - measurable: IntrinsicMeasurable, - height: Int - ) = measurable.minIntrinsicWidth(height).let { - val constraints = targetConstraints - it.coerceIn(constraints.minWidth, constraints.maxWidth) - } - - override fun IntrinsicMeasureScope.maxIntrinsicWidth( - measurable: IntrinsicMeasurable, - height: Int - ) = measurable.maxIntrinsicWidth(height).let { - val constraints = targetConstraints - it.coerceIn(constraints.minWidth, constraints.maxWidth) - } - - override fun IntrinsicMeasureScope.minIntrinsicHeight( - measurable: IntrinsicMeasurable, - width: Int - ) = measurable.minIntrinsicHeight(width).let { - val constraints = targetConstraints - it.coerceIn(constraints.minHeight, constraints.maxHeight) - } - - override fun IntrinsicMeasureScope.maxIntrinsicHeight( - measurable: IntrinsicMeasurable, - width: Int - ) = measurable.maxIntrinsicHeight(width).let { - val constraints = targetConstraints - it.coerceIn(constraints.minHeight, constraints.maxHeight) - } -} diff --git a/Owl/app/src/main/java/com/example/owl/ui/utils/NetworkImage.kt b/Owl/app/src/main/java/com/example/owl/ui/utils/NetworkImage.kt index 5d9ee0ef2e..44b47f62cc 100644 --- a/Owl/app/src/main/java/com/example/owl/ui/utils/NetworkImage.kt +++ b/Owl/app/src/main/java/com/example/owl/ui/utils/NetworkImage.kt @@ -23,7 +23,6 @@ import androidx.compose.material.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.Providers import androidx.compose.runtime.remember -import androidx.compose.runtime.staticAmbientOf import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale @@ -34,6 +33,7 @@ import coil.intercept.Interceptor import coil.request.ImageResult import coil.size.PixelSize import com.example.owl.ui.theme.compositedOnSurface +import dev.chrisbanes.accompanist.coil.AmbientImageLoader import dev.chrisbanes.accompanist.coil.CoilImage import okhttp3.HttpUrl @@ -51,7 +51,6 @@ fun NetworkImage( data = url, modifier = modifier, contentScale = contentScale, - imageLoader = AmbientImageLoader.current, loading = { if (placeholderColor != null) { Spacer( @@ -64,10 +63,6 @@ fun NetworkImage( ) } -private val AmbientImageLoader = staticAmbientOf { - error("No loader provided") -} - @Composable fun ProvideImageLoader(content: @Composable () -> Unit) { val context = ContextAmbient.current diff --git a/Owl/app/src/main/java/com/example/owl/ui/utils/Scrim.kt b/Owl/app/src/main/java/com/example/owl/ui/utils/Scrim.kt index adf4e1c0cb..1c8f992eec 100644 --- a/Owl/app/src/main/java/com/example/owl/ui/utils/Scrim.kt +++ b/Owl/app/src/main/java/com/example/owl/ui/utils/Scrim.kt @@ -31,7 +31,8 @@ fun Modifier.scrim(colors: List): Modifier = drawWithCache { startY = 0f, endY = size.height ) - onDraw { + onDrawWithContent { + drawContent() drawRect(brush = gradient) } } diff --git a/Owl/build.gradle b/Owl/build.gradle index 48c4a24d02..f8a96a94a9 100644 --- a/Owl/build.gradle +++ b/Owl/build.gradle @@ -52,7 +52,7 @@ subprojects { allWarningsAsErrors = true // Opt-in to experimental compose APIs freeCompilerArgs += '-Xopt-in=kotlin.RequiresOptIn' - freeCompilerArgs += "-Xallow-jvm-ir-dependencies" + freeCompilerArgs += '-Xallow-jvm-ir-dependencies' } } @@ -66,4 +66,4 @@ subprojects { licenseHeaderFile rootProject.file('spotless/copyright.kt') } } -} \ No newline at end of file +} diff --git a/Owl/buildSrc/src/main/java/com/example/owl/buildsrc/Dependencies.kt b/Owl/buildSrc/src/main/java/com/example/owl/buildsrc/Dependencies.kt index bf9d478d07..8706369885 100644 --- a/Owl/buildSrc/src/main/java/com/example/owl/buildsrc/Dependencies.kt +++ b/Owl/buildSrc/src/main/java/com/example/owl/buildsrc/Dependencies.kt @@ -21,12 +21,13 @@ object Versions { } object Libs { - const val androidGradlePlugin = "com.android.tools.build:gradle:4.2.0-alpha15" + const val androidGradlePlugin = "com.android.tools.build:gradle:4.2.0-alpha16" const val junit = "junit:junit:4.13" object Accompanist { - private const val version = "0.3.2" + private const val version = "0.3.3.1" const val coil = "dev.chrisbanes.accompanist:accompanist-coil:$version" + const val insets = "dev.chrisbanes.accompanist:accompanist-insets:$version" } object Kotlin { @@ -37,7 +38,7 @@ object Libs { } object Coroutines { - private const val version = "1.3.9" + private const val version = "1.4.0" const val core = "org.jetbrains.kotlinx:kotlinx-coroutines-core:$version" const val android = "org.jetbrains.kotlinx:kotlinx-coroutines-android:$version" const val test = "org.jetbrains.kotlinx:kotlinx-coroutines-test:$version" @@ -48,7 +49,7 @@ object Libs { object Compose { const val snapshot = "" - const val version = "1.0.0-alpha06" + const val version = "1.0.0-alpha07" const val runtime = "androidx.compose.runtime:runtime:$version" const val foundation = "androidx.compose.foundation:foundation:$version" diff --git a/Rally/app/build.gradle b/Rally/app/build.gradle index c3ccf14aea..c69a252413 100644 --- a/Rally/app/build.gradle +++ b/Rally/app/build.gradle @@ -81,6 +81,11 @@ android { kotlinCompilerExtensionVersion Libs.AndroidX.Compose.version } + packagingOptions { + exclude "META-INF/licenses/**" + exclude "META-INF/AL2.0" + exclude "META-INF/LGPL2.1" + } } dependencies { diff --git a/Rally/app/src/androidTest/java/com/example/compose/rally/AnimatingCircleTests.kt b/Rally/app/src/androidTest/java/com/example/compose/rally/AnimatingCircleTests.kt index 9d2d00bac4..ca6784c2fb 100644 --- a/Rally/app/src/androidTest/java/com/example/compose/rally/AnimatingCircleTests.kt +++ b/Rally/app/src/androidTest/java/com/example/compose/rally/AnimatingCircleTests.kt @@ -21,10 +21,10 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.preferredSize import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.test.onRoot import androidx.compose.ui.unit.dp import androidx.test.filters.SdkSuppress import androidx.ui.test.createComposeRule -import androidx.ui.test.onRoot import com.example.compose.rally.ui.components.AnimatedCircle import com.example.compose.rally.ui.theme.RallyTheme import org.junit.Rule diff --git a/Rally/app/src/androidTest/java/com/example/compose/rally/ScreenshotComparator.kt b/Rally/app/src/androidTest/java/com/example/compose/rally/ScreenshotComparator.kt index 345d9fedb7..3ea0642ed7 100644 --- a/Rally/app/src/androidTest/java/com/example/compose/rally/ScreenshotComparator.kt +++ b/Rally/app/src/androidTest/java/com/example/compose/rally/ScreenshotComparator.kt @@ -20,9 +20,10 @@ import android.graphics.Bitmap import android.graphics.BitmapFactory import android.os.Build import androidx.annotation.RequiresApi +import androidx.compose.ui.graphics.asAndroidBitmap +import androidx.compose.ui.test.SemanticsNodeInteraction +import androidx.compose.ui.test.captureToImage import androidx.test.platform.app.InstrumentationRegistry -import androidx.ui.test.SemanticsNodeInteraction -import androidx.ui.test.captureToBitmap import java.io.FileOutputStream /** @@ -39,7 +40,7 @@ fun assertScreenshotMatchesGolden( goldenName: String, node: SemanticsNodeInteraction ) { - val bitmap = node.captureToBitmap() + val bitmap = node.captureToImage().asAndroidBitmap() // Save screenshot to file for debugging saveScreenshot(goldenName + System.currentTimeMillis().toString(), bitmap) diff --git a/Rally/app/src/main/java/com/example/compose/rally/ui/components/CommonUi.kt b/Rally/app/src/main/java/com/example/compose/rally/ui/components/CommonUi.kt index ea2b6f054c..3bfd432e32 100644 --- a/Rally/app/src/main/java/com/example/compose/rally/ui/components/CommonUi.kt +++ b/Rally/app/src/main/java/com/example/compose/rally/ui/components/CommonUi.kt @@ -16,7 +16,6 @@ package com.example.compose.rally.ui.components -import androidx.compose.foundation.Text import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -26,14 +25,16 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.preferredHeight import androidx.compose.foundation.layout.preferredSize import androidx.compose.foundation.layout.preferredWidth -import androidx.compose.material.AmbientEmphasisLevels +import androidx.compose.material.AmbientContentAlpha +import androidx.compose.material.ContentAlpha import androidx.compose.material.Divider import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme -import androidx.compose.material.ProvideEmphasis +import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ChevronRight import androidx.compose.runtime.Composable +import androidx.compose.runtime.Providers import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -89,10 +90,8 @@ private fun BaseRow( ) Spacer(Modifier.preferredWidth(12.dp)) Column(Modifier) { - ProvideEmphasis(emphasis = AmbientEmphasisLevels.current.high) { - Text(text = title, style = typography.body1) - } - ProvideEmphasis(emphasis = AmbientEmphasisLevels.current.medium) { + Text(text = title, style = typography.body1) + Providers(AmbientContentAlpha provides ContentAlpha.medium) { Text(text = subtitle, style = typography.subtitle1) } } @@ -115,7 +114,7 @@ private fun BaseRow( } Spacer(Modifier.preferredWidth(16.dp)) - ProvideEmphasis(emphasis = AmbientEmphasisLevels.current.medium) { + Providers(AmbientContentAlpha provides ContentAlpha.medium) { Icon( asset = Icons.Filled.ChevronRight, modifier = Modifier diff --git a/Rally/app/src/main/java/com/example/compose/rally/ui/components/DetailsScreen.kt b/Rally/app/src/main/java/com/example/compose/rally/ui/components/DetailsScreen.kt index 2c26e7bc7e..7b2cdec146 100644 --- a/Rally/app/src/main/java/com/example/compose/rally/ui/components/DetailsScreen.kt +++ b/Rally/app/src/main/java/com/example/compose/rally/ui/components/DetailsScreen.kt @@ -17,7 +17,6 @@ package com.example.compose.rally.ui.components import androidx.compose.foundation.ScrollableColumn -import androidx.compose.foundation.Text import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -26,6 +25,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.preferredHeight import androidx.compose.material.Card import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier diff --git a/Rally/app/src/main/java/com/example/compose/rally/ui/components/RallyAlertDialog.kt b/Rally/app/src/main/java/com/example/compose/rally/ui/components/RallyAlertDialog.kt index 1e914c64ce..559a979f73 100644 --- a/Rally/app/src/main/java/com/example/compose/rally/ui/components/RallyAlertDialog.kt +++ b/Rally/app/src/main/java/com/example/compose/rally/ui/components/RallyAlertDialog.kt @@ -16,7 +16,6 @@ package com.example.compose.rally.ui.components -import androidx.compose.foundation.Text import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth @@ -24,6 +23,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material.AlertDialog import androidx.compose.material.Divider import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text import androidx.compose.material.TextButton import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier diff --git a/Rally/app/src/main/java/com/example/compose/rally/ui/components/TopAppBar.kt b/Rally/app/src/main/java/com/example/compose/rally/ui/components/TopAppBar.kt index 532fb188f7..2023271d6f 100644 --- a/Rally/app/src/main/java/com/example/compose/rally/ui/components/TopAppBar.kt +++ b/Rally/app/src/main/java/com/example/compose/rally/ui/components/TopAppBar.kt @@ -20,7 +20,6 @@ import androidx.compose.animation.animate import androidx.compose.animation.animateContentSize import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.tween -import androidx.compose.foundation.Text import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth @@ -31,6 +30,7 @@ import androidx.compose.foundation.selection.selectable import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface +import androidx.compose.material.Text import androidx.compose.material.ripple.RippleIndication import androidx.compose.runtime.Composable import androidx.compose.runtime.remember diff --git a/Rally/app/src/main/java/com/example/compose/rally/ui/overview/OverviewScreen.kt b/Rally/app/src/main/java/com/example/compose/rally/ui/overview/OverviewScreen.kt index cb11b7119f..de60bb0839 100644 --- a/Rally/app/src/main/java/com/example/compose/rally/ui/overview/OverviewScreen.kt +++ b/Rally/app/src/main/java/com/example/compose/rally/ui/overview/OverviewScreen.kt @@ -17,7 +17,6 @@ package com.example.compose.rally.ui.overview import androidx.compose.foundation.ScrollableColumn -import androidx.compose.foundation.Text import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -27,12 +26,11 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.preferredHeight -import androidx.compose.material.AmbientEmphasisLevels import androidx.compose.material.Card import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme -import androidx.compose.material.ProvideEmphasis +import androidx.compose.material.Text import androidx.compose.material.TextButton import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Sort @@ -102,13 +100,11 @@ private fun AlertHeader(onClickSeeAll: () -> Unit) { modifier = Modifier.padding(RallyDefaultPadding).fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween ) { - ProvideEmphasis(emphasis = AmbientEmphasisLevels.current.high) { - Text( - text = "Alerts", - style = MaterialTheme.typography.subtitle2, - modifier = Modifier.align(Alignment.CenterVertically) - ) - } + Text( + text = "Alerts", + style = MaterialTheme.typography.subtitle2, + modifier = Modifier.align(Alignment.CenterVertically) + ) TextButton( onClick = onClickSeeAll, contentPadding = PaddingValues(0.dp), @@ -128,18 +124,16 @@ private fun AlertItem(message: String) { modifier = Modifier.padding(RallyDefaultPadding), horizontalArrangement = Arrangement.SpaceBetween ) { - ProvideEmphasis(emphasis = AmbientEmphasisLevels.current.high) { - Text( - style = MaterialTheme.typography.body2, - modifier = Modifier.weight(1f), - text = message - ) - IconButton( - onClick = {}, - modifier = Modifier.align(Alignment.Top) - ) { - Icon(Icons.Filled.Sort) - } + Text( + style = MaterialTheme.typography.body2, + modifier = Modifier.weight(1f), + text = message + ) + IconButton( + onClick = {}, + modifier = Modifier.align(Alignment.Top) + ) { + Icon(Icons.Filled.Sort) } } } diff --git a/Rally/buildSrc/src/main/java/com/example/compose/rally/buildsrc/dependencies.kt b/Rally/buildSrc/src/main/java/com/example/compose/rally/buildsrc/dependencies.kt index 39e7f684cb..0aeae96119 100644 --- a/Rally/buildSrc/src/main/java/com/example/compose/rally/buildsrc/dependencies.kt +++ b/Rally/buildSrc/src/main/java/com/example/compose/rally/buildsrc/dependencies.kt @@ -21,7 +21,7 @@ object Versions { } object Libs { - const val androidGradlePlugin = "com.android.tools.build:gradle:4.2.0-alpha15" + const val androidGradlePlugin = "com.android.tools.build:gradle:4.2.0-alpha16" const val jdkDesugar = "com.android.tools:desugar_jdk_libs:1.0.9" const val junit = "junit:junit:4.13" @@ -48,7 +48,7 @@ object Libs { object Compose { const val snapshot = "" - const val version = "1.0.0-alpha06" + const val version = "1.0.0-alpha07" const val core = "androidx.compose.ui:ui:$version" const val foundation = "androidx.compose.foundation:foundation:$version"