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 5c91a3a146..7b6e11b6fa 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,7 +21,7 @@ object Versions { } object Libs { - const val androidGradlePlugin = "com.android.tools.build:gradle:7.0.0-alpha03" + const val androidGradlePlugin = "com.android.tools.build:gradle:7.0.0-alpha04" const val ktLint = "com.pinterest:ktlint:${Versions.ktLint}" object GoogleMaps { @@ -30,7 +30,7 @@ object Libs { } object Accompanist { - private const val version = "0.4.1" + private const val version = "0.4.2" const val coil = "dev.chrisbanes.accompanist:accompanist-coil:$version" } @@ -50,7 +50,7 @@ object Libs { object AndroidX { object Compose { const val snapshot = "" - private const val version = "1.0.0-alpha09" + private const val version = "1.0.0-alpha10" 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 689f6ee47b..5b5a909c2b 100644 --- a/JetNews/app/build.gradle +++ b/JetNews/app/build.gradle @@ -93,8 +93,8 @@ dependencies { implementation 'androidx.activity:activity-ktx:1.1.0' implementation 'androidx.core:core-ktx:1.5.0-alpha05' - implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.3.0-beta01" - implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.0-beta01" + implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.3.0-rc01" + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.0-rc01" androidTestImplementation 'junit:junit:4.13.1' androidTestImplementation 'androidx.test:rules:1.3.0' diff --git a/JetNews/app/src/main/java/com/example/jetnews/ui/SwipeToRefresh.kt b/JetNews/app/src/main/java/com/example/jetnews/ui/SwipeToRefresh.kt index 3bf09254ed..5e04ec06fa 100644 --- a/JetNews/app/src/main/java/com/example/jetnews/ui/SwipeToRefresh.kt +++ b/JetNews/app/src/main/java/com/example/jetnews/ui/SwipeToRefresh.kt @@ -117,7 +117,7 @@ private val SwipeableState.PreUpPostDownNestedScrollConnection: NestedScr } override fun onPreFling(available: Velocity): Velocity { - val toFling = available.pixelsPerSecond.toFloat() + val toFling = Offset(available.x, available.y).toFloat() return if (toFling < 0) { performFling(velocity = toFling) {} // since we go to the anchor with tween settling, consume all for the best UX @@ -132,7 +132,7 @@ private val SwipeableState.PreUpPostDownNestedScrollConnection: NestedScr available: Velocity, onFinished: (Velocity) -> Unit ) { - performFling(velocity = available.pixelsPerSecond.toFloat()) { + performFling(velocity = Offset(available.x, available.y).toFloat()) { // since we go to the anchor with tween settling, consume all for the best UX onFinished.invoke(available) } 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 337e0563f7..8f03dfe466 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 @@ -47,6 +47,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.AmbientContext @@ -134,17 +135,23 @@ fun HomeScreen( if (posts.hasError) { val errorMessage = stringResource(id = R.string.load_error) val retryMessage = stringResource(id = R.string.retry) + + // If onRefreshPosts or onErrorDismiss change while the LaunchedEffect is running, + // don't restart the effect and use the latest lambda values. + val onRefreshPostsState by rememberUpdatedState(onRefreshPosts) + val onErrorDismissState by rememberUpdatedState(onErrorDismiss) + // 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). - LaunchedEffect(posts.hasError) { + // automatically dismiss. This coroutine will cancel whenever posts.hasError is false + // (thanks to the surrounding if statement) or if scaffoldState changes. + LaunchedEffect(scaffoldState) { val snackbarResult = scaffoldState.snackbarHostState.showSnackbar( message = errorMessage, actionLabel = retryMessage ) when (snackbarResult) { - SnackbarResult.ActionPerformed -> onRefreshPosts() - SnackbarResult.Dismissed -> onErrorDismiss() + SnackbarResult.ActionPerformed -> onRefreshPostsState() + SnackbarResult.Dismissed -> onErrorDismissState() } } } diff --git a/JetNews/build.gradle b/JetNews/build.gradle index 234ed0ab6d..39246c6c94 100644 --- a/JetNews/build.gradle +++ b/JetNews/build.gradle @@ -16,7 +16,7 @@ buildscript { ext.kotlin_version = '1.4.21' - ext.compose_version = '1.0.0-alpha09' + ext.compose_version = '1.0.0-alpha10' ext.coroutines_version = '1.4.2' repositories { @@ -25,7 +25,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:7.0.0-alpha02' + classpath 'com.android.tools.build:gradle:7.0.0-alpha04' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/JetNews/gradle/wrapper/gradle-wrapper.properties b/JetNews/gradle/wrapper/gradle-wrapper.properties index 0d9d6792de..cfb40e4707 100644 --- a/JetNews/gradle/wrapper/gradle-wrapper.properties +++ b/JetNews/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Tue Oct 27 16:21:59 PDT 2020 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip +distributionUrl=https://services.gradle.org/distributions/gradle-6.8-rc-1-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/Jetcaster/app/src/main/AndroidManifest.xml b/Jetcaster/app/src/main/AndroidManifest.xml index 2cf77d717f..e3fa0b2ce0 100644 --- a/Jetcaster/app/src/main/AndroidManifest.xml +++ b/Jetcaster/app/src/main/AndroidManifest.xml @@ -17,6 +17,8 @@ + + diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/data/PodcastsRepository.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/data/PodcastsRepository.kt index b07a55387d..84fef6092b 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/data/PodcastsRepository.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/data/PodcastsRepository.kt @@ -16,6 +16,7 @@ package com.example.jetcaster.data +import android.util.Log import com.example.jetcaster.data.room.TransactionRunner import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope @@ -43,22 +44,26 @@ class PodcastsRepository( refreshingJob?.join() } else if (force || podcastStore.isEmpty()) { refreshingJob = scope.launch { - // Now fetch the podcasts, and add each to each store - podcastsFetcher(SampleFeeds).collect { (podcast, episodes, categories) -> - transactionRunner { - podcastStore.addPodcast(podcast) - episodeStore.addEpisodes(episodes) + try { + // Now fetch the podcasts, and add each to each store + podcastsFetcher(SampleFeeds).collect { (podcast, episodes, categories) -> + transactionRunner { + podcastStore.addPodcast(podcast) + episodeStore.addEpisodes(episodes) - categories.forEach { category -> - // First insert the category - val categoryId = categoryStore.addCategory(category) - // Now we can add the podcast to the category - categoryStore.addPodcastToCategory( - podcastUri = podcast.uri, - categoryId = categoryId - ) + categories.forEach { category -> + // First insert the category + val categoryId = categoryStore.addCategory(category) + // Now we can add the podcast to the category + categoryStore.addPodcastToCategory( + podcastUri = podcast.uri, + categoryId = categoryId + ) + } } } + } catch (e: Throwable) { + Log.d("PodcastsRepository", "podcastsFetcher(SampleFeeds).collect error: $e") } } } diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/JetcasterApp.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/JetcasterApp.kt index 2871bd461b..e7dacf404e 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/ui/JetcasterApp.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/ui/JetcasterApp.kt @@ -16,11 +16,52 @@ package com.example.jetcaster.ui +import android.content.Context +import android.net.ConnectivityManager +import androidx.compose.material.AlertDialog +import androidx.compose.material.Text +import androidx.compose.material.TextButton import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.platform.AmbientContext +import androidx.compose.ui.res.stringResource +import com.example.jetcaster.R import com.example.jetcaster.ui.home.Home @Composable fun JetcasterApp() { + val context = AmbientContext.current + var isOnline by remember { mutableStateOf(checkIfOnline(context)) } + // TODO: add some navigation - Home() + if (isOnline) { + Home() + } else { + OfflineDialog { isOnline = checkIfOnline(context) } + } +} + +// TODO: Use a better way to check internet connection +@Suppress("DEPRECATION") +private fun checkIfOnline(context: Context): Boolean { + val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + val activeNetwork = cm.activeNetworkInfo + return activeNetwork?.isConnectedOrConnecting == true +} + +@Composable +fun OfflineDialog(onRetry: () -> Unit) { + AlertDialog( + onDismissRequest = {}, + title = { Text(text = stringResource(R.string.connection_error_title)) }, + text = { Text(text = stringResource(R.string.connection_error_message)) }, + confirmButton = { + TextButton(onClick = onRetry) { + Text(stringResource(R.string.retry_label)) + } + } + ) } 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 7f87c7cea7..5f861d497d 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 @@ -16,7 +16,8 @@ package com.example.jetcaster.util -import androidx.compose.animation.animate +import androidx.compose.animation.animateAsState +import androidx.compose.animation.core.animateAsState import androidx.compose.foundation.background import androidx.compose.foundation.layout.padding import androidx.compose.material.AmbientContentColor @@ -49,24 +50,24 @@ fun ToggleFollowPodcastIconButton( isFollowed -> Icons.Default.Check else -> Icons.Default.Add }, - tint = animate( + tint = animateAsState( when { isFollowed -> AmbientContentColor.current else -> Color.Black.copy(alpha = ContentAlpha.high) } - ), + ).value, modifier = Modifier .shadow( - elevation = animate(if (isFollowed) 0.dp else 1.dp), + elevation = animateAsState(if (isFollowed) 0.dp else 1.dp).value, shape = MaterialTheme.shapes.small ) .background( - color = animate( + color = animateAsState( when { isFollowed -> MaterialTheme.colors.surface.copy(0.38f) else -> Color.White } - ), + ).value, shape = MaterialTheme.shapes.small ) .padding(4.dp) diff --git a/Jetcaster/app/src/main/java/com/example/jetcaster/util/DynamicTheming.kt b/Jetcaster/app/src/main/java/com/example/jetcaster/util/DynamicTheming.kt index 956498798f..ee5fc3a44d 100644 --- a/Jetcaster/app/src/main/java/com/example/jetcaster/util/DynamicTheming.kt +++ b/Jetcaster/app/src/main/java/com/example/jetcaster/util/DynamicTheming.kt @@ -18,7 +18,7 @@ package com.example.jetcaster.util import android.content.Context import androidx.collection.LruCache -import androidx.compose.animation.animate +import androidx.compose.animation.animateAsState import androidx.compose.animation.core.Spring import androidx.compose.animation.core.spring import androidx.compose.material.MaterialTheme @@ -61,8 +61,14 @@ fun DynamicThemePrimaryColorsFromImage( content: @Composable () -> Unit ) { val colors = MaterialTheme.colors.copy( - primary = animate(dominantColorState.color, spring(stiffness = Spring.StiffnessLow)), - onPrimary = animate(dominantColorState.onColor, spring(stiffness = Spring.StiffnessLow)) + primary = animateAsState( + dominantColorState.color, + spring(stiffness = Spring.StiffnessLow) + ).value, + onPrimary = animateAsState( + dominantColorState.onColor, + spring(stiffness = Spring.StiffnessLow) + ).value ) MaterialTheme(colors = colors, content = content) } diff --git a/Jetcaster/app/src/main/res/values/strings.xml b/Jetcaster/app/src/main/res/values/strings.xml index 686536138b..fe66f85a95 100644 --- a/Jetcaster/app/src/main/res/values/strings.xml +++ b/Jetcaster/app/src/main/res/values/strings.xml @@ -16,6 +16,11 @@ --> Jetcaster + + Connection error + Unable to fetch podcasts feeds.\nCheck your internet connection and try again. + Retry + Your podcasts Latest episodes 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 15b4140fa0..e84f7b2568 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 @@ -21,7 +21,7 @@ object Versions { } object Libs { - const val androidGradlePlugin = "com.android.tools.build:gradle:7.0.0-alpha03" + const val androidGradlePlugin = "com.android.tools.build:gradle:7.0.0-alpha04" const val jdkDesugar = "com.android.tools:desugar_jdk_libs:1.0.9" const val junit = "junit:junit:4.13" @@ -29,7 +29,7 @@ object Libs { const val material = "com.google.android.material:material:1.1.0" object Accompanist { - private const val version = "0.4.1" + private const val version = "0.4.2" const val coil = "dev.chrisbanes.accompanist:accompanist-coil:$version" const val insets = "dev.chrisbanes.accompanist:accompanist-insets:$version" } @@ -58,12 +58,11 @@ object Libs { 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" - const val coreKtx = "androidx.core:core-ktx:1.5.0-alpha04" + const val coreKtx = "androidx.core:core-ktx:1.5.0-beta01" object Compose { private const val snapshot = "" - private const val version = "1.0.0-alpha09" + private const val version = "1.0.0-alpha10" @get:JvmStatic val snapshotUrl: String 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 50d93fc805..db5df6971c 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 @@ -66,7 +66,7 @@ class ConversationTest { // Launch the conversation screen composeTestRule.setContent { Providers( - AmbientBackPressedDispatcher provides newActivity, + AmbientBackPressedDispatcher provides newActivity.onBackPressedDispatcher, AmbientWindowInsets provides windowInsets ) { JetchatTheme(isDarkTheme = themeIsDark.collectAsState(false).value) { 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 b1abc7fabd..6e1267c80e 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 @@ -65,7 +65,7 @@ class NavigationTest { // Start the app composeTestRule.setContent { Providers( - AmbientBackPressedDispatcher provides activity, + AmbientBackPressedDispatcher provides activity.onBackPressedDispatcher, AmbientWindowInsets provides windowInsets, ) { JetchatTheme { 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 509900e93d..771372d709 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 @@ -65,7 +65,7 @@ class UserInputTest { // Launch the conversation screen composeTestRule.setContent { Providers( - AmbientBackPressedDispatcher provides activity, + AmbientBackPressedDispatcher provides activity.onBackPressedDispatcher, AmbientWindowInsets provides windowInsets, ) { JetchatTheme { diff --git a/Jetchat/app/src/main/java/com/example/compose/jetchat/NavActivity.kt b/Jetchat/app/src/main/java/com/example/compose/jetchat/NavActivity.kt index 8a0cb0ff2e..b34eaf7fc1 100644 --- a/Jetchat/app/src/main/java/com/example/compose/jetchat/NavActivity.kt +++ b/Jetchat/app/src/main/java/com/example/compose/jetchat/NavActivity.kt @@ -29,7 +29,7 @@ import androidx.core.view.WindowCompat import androidx.navigation.findNavController import com.example.compose.jetchat.components.JetchatScaffold import com.example.compose.jetchat.conversation.AmbientBackPressedDispatcher -import com.example.compose.jetchat.conversation.backPressHandler +import com.example.compose.jetchat.conversation.BackPressHandler import com.example.compose.jetchat.databinding.ContentMainBinding import dev.chrisbanes.accompanist.insets.ProvideWindowInsets @@ -50,7 +50,7 @@ class NavActivity : AppCompatActivity() { // Provide WindowInsets to our content. We don't want to consume them, so that // they keep being pass down the view hierarchy (since we're using fragments). ProvideWindowInsets(consumeWindowInsets = false) { - Providers(AmbientBackPressedDispatcher provides this) { + Providers(AmbientBackPressedDispatcher provides this.onBackPressedDispatcher) { val scaffoldState = rememberScaffoldState() val openDrawerEvent = viewModel.drawerShouldBeOpened.observeAsState() @@ -62,11 +62,9 @@ class NavActivity : AppCompatActivity() { } // Intercepts back navigation when the drawer is open - backPressHandler( - enabled = scaffoldState.drawerState.isOpen, - onBackPressed = { scaffoldState.drawerState.close() }, - highPriority = true - ) + if (scaffoldState.drawerState.isOpen) { + BackPressHandler { scaffoldState.drawerState.close() } + } JetchatScaffold( scaffoldState, diff --git a/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/BackHandler.kt b/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/BackHandler.kt index 5c3a5b688a..393d542c31 100644 --- a/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/BackHandler.kt +++ b/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/BackHandler.kt @@ -17,54 +17,41 @@ package com.example.compose.jetchat.conversation import androidx.activity.OnBackPressedCallback -import androidx.activity.OnBackPressedDispatcherOwner +import androidx.activity.OnBackPressedDispatcher import androidx.compose.runtime.Ambient import androidx.compose.runtime.Composable -import androidx.compose.runtime.onCommit +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.staticAmbientOf /** - * This [Composable] can be used with a [AmbientBackPressedDispatcher] to intercept a back press (if - * [enabled]). + * This [Composable] can be used with a [AmbientBackPressedDispatcher] to intercept a back press. * * @param onBackPressed (Event) What to do when back is intercepted - * @param enabled (state) When to intercept the back navigation - * @param highPriority (config) Used to make sure this is the first handler in the dispatcher * */ @Composable -fun backPressHandler( - onBackPressed: () -> Unit, - enabled: Boolean = true, - highPriority: Boolean = false -) { - val dispatcher = AmbientBackPressedDispatcher.current.onBackPressedDispatcher +fun BackPressHandler(onBackPressed: () -> Unit) { + // Safely update the current `onBack` lambda when a new one is provided + val currentOnBackPressed by rememberUpdatedState(onBackPressed) - // This callback is going to be remembered only if onBackPressed is referentially equal. - val backCallback = remember(onBackPressed) { - object : OnBackPressedCallback(enabled) { + // Remember in Composition a back callback that calls the `onBackPressed` lambda + val backCallback = remember { + object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { - onBackPressed() + currentOnBackPressed() } } } - // Using onCommit guarantees that failed transactions don't incorrectly toggle the - // remembered callback. - onCommit(enabled, highPriority) { - if (enabled && highPriority) { - // Since the Navigation Component is also intercepting the back event, make sure - // that this is the first callback in the dispatcher. - backCallback.remove() - dispatcher.addCallback(backCallback) - } - backCallback.isEnabled = enabled - } + val backDispatcher = AmbientBackPressedDispatcher.current - onCommit(dispatcher, onBackPressed) { - // Whenever there's a new dispatcher set up the callback - dispatcher.addCallback(backCallback) + // Whenever there's a new dispatcher set up the callback + DisposableEffect(backDispatcher) { + backDispatcher.addCallback(backCallback) + // When the effect leaves the Composition, or there's a new dispatcher, remove the callback onDispose { backCallback.remove() } @@ -72,13 +59,13 @@ fun backPressHandler( } /** - * This [Ambient] is used to provide an [OnBackPressedDispatcherOwner]: + * This [Ambient] is used to provide an [OnBackPressedDispatcher]: * * ``` - * Providers(AmbientBackPressedDispatcher provides requireActivity()) { } + * Providers(AmbientBackPressedDispatcher provides requireActivity().onBackPressedDispatcher) { } * ``` * - * and setting up the callbacks with [backPressHandler]. + * and setting up the callbacks with [BackPressHandler]. */ val AmbientBackPressedDispatcher = - staticAmbientOf { error("Ambient used without Provider") } + staticAmbientOf { error("No Back Dispatcher provided") } diff --git a/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/ConversationFragment.kt b/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/ConversationFragment.kt index a217c0b6b9..af9a7ba557 100644 --- a/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/ConversationFragment.kt +++ b/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/ConversationFragment.kt @@ -59,7 +59,7 @@ class ConversationFragment : Fragment() { setContent { Providers( - AmbientBackPressedDispatcher provides requireActivity(), + AmbientBackPressedDispatcher provides requireActivity().onBackPressedDispatcher, AmbientWindowInsets provides windowInsets, ) { JetchatTheme { 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 efef2a25c3..6b4f3ba4d1 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 @@ -69,11 +69,11 @@ import androidx.compose.runtime.savedinstancestate.savedInstanceState import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.focus import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusState +import androidx.compose.ui.focus.focusModifier +import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.onFocusChanged -import androidx.compose.ui.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.layout.FirstBaseline @@ -125,10 +125,11 @@ fun UserInput( ) { var currentInputSelector by savedInstanceState { InputSelector.NONE } val dismissKeyboard = { currentInputSelector = InputSelector.NONE } - backPressHandler( - enabled = currentInputSelector != InputSelector.NONE, - onBackPressed = dismissKeyboard - ) + + // Intercept back navigation if there's a InputSelector visible + if (currentInputSelector != InputSelector.NONE) { + BackPressHandler(onBackPressed = dismissKeyboard) + } var textState by remember { mutableStateOf(TextFieldValue()) } @@ -450,7 +451,8 @@ fun EmojiSelector( Column( modifier = Modifier .focusRequester(focusRequester) // Requests focus when the Emoji selector is displayed - .focus() // Make the emoji selector focusable so it can steal focus from TextField + // Make the emoji selector focusable so it can steal focus from TextField + .focusModifier() .semantics { contentDescription = a11yLabel } ) { Row(modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp)) { 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 e77e3c1565..a8913d14a3 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:7.0.0-alpha02" + const val androidGradlePlugin = "com.android.tools.build:gradle:7.0.0-alpha04" const val jdkDesugar = "com.android.tools:desugar_jdk_libs:1.0.9" const val junit = "junit:junit:4.13" @@ -43,17 +43,17 @@ object Libs { } object Accompanist { - private const val version = "0.4.1" + private const val version = "0.4.2" const val insets = "dev.chrisbanes.accompanist:accompanist-insets:$version" } object AndroidX { const val appcompat = "androidx.appcompat:appcompat:1.2.0-rc01" - const val coreKtx = "androidx.core:core-ktx:1.5.0-alpha04" + const val coreKtx = "androidx.core:core-ktx:1.5.0-beta01" object Compose { const val snapshot = "" - const val version = "1.0.0-alpha09" + const val version = "1.0.0-alpha10" const val foundation = "androidx.compose.foundation:foundation:$version" const val layout = "androidx.compose.foundation:foundation-layout:$version" 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 af37fa96e5..789fecb572 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 @@ -16,27 +16,31 @@ package com.example.jetsnack.ui.components +import androidx.compose.foundation.AmbientIndication import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.InteractionState import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.indication import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.defaultMinSizeConstraints -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.ButtonDefaults import androidx.compose.material.MaterialTheme import androidx.compose.material.ProvideTextStyle import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.semantics.Role import com.example.jetsnack.ui.theme.JetsnackTheme @Composable @@ -44,6 +48,7 @@ fun JetsnackButton( onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, + interactionState: InteractionState = remember { InteractionState() }, shape: Shape = ButtonShape, border: BorderStroke? = null, backgroundGradient: List = JetsnackTheme.colors.interactivePrimary, @@ -68,6 +73,9 @@ fun JetsnackButton( .clickable( onClick = onClick, enabled = enabled, + role = Role.Button, + interactionState = interactionState, + indication = null ) ) { ProvideTextStyle( @@ -79,7 +87,7 @@ fun JetsnackButton( minWidth = ButtonDefaults.MinWidth, minHeight = ButtonDefaults.MinHeight ) - .fillMaxWidth() + .indication(interactionState, AmbientIndication.current()) .padding(contentPadding), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically, 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 b6bc6c3889..6cf71bbc3a 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 @@ -16,7 +16,7 @@ package com.example.jetsnack.ui.components -import androidx.compose.animation.animate +import androidx.compose.animation.animateAsState import androidx.compose.foundation.ScrollableRow import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Spacer @@ -33,6 +33,7 @@ import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.FilterList import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Shape @@ -75,18 +76,21 @@ fun FilterChip( shape: Shape = MaterialTheme.shapes.small ) { val (selected, setSelected) = filter.enabled - val backgroundColor = - animate(if (selected) JetsnackTheme.colors.brand else JetsnackTheme.colors.uiBackground) + val backgroundColor by animateAsState( + if (selected) JetsnackTheme.colors.brand else JetsnackTheme.colors.uiBackground + ) val border = Modifier.fadeInDiagonalGradientBorder( showBorder = !selected, colors = JetsnackTheme.colors.interactiveSecondary, shape = shape ) - val textColor = animate( + val textColor by animateAsState( if (selected) JetsnackTheme.colors.textInteractive else JetsnackTheme.colors.textSecondary ) JetsnackSurface( - modifier = modifier.preferredHeight(28.dp).then(border), + modifier = modifier + .preferredHeight(28.dp) + .then(border), color = backgroundColor, contentColor = textColor, shape = shape, 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 cf5475e886..a44e362931 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 @@ -16,7 +16,7 @@ package com.example.jetsnack.ui.components -import androidx.compose.animation.animate +import androidx.compose.animation.animateAsState import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.ui.Modifier @@ -71,7 +71,7 @@ fun Modifier.fadeInDiagonalGradientBorder( shape: Shape ) = composed { val animatedColors = List(colors.size) { i -> - animate(if (showBorder) colors[i] else colors[i].copy(alpha = 0f)) + animateAsState(if (showBorder) colors[i] else colors[i].copy(alpha = 0f)).value } diagonalGradientBorder( colors = animatedColors, 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 4efdb91a72..9cd6109de4 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 @@ -20,10 +20,11 @@ import androidx.annotation.FloatRange import androidx.annotation.StringRes import androidx.compose.animation.AnimatedFloatModel import androidx.compose.animation.Crossfade -import androidx.compose.animation.animate +import androidx.compose.animation.animateAsState import androidx.compose.animation.animatedFloat import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.SpringSpec +import androidx.compose.animation.core.animateAsState import androidx.compose.foundation.border import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope @@ -42,6 +43,7 @@ import androidx.compose.material.icons.outlined.Home import androidx.compose.material.icons.outlined.Search import androidx.compose.material.icons.outlined.ShoppingCart import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.runtime.onCommit import androidx.compose.runtime.remember import androidx.compose.runtime.savedinstancestate.savedInstanceState @@ -130,7 +132,7 @@ private fun JetsnackBottomNav( ) { items.forEach { section -> val selected = section == currentSection - val tint = animate( + val tint by animateAsState( if (selected) { JetsnackTheme.colors.iconInteractive } else { @@ -264,7 +266,7 @@ fun JetsnackBottomNavigationItem( contentAlignment = Alignment.Center ) { // Animate the icon/text positions within the item based on selection - val animationProgress = animate(if (selected) 1f else 0f, animSpec) + val animationProgress by animateAsState(if (selected) 1f else 0f, animSpec) JetsnackBottomNavItemLayout( icon = icon, text = text, 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 index 2a35de730d..59d7492631 100644 --- 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 @@ -28,7 +28,8 @@ import kotlinx.coroutines.flow.StateFlow * 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()) + private val _orderLines: MutableStateFlow> = + MutableStateFlow(SnackRepo.getCart()) val orderLines: StateFlow> get() = _orderLines fun removeSnack(snackId: Long) { diff --git a/Jetsnack/build.gradle b/Jetsnack/build.gradle index b6882e7e7d..54d3cec17a 100644 --- a/Jetsnack/build.gradle +++ b/Jetsnack/build.gradle @@ -64,7 +64,7 @@ subprojects { targetExclude("$buildDir/**/*.kt") targetExclude('bin/**/*.kt') - ktlint(Versions.ktlint) + ktlint(Versions.ktlint).userData([android: "true"]) licenseHeaderFile rootProject.file('spotless/copyright.kt') } } 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 1eed138f00..b9da6f12a6 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:7.0.0-alpha03" + const val androidGradlePlugin = "com.android.tools.build:gradle:7.0.0-alpha04" const val junit = "junit:junit:4.13" object Accompanist { - private const val version = "0.4.1" + private const val version = "0.4.2" const val coil = "dev.chrisbanes.accompanist:accompanist-coil:$version" const val insets = "dev.chrisbanes.accompanist:accompanist-insets:$version" } @@ -45,11 +45,11 @@ object Libs { } object AndroidX { - const val coreKtx = "androidx.core:core-ktx:1.5.0-alpha05" + const val coreKtx = "androidx.core:core-ktx:1.5.0-beta01" object Compose { const val snapshot = "" - const val version = "1.0.0-alpha09" + const val version = "1.0.0-alpha10" const val foundation = "androidx.compose.foundation:foundation:${version}" const val layout = "androidx.compose.foundation:foundation-layout:${version}" diff --git a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/signinsignup/SignInScreen.kt b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/signinsignup/SignInScreen.kt index d71c1f464d..da4769d303 100644 --- a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/signinsignup/SignInScreen.kt +++ b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/signinsignup/SignInScreen.kt @@ -39,7 +39,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focusRequester +import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -132,7 +132,9 @@ fun SignInContent( Spacer(modifier = Modifier.preferredHeight(16.dp)) Button( onClick = { onSignInSubmitted(emailState.text, passwordState.text) }, - modifier = Modifier.fillMaxWidth().padding(vertical = 16.dp), + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 16.dp), enabled = emailState.isValid && passwordState.isValid ) { Text( diff --git a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/signinsignup/SignUpFragment.kt b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/signinsignup/SignUpFragment.kt index d6c4dfa190..c57cf4d7e6 100644 --- a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/signinsignup/SignUpFragment.kt +++ b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/signinsignup/SignUpFragment.kt @@ -40,7 +40,7 @@ class SignUpFragment : Fragment() { inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { + ): View { viewModel.navigateTo.observe(owner = viewLifecycleOwner) { navigateToEvent -> navigateToEvent.getContentIfNotHandled()?.let { navigateTo -> navigate(navigateTo, Screen.SignUp) 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 8cd1bfdc1c..218333f446 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 @@ -31,7 +31,7 @@ import androidx.compose.runtime.Providers import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focusRequester +import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.tooling.preview.Preview diff --git a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/signinsignup/WelcomeFragment.kt b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/signinsignup/WelcomeFragment.kt index 80edb3873f..56f8b40002 100644 --- a/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/signinsignup/WelcomeFragment.kt +++ b/Jetsurvey/app/src/main/java/com/example/compose/jetsurvey/signinsignup/WelcomeFragment.kt @@ -52,7 +52,9 @@ class WelcomeFragment : Fragment() { WelcomeScreen( onEvent = { event -> when (event) { - is WelcomeEvent.SignInSignUp -> viewModel.handleContinue(event.email) + is WelcomeEvent.SignInSignUp -> viewModel.handleContinue( + event.email + ) WelcomeEvent.SignInAsGuest -> viewModel.signInAsGuest() } } 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 97dadf031a..9a05930987 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 @@ -16,7 +16,7 @@ package com.example.compose.jetsurvey.signinsignup -import androidx.compose.animation.animate +import androidx.compose.animation.core.animateAsState import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize @@ -65,16 +65,17 @@ fun WelcomeScreen(onEvent: (WelcomeEvent) -> Unit) { var heightWithBranding by remember { mutableStateOf(0) } val currentOffsetHolder = remember { mutableStateOf(0f) } - currentOffsetHolder.value = animate( - if (showBranding) 0f else -brandingBottom - ) + currentOffsetHolder.value = if (showBranding) 0f else -brandingBottom + val currentOffsetHolderDp = + with(AmbientDensity.current) { currentOffsetHolder.value.toDp() } val heightDp = with(AmbientDensity.current) { heightWithBranding.toDp() } - val currentOffsetHolderDp = with(AmbientDensity.current) { currentOffsetHolder.value.toDp() } Surface(modifier = Modifier.fillMaxSize()) { + val offset by animateAsState(targetValue = currentOffsetHolderDp) Column( - modifier = Modifier.fillMaxWidth() + modifier = Modifier + .fillMaxWidth() .brandingPreferredHeight(showBranding, heightDp) - .offset(y = currentOffsetHolderDp) + .offset(y = offset) .onSizeChanged { if (showBranding) { heightWithBranding = it.height @@ -82,16 +83,21 @@ fun WelcomeScreen(onEvent: (WelcomeEvent) -> Unit) { } ) { Branding( - modifier = Modifier.fillMaxWidth().weight(1f).onGloballyPositioned { - if (brandingBottom == 0f) { - brandingBottom = it.boundsInParent.bottom + modifier = Modifier + .fillMaxWidth() + .weight(1f) + .onGloballyPositioned { + if (brandingBottom == 0f) { + brandingBottom = it.boundsInParent.bottom + } } - } ) SignInCreateAccount( onEvent = onEvent, onFocusChange = { focused -> showBranding = !focused }, - modifier = Modifier.fillMaxWidth().padding(horizontal = 20.dp) + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp) ) } } @@ -102,7 +108,8 @@ private fun Modifier.brandingPreferredHeight( heightDp: Dp ): Modifier { return if (!showBranding) { - this.wrapContentHeight(unbounded = true) + this + .wrapContentHeight(unbounded = true) .preferredHeight(heightDp) } else { this @@ -114,12 +121,18 @@ private fun Branding(modifier: Modifier = Modifier) { Column( modifier = modifier.wrapContentHeight(align = Alignment.CenterVertically) ) { - Logo(modifier = Modifier.align(Alignment.CenterHorizontally).padding(horizontal = 76.dp)) + Logo( + modifier = Modifier + .align(Alignment.CenterHorizontally) + .padding(horizontal = 76.dp) + ) Text( text = stringResource(id = R.string.app_tagline), style = MaterialTheme.typography.subtitle1, textAlign = TextAlign.Center, - modifier = Modifier.padding(top = 24.dp).fillMaxWidth() + modifier = Modifier + .padding(top = 24.dp) + .fillMaxWidth() ) } } @@ -167,7 +180,9 @@ private fun SignInCreateAccount( Email(emailState = emailState, imeAction = ImeAction.Done, onImeAction = onSubmit) Button( onClick = onSubmit, - modifier = Modifier.fillMaxWidth().padding(vertical = 28.dp) + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 28.dp) ) { Text( text = stringResource(id = R.string.user_continue), 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 fbb34f82ed..5a0f56027f 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 @@ -68,7 +68,9 @@ class SurveyFragment : Fragment() { ) is SurveyState.Result -> SurveyResultScreen( result = surveyState, - onDonePressed = { activity?.onBackPressedDispatcher?.onBackPressed() } + onDonePressed = { + activity?.onBackPressedDispatcher?.onBackPressed() + } ) } } 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 5fec4ca469..1538400920 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 @@ -85,15 +85,19 @@ fun Question( MaterialTheme.colors.onSurface.copy(alpha = 0.06f) } Row( - modifier = Modifier.fillMaxWidth().background( - color = backgroundColor, - shape = MaterialTheme.shapes.small - ) + modifier = Modifier + .fillMaxWidth() + .background( + color = backgroundColor, + shape = MaterialTheme.shapes.small + ) ) { Text( text = stringResource(id = question.questionText), style = MaterialTheme.typography.subtitle1, - modifier = Modifier.fillMaxWidth().padding(vertical = 24.dp, horizontal = 16.dp) + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 24.dp, horizontal = 16.dp) ) } Spacer(modifier = Modifier.preferredHeight(24.dp)) @@ -405,7 +409,9 @@ private fun SliderQuestion( }, valueRange = possibleAnswer.range, steps = possibleAnswer.steps, - modifier = Modifier.weight(1f).padding(horizontal = 16.dp) + modifier = Modifier + .weight(1f) + .padding(horizontal = 16.dp) ) Text( text = stringResource(id = possibleAnswer.endText), 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 ceec7c70af..f6a186fa7a 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 @@ -111,7 +111,9 @@ fun SurveyResultScreen( bottomBar = { OutlinedButton( onClick = { onDonePressed() }, - modifier = Modifier.fillMaxWidth().padding(horizontal = 20.dp, vertical = 24.dp) + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp, vertical = 24.dp) ) { Text(text = stringResource(id = R.string.done)) } @@ -182,17 +184,17 @@ private fun SurveyTopAppBar( TopAppBarTitle( questionIndex = questionIndex, totalQuestionsCount = totalQuestionsCount, - modifier = Modifier.padding(vertical = 20.dp).constrainAs(text) { - centerHorizontallyTo(parent) - } + modifier = Modifier + .padding(vertical = 20.dp) + .constrainAs(text) { centerHorizontallyTo(parent) } ) Providers(AmbientContentAlpha provides ContentAlpha.medium) { IconButton( onClick = onBackPressed, - modifier = Modifier.padding(horizontal = 12.dp).constrainAs(button) { - end.linkTo(parent.end) - } + modifier = Modifier + .padding(horizontal = 12.dp) + .constrainAs(button) { end.linkTo(parent.end) } ) { Icon(Icons.Filled.Close) } @@ -200,9 +202,12 @@ private fun SurveyTopAppBar( LinearProgressIndicator( progress = (questionIndex + 1) / totalQuestionsCount.toFloat(), - modifier = Modifier.fillMaxWidth().padding(horizontal = 20.dp).constrainAs(progress) { - bottom.linkTo(text.bottom) - }, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp) + .constrainAs(progress) { + bottom.linkTo(text.bottom) + }, backgroundColor = MaterialTheme.colors.progressIndicatorBackground ) } diff --git a/Jetsurvey/build.gradle b/Jetsurvey/build.gradle index d19b7138ae..27e545cee7 100644 --- a/Jetsurvey/build.gradle +++ b/Jetsurvey/build.gradle @@ -52,7 +52,7 @@ subprojects { targetExclude("$buildDir/**/*.kt") targetExclude('bin/**/*.kt') - ktlint(Versions.ktlint) + ktlint(Versions.ktlint).userData([android: "true"]) licenseHeaderFile rootProject.file('spotless/copyright.kt') } } 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 8241620c40..d7a2a88eaf 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,7 +21,7 @@ object Versions { } object Libs { - const val androidGradlePlugin = "com.android.tools.build:gradle:7.0.0-alpha03" + const val androidGradlePlugin = "com.android.tools.build:gradle:7.0.0-alpha04" const val jdkDesugar = "com.android.tools:desugar_jdk_libs:1.0.9" const val junit = "junit:junit:4.13" @@ -29,7 +29,7 @@ object Libs { const val material = "com.google.android.material:material:1.1.0" object Accompanist { - private const val version = "0.4.1" + private const val version = "0.4.2" const val coil = "dev.chrisbanes.accompanist:accompanist-coil:$version" } @@ -53,7 +53,7 @@ object Libs { object Compose { const val snapshot = "" - const val version = "1.0.0-alpha09" + const val version = "1.0.0-alpha10" @get:JvmStatic val snapshotUrl: String diff --git a/Owl/app/build.gradle b/Owl/app/build.gradle index aa32b793c4..aaee72fb1e 100644 --- a/Owl/app/build.gradle +++ b/Owl/app/build.gradle @@ -84,7 +84,7 @@ android { dependencies { implementation Libs.Kotlin.stdlib implementation Libs.Coroutines.android - + implementation Libs.AndroidX.coreKtx implementation Libs.AndroidX.navigation 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 dabb991164..ec5549eb63 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 @@ -16,7 +16,7 @@ package com.example.owl.ui.course -import androidx.compose.animation.animate +import androidx.compose.animation.core.animateAsState import androidx.compose.foundation.Image import androidx.compose.foundation.ScrollableColumn import androidx.compose.foundation.clickable @@ -57,6 +57,7 @@ import androidx.compose.material.rememberSwipeableState import androidx.compose.material.swipeable import androidx.compose.runtime.Composable import androidx.compose.runtime.Providers +import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -375,7 +376,7 @@ private fun Lessons( .statusBarsPadding() ) { val scroll = rememberScrollState() - val appBarElevation = animate(if (scroll.value > 0f) 4.dp else 0.dp) + val appBarElevation by animateAsState(if (scroll.value > 0f) 4.dp else 0.dp) val appBarColor = if (appBarElevation > 0.dp) surfaceColor else Color.Transparent TopAppBar( backgroundColor = appBarColor, diff --git a/Owl/app/src/main/java/com/example/owl/ui/utils/Navigation.kt b/Owl/app/src/main/java/com/example/owl/ui/utils/Navigation.kt index 0be19dbd9d..c67f609221 100644 --- a/Owl/app/src/main/java/com/example/owl/ui/utils/Navigation.kt +++ b/Owl/app/src/main/java/com/example/owl/ui/utils/Navigation.kt @@ -19,8 +19,11 @@ package com.example.owl.ui.utils import androidx.activity.OnBackPressedCallback import androidx.activity.OnBackPressedDispatcher import androidx.compose.runtime.Composable -import androidx.compose.runtime.onCommit +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.staticAmbientOf /** @@ -31,20 +34,26 @@ fun backHandler( enabled: Boolean = true, onBack: () -> Unit ) { - val backCallback = remember(onBack) { + // Safely update the current `onBack` lambda when a new one is provided + val currentOnBack by rememberUpdatedState(onBack) + // Remember in Composition a back callback that calls the `onBack` lambda + val backCallback = remember { object : OnBackPressedCallback(enabled) { override fun handleOnBackPressed() { - onBack() + currentOnBack() } } } - onCommit(enabled) { + // On every successful composition, update the callback with the `enabled` value + SideEffect { backCallback.isEnabled = enabled } - - val dispatcher = AmbientBackDispatcher.current - onCommit(backCallback) { - dispatcher.addCallback(backCallback) + val backDispatcher = AmbientBackDispatcher.current + // If `backDispatcher` changes, dispose and reset the effect + DisposableEffect(backDispatcher) { + // Add callback to the backDispatcher + backDispatcher.addCallback(backCallback) + // When the effect leaves the Composition, remove the callback onDispose { backCallback.remove() } diff --git a/Owl/build.gradle b/Owl/build.gradle index 9d7bfb02d5..fd710aa5c4 100644 --- a/Owl/build.gradle +++ b/Owl/build.gradle @@ -62,7 +62,7 @@ subprojects { target '**/*.kt' targetExclude("$buildDir/**/*.kt") targetExclude('bin/**/*.kt') - ktlint(Versions.ktlint) + ktlint(Versions.ktlint).userData([android: "true"]) licenseHeaderFile rootProject.file('spotless/copyright.kt') } } 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 9e66cf195d..c9279f2f10 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,11 +21,11 @@ object Versions { } object Libs { - const val androidGradlePlugin = "com.android.tools.build:gradle:7.0.0-alpha03" + const val androidGradlePlugin = "com.android.tools.build:gradle:7.0.0-alpha04" const val junit = "junit:junit:4.13" object Accompanist { - private const val version = "0.4.1" + private const val version = "0.4.2" const val coil = "dev.chrisbanes.accompanist:accompanist-coil:$version" const val insets = "dev.chrisbanes.accompanist:accompanist-insets:$version" } @@ -45,12 +45,12 @@ object Libs { } object AndroidX { - const val coreKtx = "androidx.core:core-ktx:1.5.0-alpha05" - const val navigation = "androidx.navigation:navigation-compose:1.0.0-alpha04" + const val coreKtx = "androidx.core:core-ktx:1.5.0-beta01" + const val navigation = "androidx.navigation:navigation-compose:1.0.0-alpha05" object Compose { const val snapshot = "" - const val version = "1.0.0-alpha09" + const val version = "1.0.0-alpha10" const val animation = "androidx.compose.animation:animation:$version" const val foundation = "androidx.compose.foundation:foundation:$version" 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 fc4cc593f7..d0c49fe38c 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,7 +21,7 @@ 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.ExperimentalTesting +import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onRoot import androidx.compose.ui.unit.dp @@ -41,7 +41,7 @@ import org.junit.Test * * Note that different systems can produce slightly different screenshots making the test fail. */ -@ExperimentalTesting +@ExperimentalTestApi @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O) class AnimatingCircleTests { 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 c820fce07a..16bed1e2d3 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 @@ -16,7 +16,7 @@ package com.example.compose.rally.ui.components -import androidx.compose.animation.animate +import androidx.compose.animation.animateAsState import androidx.compose.animation.animateContentSize import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.tween @@ -33,6 +33,7 @@ import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.material.ripple.rememberRipple import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -47,7 +48,11 @@ fun RallyTopAppBar( onTabSelected: (RallyScreen) -> Unit, currentScreen: RallyScreen ) { - Surface(Modifier.preferredHeight(TabHeight).fillMaxWidth()) { + Surface( + Modifier + .preferredHeight(TabHeight) + .fillMaxWidth() + ) { Row { allScreens.forEach { screen -> RallyTab( @@ -77,9 +82,9 @@ private fun RallyTab( delayMillis = TabFadeInAnimationDelay ) } - val tabTintColor = animate( - target = if (selected) color else color.copy(alpha = InactiveTabOpacity), - animSpec = animSpec + val tabTintColor by animateAsState( + targetValue = if (selected) color else color.copy(alpha = InactiveTabOpacity), + animationSpec = animSpec ) Row( modifier = Modifier 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 c3f2c53bee..90640065cc 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 @@ -17,11 +17,11 @@ package com.example.compose.rally.buildsrc object Versions { - const val ktlint = "0.39.0" + const val ktlint = "0.40.0" } object Libs { - const val androidGradlePlugin = "com.android.tools.build:gradle:7.0.0-alpha02" + const val androidGradlePlugin = "com.android.tools.build:gradle:7.0.0-alpha04" 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-alpha09" + const val version = "1.0.0-alpha10" const val core = "androidx.compose.ui:ui:$version" const val foundation = "androidx.compose.foundation:foundation:$version" diff --git a/Rally/gradle/wrapper/gradle-wrapper.properties b/Rally/gradle/wrapper/gradle-wrapper.properties index 88fae9754d..c7948985bf 100644 --- a/Rally/gradle/wrapper/gradle-wrapper.properties +++ b/Rally/gradle/wrapper/gradle-wrapper.properties @@ -19,4 +19,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip +distributionUrl=https://services.gradle.org/distributions/gradle-6.8-rc-1-bin.zip