diff --git a/README.md b/README.md
index 0a4c1de3..34dc1924 100644
--- a/README.md
+++ b/README.md
@@ -18,8 +18,8 @@
-
-
+
+
@@ -100,9 +100,12 @@ Please get **Android Studio Bumblebee latest Canary** [from here](https://develo
- __Others:__ After the above steps feel free to deep dive into Tablayouts, carousel, Dialogs and BottomSheets
+### Adaptive layouts (large screens)
+- `NavigationSuiteScaffold` switches between bottom bar, rail and drawer based on window size
+- `ListDetailPaneScaffold` two-pane layout on tablets/foldables — find it under "Adaptive UI" on the home screen
+
## Coming Soon
- Some of the features that will be available in coming weeks
- - Advance lists: Pull Refresh, Swipe lists etc
- Clean Architecture Sample with coroutines.
- Advance canvas drawing.
Much more in pipeline stay tuned!!
diff --git a/animations/canvas/build.gradle.kts b/animations/canvas/build.gradle.kts
index 808a2451..922e4121 100644
--- a/animations/canvas/build.gradle.kts
+++ b/animations/canvas/build.gradle.kts
@@ -1,5 +1,3 @@
-import com.guru.composecookbook.build.dependencies.addComposeOfficialDependencies
-
plugins {
/** See [common-compose-module-configs-script-plugin.gradle.kts] file */
id("common-compose-module-configs-script-plugin")
@@ -9,5 +7,6 @@ android { namespace = "com.guru.composecookbook.canvas" }
dependencies {
implementation(project(":theme"))
- addComposeOfficialDependencies()
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.bundles.compose.official)
}
diff --git a/animations/lottie/build.gradle.kts b/animations/lottie/build.gradle.kts
index 7c74eed7..7e81a0e1 100644
--- a/animations/lottie/build.gradle.kts
+++ b/animations/lottie/build.gradle.kts
@@ -1,7 +1,3 @@
-import com.guru.composecookbook.build.dependencies.addComposeOfficialDependencies
-import com.guru.composecookbook.build.dependencies.addCoreAndroidDependencies
-import com.guru.composecookbook.build.dependencies.addThirdPartyUiDependencies
-
plugins {
/** See [common-compose-module-configs-script-plugin.gradle.kts] file */
id("common-compose-module-configs-script-plugin")
@@ -10,7 +6,8 @@ plugins {
android { namespace = "com.guru.composecookbook.lottie" }
dependencies {
- addComposeOfficialDependencies()
- addCoreAndroidDependencies()
- addThirdPartyUiDependencies()
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.bundles.compose.official)
+ implementation(libs.bundles.core.android)
+ implementation(libs.bundles.thirdparty.ui)
}
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 151116eb..aa2fb412 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -1,19 +1,4 @@
import com.guru.composecookbook.build.configurations.ProjectConfigs
-import com.guru.composecookbook.build.dependencies.addAndroidInstrumentationTestsDependencies
-import com.guru.composecookbook.build.dependencies.addBiometricDependency
-import com.guru.composecookbook.build.dependencies.addComposeDebugDependencies
-import com.guru.composecookbook.build.dependencies.addComposeOfficialDependencies
-import com.guru.composecookbook.build.dependencies.addComposeThirdPartyDependencies
-import com.guru.composecookbook.build.dependencies.addCoreAndroidDependencies
-import com.guru.composecookbook.build.dependencies.addCoreAndroidUiDependencies
-import com.guru.composecookbook.build.dependencies.addDataDependencies
-import com.guru.composecookbook.build.dependencies.addGoogleAndroidDependencies
-import com.guru.composecookbook.build.dependencies.addJunit5TestDependencies
-import com.guru.composecookbook.build.dependencies.addKotlinDependencies
-import com.guru.composecookbook.build.dependencies.addKotlinTestDependencies
-import com.guru.composecookbook.build.dependencies.addNetworkingDependencies
-import com.guru.composecookbook.build.dependencies.addThirdPartyUiDependencies
-import com.guru.composecookbook.build.dependencies.addThirdPartyUnitTestsDependencies
plugins {
id("com.android.application")
@@ -86,25 +71,30 @@ dependencies {
implementation(project(":animations:canvas"))
implementation(project(":animations:lottie"))
- addKotlinDependencies()
+ implementation(libs.bundles.kotlin)
- addDataDependencies()
+ implementation(platform(libs.androidx.compose.bom))
+ ksp(libs.androidx.room.compiler)
+ implementation(libs.bundles.data)
- addComposeOfficialDependencies()
- addComposeDebugDependencies()
- addComposeThirdPartyDependencies()
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.bundles.compose.official)
+ debugImplementation(platform(libs.androidx.compose.bom))
+ debugImplementation(libs.bundles.compose.debug)
+ implementation(libs.bundles.compose.thirdparty)
- addThirdPartyUiDependencies()
+ implementation(libs.bundles.thirdparty.ui)
- addCoreAndroidDependencies()
- addCoreAndroidUiDependencies()
- addGoogleAndroidDependencies()
- addNetworkingDependencies()
+ implementation(libs.bundles.core.android)
+ implementation(libs.bundles.core.android.ui)
+ implementation(libs.bundles.google.android)
+ implementation(libs.bundles.networking)
- addKotlinTestDependencies()
- addJunit5TestDependencies()
- addThirdPartyUnitTestsDependencies()
+ testImplementation(libs.bundles.kotlin.test)
+ testImplementation(libs.bundles.junit5.test)
+ testImplementation(libs.truth)
- addAndroidInstrumentationTestsDependencies()
- addBiometricDependency()
+ androidTestImplementation(platform(libs.androidx.compose.bom))
+ androidTestImplementation(libs.bundles.instrumentation.test)
+ implementation(libs.androidx.biometric)
}
diff --git a/app/src/androidTest/java/com/guru/composecookbook/ui/home/adaptive/AdaptiveUIActivityTest.kt b/app/src/androidTest/java/com/guru/composecookbook/ui/home/adaptive/AdaptiveUIActivityTest.kt
new file mode 100644
index 00000000..8dc083ff
--- /dev/null
+++ b/app/src/androidTest/java/com/guru/composecookbook/ui/home/adaptive/AdaptiveUIActivityTest.kt
@@ -0,0 +1,64 @@
+package com.guru.composecookbook.ui.home.adaptive
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onAllNodesWithText
+import androidx.compose.ui.test.onFirst
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import org.junit.Rule
+import org.junit.Test
+
+class AdaptiveUIActivityTest {
+
+ @get:Rule val composeTestRule = createComposeRule()
+
+ @Test
+ fun everyNavigationDestinationIsLabelled() {
+ composeTestRule.setContent { AdaptiveDemoScreen() }
+
+ composeTestRule.onNodeWithText("Albums").assertIsDisplayed()
+ composeTestRule.onNodeWithText("Favorites").assertIsDisplayed()
+ composeTestRule.onNodeWithText("Profile").assertIsDisplayed()
+ }
+
+ @Test
+ fun albumsDestinationIsShownOnLaunch() {
+ composeTestRule.setContent { AdaptiveDemoScreen() }
+
+ composeTestRule.onNodeWithText("Perfect").assertIsDisplayed()
+ // Two albums in the sample data are by Ed Sheeran, so the artist alone is not unique.
+ composeTestRule.onAllNodesWithText("Ed Sheeran").onFirst().assertIsDisplayed()
+ }
+
+ @Test
+ fun selectingFavoritesReportsTheCurrentWindowSizeClasses() {
+ composeTestRule.setContent { AdaptiveDemoScreen() }
+
+ composeTestRule.onNodeWithText("Favorites").performClick()
+ composeTestRule.waitForIdle()
+
+ composeTestRule.onNodeWithText("Width size class:", substring = true).assertIsDisplayed()
+ composeTestRule.onNodeWithText("Height size class:", substring = true).assertIsDisplayed()
+ }
+
+ @Test
+ fun albumListShowsSongArtistAndGenre() {
+ composeTestRule.setContent { AlbumListDetail() }
+
+ composeTestRule.onNodeWithText("Perfect").assertIsDisplayed()
+ // Artist and genre both repeat across the sample data; the song title is the unique field.
+ composeTestRule.onAllNodesWithText("Ed Sheeran").onFirst().assertIsDisplayed()
+ composeTestRule.onAllNodesWithText("Pop").onFirst().assertIsDisplayed()
+ }
+
+ @Test
+ fun tappingAnAlbumOpensItsDetailPane() {
+ composeTestRule.setContent { AlbumListDetail() }
+
+ composeTestRule.onNodeWithText("Perfect").performClick()
+ composeTestRule.waitForIdle()
+
+ composeTestRule.onNodeWithText("Album by Ed Sheeran-2016").assertIsDisplayed()
+ }
+}
diff --git a/app/src/androidTest/java/com/guru/composecookbook/ui/home/advancelists/SwipeableListsTest.kt b/app/src/androidTest/java/com/guru/composecookbook/ui/home/advancelists/SwipeableListsTest.kt
new file mode 100644
index 00000000..273e70d5
--- /dev/null
+++ b/app/src/androidTest/java/com/guru/composecookbook/ui/home/advancelists/SwipeableListsTest.kt
@@ -0,0 +1,74 @@
+package com.guru.composecookbook.ui.home.advancelists
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onAllNodesWithContentDescription
+import androidx.compose.ui.test.onAllNodesWithText
+import androidx.compose.ui.test.onFirst
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.onParent
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.swipeLeft
+import androidx.compose.ui.test.swipeRight
+import org.junit.Rule
+import org.junit.Test
+
+class SwipeableListsTest {
+
+ @get:Rule val composeTestRule = createComposeRule()
+
+ @Test
+ fun albumsAreListedOnFirstComposition() {
+ composeTestRule.setContent { SwipeableLists() }
+
+ composeTestRule.onNodeWithText("Perfect").assertIsDisplayed()
+ composeTestRule.onNodeWithText("Havana").assertIsDisplayed()
+ // Two albums in the sample data are by Ed Sheeran, so the artist alone is not unique.
+ composeTestRule.onAllNodesWithText("Ed Sheeran").onFirst().assertIsDisplayed()
+ }
+
+ @Test
+ fun everyRowExposesItsOverflowButtonToAccessibilityServices() {
+ composeTestRule.setContent { SwipeableLists() }
+
+ composeTestRule.onAllNodesWithContentDescription("More options").onFirst().assertIsDisplayed()
+ }
+
+ @Test
+ fun swipingEndToStartDeletesTheRowAndOffersUndo() {
+ composeTestRule.setContent { SwipeableLists() }
+
+ // The swipe must be dispatched to the row, not to the song title: the title node is only a
+ // few pixels wide, well short of the box's 50% positional threshold.
+ composeTestRule.onNodeWithText("Perfect").onParent().performTouchInput { swipeLeft() }
+ composeTestRule.waitForIdle()
+
+ composeTestRule.onNodeWithText("Perfect").assertDoesNotExist()
+ composeTestRule.onNodeWithText("Perfect deleted").assertIsDisplayed()
+ composeTestRule.onNodeWithText("Undo").assertIsDisplayed()
+ }
+
+ @Test
+ fun swipingStartToEndArchivesTheRowAndOffersUndo() {
+ composeTestRule.setContent { SwipeableLists() }
+
+ composeTestRule.onNodeWithText("Perfect").onParent().performTouchInput { swipeRight() }
+ composeTestRule.waitForIdle()
+
+ composeTestRule.onNodeWithText("Perfect").assertDoesNotExist()
+ composeTestRule.onNodeWithText("Perfect archived").assertIsDisplayed()
+ }
+
+ @Test
+ fun undoRestoresASwipedAwayRow() {
+ composeTestRule.setContent { SwipeableLists() }
+
+ composeTestRule.onNodeWithText("Perfect").onParent().performTouchInput { swipeLeft() }
+ composeTestRule.waitForIdle()
+ composeTestRule.onNodeWithText("Undo").performClick()
+ composeTestRule.waitForIdle()
+
+ composeTestRule.onNodeWithText("Perfect").assertIsDisplayed()
+ }
+}
diff --git a/app/src/androidTest/java/com/guru/composecookbook/ui/home/predictiveback/PredictiveBackActivityTest.kt b/app/src/androidTest/java/com/guru/composecookbook/ui/home/predictiveback/PredictiveBackActivityTest.kt
new file mode 100644
index 00000000..795b7012
--- /dev/null
+++ b/app/src/androidTest/java/com/guru/composecookbook/ui/home/predictiveback/PredictiveBackActivityTest.kt
@@ -0,0 +1,60 @@
+package com.guru.composecookbook.ui.home.predictiveback
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithContentDescription
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import org.junit.Rule
+import org.junit.Test
+
+class PredictiveBackActivityTest {
+
+ @get:Rule val composeTestRule = createComposeRule()
+
+ @Test
+ fun screenIsTitledAndTheDrawerStartsOpen() {
+ composeTestRule.setContent { PredictiveBackDemoScreen() }
+
+ composeTestRule.onNodeWithText("Predictive Back").assertIsDisplayed()
+ composeTestRule.onNodeWithText("Mail").assertIsDisplayed()
+ composeTestRule.onNodeWithText("Inbox").assertIsDisplayed()
+ composeTestRule.onNodeWithText("Trash").assertIsDisplayed()
+ }
+
+ @Test
+ fun openDrawerExplainsTheBackGestureAffordance() {
+ composeTestRule.setContent { PredictiveBackDemoScreen() }
+
+ composeTestRule.onNodeWithText("Content area").assertIsDisplayed()
+ composeTestRule
+ .onNodeWithText("the drawer follows your gesture", substring = true)
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun togglingTheDrawerClosedHidesItAndOffersToReopen() {
+ composeTestRule.setContent { PredictiveBackDemoScreen() }
+
+ composeTestRule.onNodeWithContentDescription("Toggle drawer").performClick()
+ composeTestRule.waitForIdle()
+
+ composeTestRule.onNodeWithText("Mail").assertDoesNotExist()
+ composeTestRule.onNodeWithText("Inbox").assertDoesNotExist()
+ composeTestRule.onNodeWithText("Reopen drawer").assertIsDisplayed()
+ composeTestRule.onNodeWithText("Drawer closed.", substring = true).assertIsDisplayed()
+ }
+
+ @Test
+ fun reopenButtonBringsTheDrawerBack() {
+ composeTestRule.setContent { PredictiveBackDemoScreen() }
+
+ composeTestRule.onNodeWithContentDescription("Toggle drawer").performClick()
+ composeTestRule.waitForIdle()
+ composeTestRule.onNodeWithText("Reopen drawer").performClick()
+ composeTestRule.waitForIdle()
+
+ composeTestRule.onNodeWithText("Mail").assertIsDisplayed()
+ composeTestRule.onNodeWithText("Reopen drawer").assertDoesNotExist()
+ }
+}
diff --git a/app/src/androidTest/java/com/guru/composecookbook/ui/home/pullrefreshdemos/PullRefreshListTest.kt b/app/src/androidTest/java/com/guru/composecookbook/ui/home/pullrefreshdemos/PullRefreshListTest.kt
new file mode 100644
index 00000000..3a221bd8
--- /dev/null
+++ b/app/src/androidTest/java/com/guru/composecookbook/ui/home/pullrefreshdemos/PullRefreshListTest.kt
@@ -0,0 +1,52 @@
+package com.guru.composecookbook.ui.home.pullrefreshdemos
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onAllNodesWithText
+import androidx.compose.ui.test.onFirst
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.swipeDown
+import org.junit.Assert.assertEquals
+import org.junit.Rule
+import org.junit.Test
+
+class PullRefreshListTest {
+
+ @get:Rule val composeTestRule = createComposeRule()
+
+ @Test
+ fun firstAlbumIsDisplayedInitially() {
+ composeTestRule.setContent { PullRefreshList(onPullRefresh = {}) }
+
+ composeTestRule.onNodeWithText("Perfect").assertIsDisplayed()
+ // Two albums in the sample data are by Ed Sheeran, so this row text is not unique.
+ composeTestRule
+ .onAllNodesWithText("Ed Sheeran, Album by Ed Sheeran-2016")
+ .onFirst()
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun refreshedAlbumIsAbsentBeforeAnyRefresh() {
+ composeTestRule.setContent { PullRefreshList(onPullRefresh = {}) }
+
+ // AlbumsDataProvider.album is only prepended by a refresh, so it must not be there yet.
+ // Match on the artist/description line: it is the one string unique to that album.
+ composeTestRule.onNodeWithText("Adele, Album by Adele-2016").assertDoesNotExist()
+ }
+
+ @Test
+ fun pullingDownInvokesTheRefreshCallback() {
+ var invocations = 0
+ composeTestRule.setContent { PullRefreshList(onPullRefresh = { invocations++ }) }
+
+ // The gesture has to travel far enough to cross PullToRefreshBox's threshold, so it is
+ // dispatched to the whole screen rather than to a single row.
+ composeTestRule.onRoot().performTouchInput { swipeDown() }
+ composeTestRule.waitForIdle()
+
+ assertEquals(1, invocations)
+ }
+}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1f7adee5..1855d2fe 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -8,6 +8,7 @@
+
+
{
DynamicUIActivity.newIntent(context, DynamicUiType.MOTIONLAYOUT.name, isDarkTheme)
}
+ HomeScreenItems.AdaptiveUI -> {
+ AdaptiveUIActivity.newIntent(context, isDarkTheme)
+ }
+ HomeScreenItems.PredictiveBack -> {
+ PredictiveBackActivity.newIntent(context, isDarkTheme)
+ }
}
context.startActivity(intent)
}
diff --git a/app/src/main/java/com/guru/composecookbook/ui/home/adaptive/AdaptiveUIActivity.kt b/app/src/main/java/com/guru/composecookbook/ui/home/adaptive/AdaptiveUIActivity.kt
new file mode 100644
index 00000000..e6aa443d
--- /dev/null
+++ b/app/src/main/java/com/guru/composecookbook/ui/home/adaptive/AdaptiveUIActivity.kt
@@ -0,0 +1,184 @@
+package com.guru.composecookbook.ui.home.adaptive
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.BackHandler
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Album
+import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.material.icons.filled.Person
+import androidx.compose.material3.Card
+import androidx.compose.material3.Icon
+import androidx.compose.material3.ListItem
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
+import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
+import androidx.compose.material3.adaptive.layout.AnimatedPane
+import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold
+import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole
+import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator
+import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold
+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.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.unit.dp
+import com.guru.composecookbook.data.AlbumsDataProvider
+import com.guru.composecookbook.data.model.Album
+import com.guru.composecookbook.theme.ComposeCookBookMaterial3Theme
+import com.guru.composecookbook.ui.home.dynamic.DynamicUIActivity
+
+/**
+ * Demonstrates adaptive layouts for large screens:
+ * - [NavigationSuiteScaffold] automatically switches between a bottom bar (compact), navigation
+ * rail (medium) and drawer (expanded) based on the window size class.
+ * - [ListDetailPaneScaffold] shows a single pane on phones and a two-pane list/detail layout on
+ * tablets, foldables and desktops.
+ */
+class AdaptiveUIActivity : ComponentActivity() {
+
+ private val isDarkTheme: Boolean by lazy {
+ intent?.getBooleanExtra(DynamicUIActivity.DARK_THEME, false) ?: false
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent { ComposeCookBookMaterial3Theme(isDarkTheme) { AdaptiveDemoScreen() } }
+ }
+
+ companion object {
+ fun newIntent(context: Context, isDarkTheme: Boolean) =
+ Intent(context, AdaptiveUIActivity::class.java).apply {
+ putExtra(DynamicUIActivity.DARK_THEME, isDarkTheme)
+ }
+ }
+}
+
+enum class AdaptiveDemoDestination(val label: String, val icon: ImageVector) {
+ Albums("Albums", Icons.Default.Album),
+ Favorites("Favorites", Icons.Default.Favorite),
+ Profile("Profile", Icons.Default.Person),
+}
+
+@Composable
+fun AdaptiveDemoScreen() {
+ var currentDestination by remember { mutableStateOf(AdaptiveDemoDestination.Albums) }
+
+ NavigationSuiteScaffold(
+ navigationSuiteItems = {
+ AdaptiveDemoDestination.entries.forEach { destination ->
+ item(
+ icon = { Icon(destination.icon, contentDescription = destination.label) },
+ label = { Text(destination.label) },
+ selected = destination == currentDestination,
+ onClick = { currentDestination = destination },
+ )
+ }
+ }
+ ) {
+ when (currentDestination) {
+ AdaptiveDemoDestination.Albums -> AlbumListDetail()
+ AdaptiveDemoDestination.Favorites -> WindowInfoPane("Favorites")
+ AdaptiveDemoDestination.Profile -> WindowInfoPane("Profile")
+ }
+ }
+}
+
+@OptIn(ExperimentalMaterial3AdaptiveApi::class)
+@Composable
+fun AlbumListDetail() {
+ val navigator = rememberListDetailPaneScaffoldNavigator()
+
+ BackHandler(navigator.canNavigateBack()) { navigator.navigateBack() }
+
+ ListDetailPaneScaffold(
+ directive = navigator.scaffoldDirective,
+ value = navigator.scaffoldValue,
+ listPane = {
+ AnimatedPane {
+ LazyColumn {
+ items(AlbumsDataProvider.albums) { album ->
+ ListItem(
+ modifier =
+ Modifier.fillMaxWidth().clickable {
+ navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, album)
+ },
+ headlineContent = { Text(album.song) },
+ supportingContent = { Text(album.artist) },
+ leadingContent = { Icon(Icons.Default.Album, contentDescription = null) },
+ trailingContent = { Text(album.genre, style = MaterialTheme.typography.labelSmall) },
+ tonalElevation = 2.dp,
+ )
+ androidx.compose.material3.HorizontalDivider()
+ }
+ }
+ }
+ },
+ detailPane = {
+ AnimatedPane {
+ val album = navigator.currentDestination?.content
+ AlbumDetailPane(album)
+ }
+ },
+ )
+}
+
+@Composable
+private fun AlbumDetailPane(album: Album?) {
+ Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ if (album == null) {
+ Text(
+ text = "Select an album from the list",
+ style = MaterialTheme.typography.bodyLarge,
+ modifier = Modifier.padding(16.dp),
+ )
+ } else {
+ Card(modifier = Modifier.padding(16.dp)) {
+ Column(modifier = Modifier.padding(24.dp)) {
+ Text(text = album.song, style = MaterialTheme.typography.headlineMedium)
+ Text(text = album.artist, style = MaterialTheme.typography.titleMedium)
+ Text(
+ text = album.descriptions,
+ style = MaterialTheme.typography.bodyMedium,
+ modifier = Modifier.padding(top = 16.dp),
+ )
+ }
+ }
+ }
+ }
+}
+
+@Composable
+private fun WindowInfoPane(title: String) {
+ val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
+ Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ Column(horizontalAlignment = Alignment.CenterHorizontally) {
+ Text(text = title, style = MaterialTheme.typography.headlineMedium)
+ Text(
+ text = "Width size class: ${windowSizeClass.windowWidthSizeClass}",
+ style = MaterialTheme.typography.bodyMedium,
+ modifier = Modifier.padding(top = 8.dp),
+ )
+ Text(
+ text = "Height size class: ${windowSizeClass.windowHeightSizeClass}",
+ style = MaterialTheme.typography.bodyMedium,
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/com/guru/composecookbook/ui/home/advancelists/AdvanceListsActivity.kt b/app/src/main/java/com/guru/composecookbook/ui/home/advancelists/AdvanceListsActivity.kt
index b89d5438..ffd0c8b7 100644
--- a/app/src/main/java/com/guru/composecookbook/ui/home/advancelists/AdvanceListsActivity.kt
+++ b/app/src/main/java/com/guru/composecookbook/ui/home/advancelists/AdvanceListsActivity.kt
@@ -16,8 +16,8 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
-import androidx.compose.material3.SmallTopAppBar
import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -49,7 +49,7 @@ class AdvanceListsActivity : ComponentActivity() {
// A surface container using the 'background' color from the theme
Scaffold(
topBar = {
- SmallTopAppBar(
+ TopAppBar(
title = { Text(text = "Advance Lists(In Progress)") },
navigationIcon = {
IconButton(onClick = { onBackPressed() }) {
diff --git a/app/src/main/java/com/guru/composecookbook/ui/home/advancelists/AnimatedLists.kt b/app/src/main/java/com/guru/composecookbook/ui/home/advancelists/AnimatedLists.kt
index 6db2af23..fb10a504 100644
--- a/app/src/main/java/com/guru/composecookbook/ui/home/advancelists/AnimatedLists.kt
+++ b/app/src/main/java/com/guru/composecookbook/ui/home/advancelists/AnimatedLists.kt
@@ -32,7 +32,7 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
-import coil.compose.rememberImagePainter
+import coil3.compose.rememberAsyncImagePainter
import com.guru.composecookbook.data.DemoDataProvider
import com.guru.composecookbook.data.model.Tweet
import com.guru.composecookbook.theme.typography
@@ -145,8 +145,8 @@ fun AnimatedListItem(tweet: Tweet, itemIndex: Int, animationIndex: Int) {
Row(modifier = animatedModifier, verticalAlignment = Alignment.CenterVertically) {
Image(
painter =
- rememberImagePainter(
- data =
+ rememberAsyncImagePainter(
+ model =
"https://picsum.photos/id/${
itemIndex +
1
diff --git a/app/src/main/java/com/guru/composecookbook/ui/home/advancelists/SwipeableLists.kt b/app/src/main/java/com/guru/composecookbook/ui/home/advancelists/SwipeableLists.kt
index 3cf707f2..0d4cbb66 100644
--- a/app/src/main/java/com/guru/composecookbook/ui/home/advancelists/SwipeableLists.kt
+++ b/app/src/main/java/com/guru/composecookbook/ui/home/advancelists/SwipeableLists.kt
@@ -1,71 +1,129 @@
package com.guru.composecookbook.ui.home.advancelists
-import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.itemsIndexed
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Archive
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.guru.composecookbook.data.AlbumsDataProvider
import com.guru.composecookbook.data.model.Album
+import kotlinx.coroutines.launch
+/**
+ * Demonstrates Material3 [SwipeToDismissBox]:
+ * - swipe left (end to start) to delete an item,
+ * - swipe right (start to end) to archive it,
+ * - either action removes the item and offers Undo via a [Snackbar].
+ */
@Composable
fun SwipeableLists() {
- val albums by remember { mutableStateOf(AlbumsDataProvider.albums) }
- LazyColumn {
- itemsIndexed(
- items = albums,
- itemContent = { index, album -> SwipeableListItem(index, album) { index -> } },
- )
+ val albums = remember { AlbumsDataProvider.albums.toMutableStateList() }
+ val snackbarHostState = remember { SnackbarHostState() }
+ val listState = rememberLazyListState()
+ val scope = rememberCoroutineScope()
+
+ Box(modifier = Modifier.fillMaxSize()) {
+ LazyColumn(state = listState) {
+ items(items = albums, key = { it.id }) { album ->
+ SwipeableListItem(
+ album = album,
+ onItemSwiped = { dismissValue ->
+ val index = albums.indexOf(album)
+ if (index >= 0) {
+ albums.removeAt(index)
+ val action =
+ if (dismissValue == SwipeToDismissBoxValue.StartToEnd) "archived" else "deleted"
+ scope.launch {
+ val result =
+ snackbarHostState.showSnackbar(
+ message = "${album.song} $action",
+ actionLabel = "Undo",
+ duration = SnackbarDuration.Short,
+ )
+ if (result == SnackbarResult.ActionPerformed) {
+ val restoreAt = index.coerceAtMost(albums.size)
+ albums.add(restoreAt, album)
+ // LazyColumn keeps the first visible item anchored, so a row re-inserted above
+ // it would silently land off-screen. Scroll it back into view.
+ listState.animateScrollToItem(restoreAt)
+ }
+ }
+ }
+ },
+ )
+ }
+ }
+ SnackbarHost(hostState = snackbarHostState, modifier = Modifier.align(Alignment.BottomCenter))
}
}
-@OptIn(ExperimentalMaterial3Api::class, ExperimentalAnimationApi::class)
@Composable
-fun SwipeableListItem(index: Int, album: Album, onItemSwiped: (Int) -> Unit) {
+fun SwipeableListItem(album: Album, onItemSwiped: (SwipeToDismissBoxValue) -> Unit) {
val dismissState =
rememberSwipeToDismissBoxState(
confirmValueChange = { dismissValue ->
- if (dismissValue == SwipeToDismissBoxValue.EndToStart) {
- onItemSwiped(index)
+ if (dismissValue != SwipeToDismissBoxValue.Settled) {
+ onItemSwiped(dismissValue)
true
} else {
false
}
- }
+ },
+ // require half of the item width to be dragged before dismissing
+ positionalThreshold = { totalDistance -> totalDistance * 0.5f },
)
SwipeToDismissBox(
state = dismissState,
- backgroundContent = {
- Box(
- modifier =
- Modifier.fillMaxSize()
- .background(MaterialTheme.colorScheme.errorContainer)
- .padding(horizontal = 16.dp),
- contentAlignment = Alignment.CenterEnd,
- ) {
- Icon(
- imageVector = Icons.Default.Delete,
- contentDescription = "Delete",
- tint = MaterialTheme.colorScheme.onErrorContainer,
- )
- }
- },
+ backgroundContent = { SwipeActionBackground(dismissState.dismissDirection) },
content = { ListItemContent(album) },
- enableDismissFromStartToEnd = false,
+ enableDismissFromStartToEnd = true,
enableDismissFromEndToStart = true,
)
}
+@Composable
+private fun SwipeActionBackground(direction: SwipeToDismissBoxValue) {
+ val isArchive = direction == SwipeToDismissBoxValue.StartToEnd
+ val background =
+ if (isArchive) MaterialTheme.colorScheme.secondaryContainer
+ else MaterialTheme.colorScheme.errorContainer
+ Box(
+ modifier =
+ Modifier.fillMaxSize()
+ .padding(horizontal = 16.dp, vertical = 8.dp)
+ .clip(MaterialTheme.shapes.medium)
+ .background(background)
+ .padding(horizontal = 16.dp),
+ contentAlignment = if (isArchive) Alignment.CenterStart else Alignment.CenterEnd,
+ ) {
+ if (isArchive) {
+ Icon(
+ imageVector = Icons.Default.Archive,
+ contentDescription = "Archive",
+ tint = MaterialTheme.colorScheme.onSecondaryContainer,
+ )
+ } else {
+ Icon(
+ imageVector = Icons.Default.Delete,
+ contentDescription = "Delete",
+ tint = MaterialTheme.colorScheme.onErrorContainer,
+ )
+ }
+ }
+}
+
@Composable
private fun ListItemContent(album: Album) {
Card(modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 8.dp)) {
diff --git a/app/src/main/java/com/guru/composecookbook/ui/home/customfling/FlingListActivity.kt b/app/src/main/java/com/guru/composecookbook/ui/home/customfling/FlingListActivity.kt
index 88adf82e..cb7e5f7f 100644
--- a/app/src/main/java/com/guru/composecookbook/ui/home/customfling/FlingListActivity.kt
+++ b/app/src/main/java/com/guru/composecookbook/ui/home/customfling/FlingListActivity.kt
@@ -18,8 +18,8 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
-import androidx.compose.material3.SmallTopAppBar
import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.livedata.observeAsState
@@ -139,7 +139,7 @@ fun ListViewContent(
*/
Scaffold(
topBar = {
- SmallTopAppBar(
+ TopAppBar(
title = {
Column(modifier = Modifier.padding(4.dp)) {
/*
diff --git a/app/src/main/java/com/guru/composecookbook/ui/home/dialogs/DialogsActivity.kt b/app/src/main/java/com/guru/composecookbook/ui/home/dialogs/DialogsActivity.kt
index e53afdc9..10eb1bd6 100644
--- a/app/src/main/java/com/guru/composecookbook/ui/home/dialogs/DialogsActivity.kt
+++ b/app/src/main/java/com/guru/composecookbook/ui/home/dialogs/DialogsActivity.kt
@@ -24,9 +24,9 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Scaffold
-import androidx.compose.material3.SmallTopAppBar
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
+import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -77,7 +77,7 @@ class DialogsActivity : AppCompatActivity() {
fun DialogScreen(onBack: () -> Unit) {
Scaffold(
topBar = {
- SmallTopAppBar(
+ TopAppBar(
title = { Text(text = "Dialogs") },
navigationIcon = {
IconButton(onClick = onBack) {
diff --git a/app/src/main/java/com/guru/composecookbook/ui/home/dynamic/DynamicUIActivity.kt b/app/src/main/java/com/guru/composecookbook/ui/home/dynamic/DynamicUIActivity.kt
index fcc3f87b..44ef743f 100644
--- a/app/src/main/java/com/guru/composecookbook/ui/home/dynamic/DynamicUIActivity.kt
+++ b/app/src/main/java/com/guru/composecookbook/ui/home/dynamic/DynamicUIActivity.kt
@@ -13,8 +13,8 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
-import androidx.compose.material3.SmallTopAppBar
import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
@@ -64,7 +64,7 @@ class DynamicUIActivity : ComponentActivity() {
fun DynamicUiWrapper(uiType: String, onback: () -> Unit) {
Scaffold(
topBar = {
- SmallTopAppBar(
+ TopAppBar(
title = { Text(text = uiType) },
navigationIcon = {
IconButton(onClick = onback) {
diff --git a/app/src/main/java/com/guru/composecookbook/ui/home/lists/ListViewActivity.kt b/app/src/main/java/com/guru/composecookbook/ui/home/lists/ListViewActivity.kt
index 45c1f9f2..59022f45 100644
--- a/app/src/main/java/com/guru/composecookbook/ui/home/lists/ListViewActivity.kt
+++ b/app/src/main/java/com/guru/composecookbook/ui/home/lists/ListViewActivity.kt
@@ -21,8 +21,8 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
-import androidx.compose.material3.SmallTopAppBar
import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
@@ -73,7 +73,7 @@ fun BaseView(isDarkTheme: Boolean, content: @Composable () -> Unit) {
fun ListViewContent(listType: String, onBack: () -> Unit) {
Scaffold(
topBar = {
- SmallTopAppBar(
+ TopAppBar(
title = {
Column(modifier = Modifier.padding(4.dp)) {
Text(text = "ListView")
diff --git a/app/src/main/java/com/guru/composecookbook/ui/home/predictiveback/PredictiveBackActivity.kt b/app/src/main/java/com/guru/composecookbook/ui/home/predictiveback/PredictiveBackActivity.kt
new file mode 100644
index 00000000..79bdbf25
--- /dev/null
+++ b/app/src/main/java/com/guru/composecookbook/ui/home/predictiveback/PredictiveBackActivity.kt
@@ -0,0 +1,179 @@
+package com.guru.composecookbook.ui.home.predictiveback
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.PredictiveBackHandler
+import androidx.activity.compose.setContent
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.MenuOpen
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.ListItem
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableFloatStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.unit.dp
+import com.guru.composecookbook.theme.ComposeCookBookMaterial3Theme
+import com.guru.composecookbook.ui.home.dynamic.DynamicUIActivity
+import kotlin.coroutines.cancellation.CancellationException
+
+/**
+ * Demonstrates the Android 15+ predictive back gesture with Compose's [PredictiveBackHandler]. An
+ * in-app navigation drawer follows the user's back-swipe progress in real time — it slides out and
+ * scales as the gesture proceeds, commits closed when the gesture completes, and springs back if
+ * the gesture is cancelled.
+ */
+class PredictiveBackActivity : ComponentActivity() {
+
+ private val isDarkTheme: Boolean by lazy {
+ intent?.getBooleanExtra(DynamicUIActivity.DARK_THEME, false) ?: false
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent { ComposeCookBookMaterial3Theme(isDarkTheme) { PredictiveBackDemoScreen() } }
+ }
+
+ companion object {
+ fun newIntent(context: Context, isDarkTheme: Boolean) =
+ Intent(context, PredictiveBackActivity::class.java).apply {
+ putExtra(DynamicUIActivity.DARK_THEME, isDarkTheme)
+ }
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun PredictiveBackDemoScreen() {
+ var drawerOpen by remember { mutableStateOf(true) }
+ // Live gesture progress (0f..1f) reported by the predictive back handler.
+ var backProgress by remember { mutableFloatStateOf(0f) }
+
+ // Only intercept back while the drawer is open; when closed the system handles back normally.
+ PredictiveBackHandler(enabled = drawerOpen) { progress ->
+ try {
+ progress.collect { backEvent -> backProgress = backEvent.progress }
+ // Gesture committed -> close the drawer instead of finishing the activity.
+ drawerOpen = false
+ backProgress = 0f
+ } catch (e: CancellationException) {
+ // Gesture cancelled -> snap the drawer back open.
+ backProgress = 0f
+ throw e
+ }
+ }
+
+ Scaffold(
+ topBar = {
+ TopAppBar(
+ title = { Text("Predictive Back") },
+ actions = {
+ IconButton(onClick = { drawerOpen = !drawerOpen }) {
+ Icon(Icons.AutoMirrored.Filled.MenuOpen, contentDescription = "Toggle drawer")
+ }
+ },
+ )
+ }
+ ) { padding ->
+ Box(modifier = Modifier.fillMaxSize().padding(padding)) {
+ DetailContent(drawerOpen = drawerOpen, onOpenDrawer = { drawerOpen = true })
+
+ if (drawerOpen) {
+ // Translate + scale + fade the drawer in lock-step with the gesture so the user sees
+ // exactly where releasing will take them (the predictive back affordance).
+ val animatedProgress by animateFloatAsState(backProgress, label = "backProgress")
+ DemoDrawer(
+ modifier =
+ Modifier.graphicsLayer {
+ translationX = -size.width * 0.25f * animatedProgress
+ scaleX = 1f - 0.1f * animatedProgress
+ scaleY = 1f - 0.1f * animatedProgress
+ alpha = 1f - 0.5f * animatedProgress
+ }
+ )
+ }
+ }
+ }
+}
+
+@Composable
+private fun DetailContent(drawerOpen: Boolean, onOpenDrawer: () -> Unit) {
+ Column(
+ modifier = Modifier.fillMaxSize().padding(24.dp),
+ verticalArrangement = androidx.compose.foundation.layout.Arrangement.spacedBy(12.dp),
+ ) {
+ Text("Content area", style = MaterialTheme.typography.headlineSmall)
+ Text(
+ if (drawerOpen)
+ "Swipe from the screen edge (or press back) — the drawer follows your gesture and " +
+ "closes when you commit, instead of leaving the screen."
+ else "Drawer closed. Press back again to leave, or tap below to reopen it.",
+ style = MaterialTheme.typography.bodyMedium,
+ )
+ if (!drawerOpen) {
+ androidx.compose.material3.Button(onClick = onOpenDrawer) { Text("Reopen drawer") }
+ }
+ }
+}
+
+@Composable
+private fun DemoDrawer(modifier: Modifier = Modifier) {
+ val items = listOf("Inbox", "Starred", "Sent", "Drafts", "Spam", "Trash")
+ Box(
+ modifier =
+ modifier
+ .fillMaxHeight()
+ .width(260.dp)
+ .background(MaterialTheme.colorScheme.surfaceContainerHigh)
+ ) {
+ Column(modifier = Modifier.fillMaxWidth().padding(top = 16.dp)) {
+ Text(
+ "Mail",
+ style = MaterialTheme.typography.titleLarge,
+ modifier = Modifier.padding(horizontal = 24.dp, vertical = 12.dp),
+ )
+ LazyColumn {
+ items(items) { label ->
+ ListItem(
+ headlineContent = { Text(label) },
+ colors =
+ androidx.compose.material3.ListItemDefaults.colors(
+ containerColor = androidx.compose.ui.graphics.Color.Transparent
+ ),
+ )
+ }
+ }
+ }
+ Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.BottomEnd) {
+ Text(
+ "← swipe back to close",
+ style = MaterialTheme.typography.labelSmall,
+ modifier = Modifier.padding(16.dp),
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/com/guru/composecookbook/ui/home/pullrefreshdemos/PullRefreshList.kt b/app/src/main/java/com/guru/composecookbook/ui/home/pullrefreshdemos/PullRefreshList.kt
index c68f1b01..1438fdc5 100644
--- a/app/src/main/java/com/guru/composecookbook/ui/home/pullrefreshdemos/PullRefreshList.kt
+++ b/app/src/main/java/com/guru/composecookbook/ui/home/pullrefreshdemos/PullRefreshList.kt
@@ -1,69 +1,53 @@
package com.guru.composecookbook.ui.home.pullrefreshdemos
-import androidx.compose.animation.core.Animatable
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.toMutableStateList
+import androidx.compose.ui.Modifier
import com.guru.composecookbook.data.AlbumsDataProvider
+import com.guru.composecookbook.spotify.ui.details.components.SpotifySongListItem
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+/**
+ * Demonstrates Material3 [PullToRefreshBox]: pull the list down to trigger a simulated network
+ * refresh (1.5s delay) that prepends a "new" album to the list.
+ */
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PullRefreshList(onPullRefresh: () -> Unit) {
- val albums = AlbumsDataProvider.albums
- // TODO revisit pull refresh
- val initialYTranslate = -100f
- val maximumYTranslate = 200f
- val animatedProgress = remember { Animatable(0f) }
+ val albums = remember { AlbumsDataProvider.albums.toMutableStateList() }
+ var isRefreshing by remember { mutableStateOf(false) }
+ val scope = rememberCoroutineScope()
val lazyListState = rememberLazyListState()
- // val draggableModifier = Modifier.draggable(
- // orientation = Orientation.Vertical,
- // reverseDirection = false,
- // enabled = lazyListState.firstVisibleItemIndex < 2,
- // onDragStarted = {
- // onPullRefresh.invoke()
- // if (animatedProgress.value < maximumYTranslate) {
- // animatedProgress.animateTo(
- // targetValue = maximumYTranslate,
- // animationSpec = tween(durationMillis = 1200, easing = LinearEasing),
- // )
- // }
- // },
- // onDragStopped = {
- // animatedProgress.animateTo(
- // targetValue = 3000f,
- // animationSpec = repeatable(
- // iterations = 1,
- // animation = tween(durationMillis = 3000, easing = LinearEasing),
- // ),
- // )
- // }
- // )
-
- // Box(modifier = draggableModifier) {
- // LazyColumn(state = lazyListState) {
- // items(
- // count = albums.size,
- // itemContent = { index ->
- // SpotifySongListItem(album = albums[index])
- // })
- // }
- // //Animated Icon
- // Icon(
- // imageVector = Icons.Default.RotateRight,
- // tint = Color.Black,
- // contentDescription = null,
- // modifier = Modifier.align(Alignment.TopCenter)
- // .graphicsLayer(
- // translationY = animateFloatAsState(
- // animatedProgress.value.coerceIn(
- // initialYTranslate,
- // maximumYTranslate
- // )
- // ).value,
- // rotationZ = animateFloatAsState(
- // animatedProgress.value
- // ).value
- // ).background(Color.LightGray, shape = CircleShape).padding(2.dp)
- // )
- // }
+ PullToRefreshBox(
+ isRefreshing = isRefreshing,
+ onRefresh = {
+ onPullRefresh.invoke()
+ scope.launch {
+ isRefreshing = true
+ // Simulate a network reload
+ delay(1500)
+ albums.add(0, AlbumsDataProvider.album.copy(id = albums.size + 1))
+ isRefreshing = false
+ lazyListState.scrollToItem(0)
+ }
+ },
+ modifier = Modifier.fillMaxSize(),
+ ) {
+ LazyColumn(state = lazyListState, modifier = Modifier.fillMaxSize()) {
+ items(albums) { album -> SpotifySongListItem(album = album) }
+ }
+ }
}
diff --git a/app/src/main/java/com/guru/composecookbook/ui/learnwidgets/AppBars.kt b/app/src/main/java/com/guru/composecookbook/ui/learnwidgets/AppBars.kt
index ef1e7752..cb496194 100644
--- a/app/src/main/java/com/guru/composecookbook/ui/learnwidgets/AppBars.kt
+++ b/app/src/main/java/com/guru/composecookbook/ui/learnwidgets/AppBars.kt
@@ -25,8 +25,8 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.SmallTopAppBar
import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar as Material3TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -57,7 +57,7 @@ fun AppBars() {
fun TopAppBarsDemo() {
SubtitleText(subtitle = "Top App bar")
- SmallTopAppBar(
+ Material3TopAppBar(
title = { Text(text = "Home") },
navigationIcon = {
IconButton(onClick = {}) {
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 935e3c3a..44ca9b0e 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -17,6 +17,8 @@
ca-app-pub-3940256099942544/6300978111
ca-app-pub-5207778306358129~3940384518
My Compose App
+ Adaptive UI
+ Predictive Back
Custom Fling
Custom Fling Settings
Templates
diff --git a/build.gradle.kts b/build.gradle.kts
index 0b97f8c3..84e272b0 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,17 +1,9 @@
-buildscript {
- repositories {
- mavenCentral()
- google()
- }
- dependencies {
- classpath("com.android.tools.build:gradle:8.7.3")
- classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.1.20")
- classpath("org.jetbrains.kotlin:compose-compiler-gradle-plugin:2.1.20")
- }
-}
-
+// Plugin versions come from gradle/libs.versions.toml (the version catalog).
+// AGP, Kotlin, KSP, serialization and the Compose compiler plugin reach the
+// build classpath through buildSrc's dependencies (also catalog-driven), so
+// only ktfmt needs to be declared here.
plugins {
- id("com.ncorti.ktfmt.gradle") version "0.22.0" apply false
+ alias(libs.plugins.ktfmt) apply false
}
allprojects {
diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts
index 1f16e886..994df797 100644
--- a/buildSrc/build.gradle.kts
+++ b/buildSrc/build.gradle.kts
@@ -11,41 +11,28 @@ sourceSets {
}
}
-buildscript {
-
- repositories {
- mavenLocal()
- mavenCentral()
- google()
- }
-
- dependencies {
- classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.1.20")
- }
-}
-
-
repositories {
- mavenLocal()
mavenCentral()
google()
- google()
}
dependencies {
// in order to be able to use "kotlin-android" in the common script
- implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.1.20")
+ implementation(libs.kotlin.gradle.plugin)
// in order to be able to apply "org.jetbrains.kotlin.plugin.compose" in the common scripts
- implementation("org.jetbrains.kotlin:compose-compiler-gradle-plugin:2.1.20")
+ implementation(libs.compose.compiler.gradle.plugin)
+
+ // in order to be able to apply "org.jetbrains.kotlin.plugin.serialization" in module scripts
+ implementation(libs.kotlin.serialization.plugin)
// in order to be able to apply "com.google.devtools.ksp" in the common scripts
- // (KSP version must match the Kotlin version above)
- implementation("com.google.devtools.ksp:symbol-processing-gradle-plugin:2.1.20-1.0.31")
+ // (the KSP version in the catalog must match the Kotlin version)
+ implementation(libs.ksp.gradle.plugin)
// in order to recognize the "plugins" block in the common script
- implementation("com.android.tools.build:gradle:8.7.3")
+ implementation(libs.android.gradle.plugin)
// in order to recognize the "android" block in the common script
- implementation("com.android.tools.build:gradle-api:8.7.3")
+ implementation(libs.android.gradle.api)
}
diff --git a/buildSrc/settings.gradle.kts b/buildSrc/settings.gradle.kts
new file mode 100644
index 00000000..ee11cc12
--- /dev/null
+++ b/buildSrc/settings.gradle.kts
@@ -0,0 +1,5 @@
+// Expose the root version catalog to buildSrc so plugin/dependency
+// versions live in a single place: gradle/libs.versions.toml
+dependencyResolutionManagement {
+ versionCatalogs { create("libs") { from(files("../gradle/libs.versions.toml")) } }
+}
diff --git a/buildSrc/src/main/kotlin/com/guru/composecookbook/build/dependencies/Dependencies.kt b/buildSrc/src/main/kotlin/com/guru/composecookbook/build/dependencies/Dependencies.kt
deleted file mode 100644
index c2c63dd6..00000000
--- a/buildSrc/src/main/kotlin/com/guru/composecookbook/build/dependencies/Dependencies.kt
+++ /dev/null
@@ -1,88 +0,0 @@
-package com.guru.composecookbook.build.dependencies
-
-object Dependencies {
-
- const val kotlinReflect = "org.jetbrains.kotlin:kotlin-reflect:${Versions.kotlin}"
- const val fontAwesomeCompose =
- "com.github.Gurupreet:FontAwesomeCompose:${Versions.fontAwesomeCompose}"
- const val kotlinStandardLibraryJdk8 =
- "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${Versions.kotlin}"
- const val androidCoreKtx = "androidx.core:core-ktx:${Versions.coreKtx}"
- const val androidAppCompat = "androidx.appcompat:appcompat:${Versions.appcompat}"
- const val androidPaletteKtx = "androidx.palette:palette-ktx:${Versions.paletteKtx}"
- const val googleMaterial = "com.google.android.material:material:${Versions.material}"
- const val androidMultiDex = "androidx.multidex:multidex:${Versions.multidex}"
- const val playServicesAds =
- "com.google.android.gms:play-services-ads:${Versions.playServicesAds}"
- // Removed googleMaps = "com.google.android.libraries.maps:maps" as it's not publicly available
- const val playServicesMaps =
- "com.google.android.gms:play-services-maps:${Versions.playServicesMaps}"
- const val composeUi = "androidx.compose.ui:ui:${Versions.compose}"
- const val composeUiTooling = "androidx.compose.ui:ui-tooling:${Versions.compose}"
- // Material Design 1 and 3 dependencies
- const val composeMaterial = "androidx.compose.material:material:${Versions.compose}"
- const val composeMaterial3 = "androidx.compose.material3:material3:${Versions.material3}"
- const val composeMaterial3WindowSizeClass = "androidx.compose.material3:material3-window-size-class:${Versions.material3}"
- const val composeMaterialIconsExtended =
- "androidx.compose.material:material-icons-extended:${Versions.compose}"
-
- // Runtime and State Management
- const val composeRuntime = "androidx.compose.runtime:runtime:${Versions.compose}"
- const val composeRuntimeLivedata =
- "androidx.compose.runtime:runtime-livedata:${Versions.compose}"
-
- // Layout and Navigation
- const val composeConstraintLayout =
- "androidx.constraintlayout:constraintlayout-compose:${Versions.constraintLayoutCompose}"
- const val composePaging = "androidx.paging:paging-compose:${Versions.pagingCompose}"
- const val composeViewModel =
- "androidx.lifecycle:lifecycle-viewmodel-compose:${Versions.androidLifecycleGrouped}"
- const val composeActivity = "androidx.activity:activity-compose:${Versions.activityCompose}"
- const val composeLottie = "com.airbnb.android:lottie-compose:${Versions.lottie}"
- const val composeNavigation = "androidx.navigation:navigation-compose:${Versions.navCompose}"
-
- const val composeUiTestJunit4 = "androidx.compose.ui:ui-test-junit4:${Versions.compose}"
- const val composeUiTestManifest = "androidx.compose.ui:ui-test-manifest:${Versions.compose}"
-
- /**
- * Custom fling behaviour
- * refer to https://github.com/iamjosephmj/flinger for more insights on the library
- */
- const val flinger = "com.github.iamjosephmj:flinger:${Versions.flinger}"
-
- const val androidPagingRuntime = "androidx.paging:paging-runtime-ktx:${Versions.paging}"
- const val lottie = "com.airbnb.android:lottie:${Versions.lottie}"
- const val androidExoPlayer = "com.google.android.exoplayer:exoplayer:${Versions.exoplayer}"
- const val roomRuntime = "androidx.room:room-runtime:${Versions.room}"
- const val roomKtx = "androidx.room:room-ktx:${Versions.room}"
- const val roomCompiler = "androidx.room:room-compiler:${Versions.room}"
- const val coilCompose = "io.coil-kt:coil-compose:${Versions.coilCompose}"
- const val retrofit = "com.squareup.retrofit2:retrofit:${Versions.retrofit}"
- const val okHttpLoggingInterceptor =
- "com.squareup.okhttp3:logging-interceptor:${Versions.loggingInterceptor}"
- const val gson = "com.google.code.gson:gson:${Versions.gson}"
- const val okHttpGsonConverter =
- "com.squareup.retrofit2:converter-gson:${Versions.retrofitConverterGson}"
- const val coroutinesCore =
- "org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.coroutines}"
- const val coroutinesAndroid =
- "org.jetbrains.kotlinx:kotlinx-coroutines-android:${Versions.coroutines}"
- const val viewModelKtx =
- "androidx.lifecycle:lifecycle-viewmodel-ktx:${Versions.androidLifecycleGrouped}"
- const val liveDataKtx =
- "androidx.lifecycle:lifecycle-livedata-ktx:${Versions.androidLifecycleGrouped}"
- const val lifecycleRuntimeKtx =
- "androidx.lifecycle:lifecycle-runtime-ktx:${Versions.androidLifecycleGrouped}"
- const val lifecycleSavedState =
- "androidx.lifecycle:lifecycle-viewmodel-savedstate:${Versions.androidLifecycleGrouped}"
- const val junitJupiterApi = "org.junit.jupiter:junit-jupiter-api:${Versions.junitJupiterApi}"
- const val junitJupiterEngine =
- "org.junit.jupiter:junit-jupiter-engine:${Versions.junitJupiterEngine}"
- const val truth = "com.google.truth:truth:${Versions.truth}"
- const val kotlinJunit5 = "org.jetbrains.kotlin:kotlin-test-junit5:${Versions.kotlin}"
- const val kotlinTest = "org.jetbrains.kotlin:kotlin-test:${Versions.kotlin}"
- const val androidXJunit = "androidx.test.ext:junit:${Versions.androidXJunit}"
-
- const val biometric = "androidx.biometric:biometric:${Versions.biometric}"
-
-}
diff --git a/buildSrc/src/main/kotlin/com/guru/composecookbook/build/dependencies/DependencyHandlerExtensions.kt b/buildSrc/src/main/kotlin/com/guru/composecookbook/build/dependencies/DependencyHandlerExtensions.kt
deleted file mode 100644
index 54a15de4..00000000
--- a/buildSrc/src/main/kotlin/com/guru/composecookbook/build/dependencies/DependencyHandlerExtensions.kt
+++ /dev/null
@@ -1,92 +0,0 @@
-package com.guru.composecookbook.build.dependencies
-
-import org.gradle.api.artifacts.dsl.DependencyHandler
-
-fun DependencyHandler.addComposeOfficialDependencies() {
- composeOfficialDependencies.forEach {
- add("implementation", it)
- }
-}
-
-fun DependencyHandler.addComposeThirdPartyDependencies() {
- composeThirdPartyDependencies.forEach {
- add("implementation", it)
- }
-}
-
-fun DependencyHandler.addComposeDebugDependencies() {
- composeDebugDependencies.forEach {
- add("debugImplementation", it)
- }
-}
-
-fun DependencyHandler.addKotlinDependencies() {
- kotlinDependencies.forEach {
- add("implementation", it)
- }
-}
-
-fun DependencyHandler.addKotlinTestDependencies() {
- kotlinTestDependencies.forEach {
- add("testImplementation", it)
- }
-}
-
-fun DependencyHandler.addDataDependencies() {
- add("ksp", Dependencies.roomCompiler)
- dataDependencies.forEach {
- add("implementation", it)
- }
-}
-
-fun DependencyHandler.addCoreAndroidDependencies() {
- coreAndroidDependencies.forEach {
- add("implementation", it)
- }
-}
-
-fun DependencyHandler.addCoreAndroidUiDependencies() {
- coreAndroidUiDependencies.forEach {
- add("implementation", it)
- }
-}
-
-fun DependencyHandler.addGoogleAndroidDependencies() {
- googleAndroidLibraries.forEach {
- add("implementation", it)
- }
-}
-
-fun DependencyHandler.addNetworkingDependencies() {
- networkingDependencies.forEach {
- add("implementation", it)
- }
-}
-
-fun DependencyHandler.addJunit5TestDependencies() {
- junit5TestDependencies.forEach {
- add("testImplementation", it)
- }
-}
-
-fun DependencyHandler.addThirdPartyUnitTestsDependencies() {
- thirdPartyUnitTestsDependencies.forEach {
- add("testImplementation", it)
- }
-}
-
-fun DependencyHandler.addAndroidInstrumentationTestsDependencies() {
- androidInstrumentationTestsDependencies.forEach {
- add("androidTestImplementation", it)
- }
-}
-
-fun DependencyHandler.addThirdPartyUiDependencies() {
- thirdPartyUiDependencies.forEach {
- add("implementation", it)
- }
-}
-
-fun DependencyHandler.addBiometricDependency() {
- add("implementation", Dependencies.biometric)
-}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/com/guru/composecookbook/build/dependencies/GroupedDependencies.kt b/buildSrc/src/main/kotlin/com/guru/composecookbook/build/dependencies/GroupedDependencies.kt
deleted file mode 100644
index 8cd6ff23..00000000
--- a/buildSrc/src/main/kotlin/com/guru/composecookbook/build/dependencies/GroupedDependencies.kt
+++ /dev/null
@@ -1,99 +0,0 @@
-package com.guru.composecookbook.build.dependencies
-
-internal val composeOfficialDependencies = listOf(
- Dependencies.composeUi,
- Dependencies.composeUiTooling,
- Dependencies.composeMaterial,
- Dependencies.composeMaterial3,
- Dependencies.composeMaterialIconsExtended,
- Dependencies.composeRuntimeLivedata,
- Dependencies.composeConstraintLayout,
- Dependencies.composePaging,
- Dependencies.composeViewModel,
- Dependencies.composeActivity,
- Dependencies.composeNavigation,
- Dependencies.composeRuntime,
-)
-
-internal val composeThirdPartyDependencies = listOf(
- Dependencies.fontAwesomeCompose,
- Dependencies.composeLottie,
- Dependencies.flinger
-)
-
-internal val composeDebugDependencies = listOf(
- Dependencies.kotlinReflect,
- Dependencies.composeUiTooling,
- Dependencies.composeUiTestManifest
-)
-
-internal val kotlinDependencies = listOf(
- Dependencies.kotlinStandardLibraryJdk8,
- Dependencies.coroutinesCore,
- Dependencies.coroutinesAndroid
-)
-
-internal val kotlinTestDependencies = listOf(
- Dependencies.kotlinTest,
- Dependencies.kotlinJunit5
-)
-
-internal val dataDependencies = listOf(
- Dependencies.roomRuntime,
- Dependencies.roomKtx,
- Dependencies.androidPagingRuntime,
- Dependencies.composeRuntime
-)
-
-internal val coreAndroidDependencies = listOf(
- Dependencies.androidMultiDex,
- Dependencies.androidCoreKtx,
- Dependencies.androidAppCompat,
-)
-
-internal val coreAndroidUiDependencies = listOf(
- Dependencies.googleMaterial,
- Dependencies.androidPaletteKtx,
- Dependencies.androidPagingRuntime,
- Dependencies.lifecycleRuntimeKtx,
- Dependencies.lifecycleSavedState,
- Dependencies.liveDataKtx,
- Dependencies.viewModelKtx
-)
-
-internal val googleAndroidLibraries = listOf(
- Dependencies.androidExoPlayer,
- Dependencies.playServicesAds,
- Dependencies.playServicesMaps
- // Removed Dependencies.googleMaps as it's not available
-)
-
-internal val networkingDependencies = listOf(
- Dependencies.retrofit,
- Dependencies.okHttpLoggingInterceptor,
- Dependencies.gson,
- Dependencies.okHttpGsonConverter,
-)
-
-internal val junit5TestDependencies = listOf(
- Dependencies.junitJupiterApi,
- Dependencies.junitJupiterEngine
-)
-
-internal val thirdPartyUnitTestsDependencies = listOf(
- Dependencies.truth,
-)
-
-internal val androidInstrumentationTestsDependencies = listOf(
- Dependencies.composeUiTestJunit4,
- Dependencies.composeActivity,
- Dependencies.androidXJunit,
- Dependencies.composeUiTestManifest
-)
-
-internal val thirdPartyUiDependencies = listOf(
- Dependencies.coilCompose,
- Dependencies.lottie,
- Dependencies.composeLottie
-)
-
diff --git a/buildSrc/src/main/kotlin/com/guru/composecookbook/build/dependencies/Versions.kt b/buildSrc/src/main/kotlin/com/guru/composecookbook/build/dependencies/Versions.kt
deleted file mode 100644
index 270f8411..00000000
--- a/buildSrc/src/main/kotlin/com/guru/composecookbook/build/dependencies/Versions.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.guru.composecookbook.build.dependencies
-
-internal object Versions {
- const val compose = "1.6.1" // Latest stable Compose UI version
- const val activityCompose = "1.8.2" // Latest stable
- const val pagingCompose = "3.2.1" // Latest stable
- const val navCompose = "2.7.7" // Latest stable navigation
- const val constraintLayoutCompose = "1.1.0" // Latest stable
- const val fontAwesomeCompose = "1.1.0" // Keeping as is since it's a third-party lib
- const val coilCompose = "2.5.0" // Latest stable
- const val kotlin = "2.1.20" // Kotlin 2.x with the K2 compiler
- const val coroutines = "1.7.3" // Latest stable
- const val androidLifecycleGrouped = "2.7.0" // Latest stable
- const val flinger = "1.0.5" // Keeping as is since it's a third-party lib
- const val paging = "3.2.1" // Latest stable
- const val lottie = "6.3.0" // Latest stable
- const val room = "2.6.1" // Latest stable
- const val coreKtx = "1.12.0" // Latest stable
- const val appcompat = "1.6.1" // Latest stable
- const val paletteKtx = "1.0.0" // Latest stable
- const val material = "1.11.0" // Latest stable Material Design 1
- const val material3 = "1.2.1" // Latest stable Material 3
- const val playServicesAds = "22.5.0" // Latest stable
- const val googleMaps = "2.0.1" // Latest Google Maps version
- const val playServicesMaps = "18.2.0" // Latest stable
- const val exoplayer = "2.19.1" // Latest stable
- const val loggingInterceptor = "4.12.0" // Latest stable
- const val retrofit = "2.9.0" // Latest stable
- const val gson = "2.10.1" // Latest stable
- const val retrofitConverterGson = "2.9.0" // Latest stable
- const val multidex = "2.0.1" // No new versions expected
- const val junitJupiterApi = "5.10.1" // Latest stable
- const val junitJupiterEngine = "5.10.1" // Latest stable
- const val truth = "1.1.3" // Latest stable
- const val androidXJunit = "1.1.5" // Latest stable
- const val biometric = "1.2.0-alpha05" // Latest stable
-}
diff --git a/buildSrc/src/main/kotlin/common-compose-module-configs-script-plugin.gradle.kts b/buildSrc/src/main/kotlin/common-compose-module-configs-script-plugin.gradle.kts
index e537fbae..f2389f7c 100644
--- a/buildSrc/src/main/kotlin/common-compose-module-configs-script-plugin.gradle.kts
+++ b/buildSrc/src/main/kotlin/common-compose-module-configs-script-plugin.gradle.kts
@@ -12,6 +12,7 @@ android {
defaultConfig {
minSdk = ProjectConfigs.minSdkVersion
targetSdk = ProjectConfigs.targetSdkVersion
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
compileOptions {
diff --git a/components/carousel/build.gradle.kts b/components/carousel/build.gradle.kts
index e2bc39ef..2810b6d4 100644
--- a/components/carousel/build.gradle.kts
+++ b/components/carousel/build.gradle.kts
@@ -1,5 +1,3 @@
-import com.guru.composecookbook.build.dependencies.addComposeOfficialDependencies
-
plugins {
/** See [common-compose-module-configs-script-plugin.gradle.kts] file */
id("common-compose-module-configs-script-plugin")
@@ -11,5 +9,9 @@ dependencies {
implementation(project(":theme"))
implementation(project(":data"))
- addComposeOfficialDependencies()
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.bundles.compose.official)
+
+ androidTestImplementation(platform(libs.androidx.compose.bom))
+ androidTestImplementation(libs.bundles.instrumentation.test)
}
diff --git a/components/carousel/src/androidTest/java/com/guru/composecookbook/carousel/InfiniteCarouselTest.kt b/components/carousel/src/androidTest/java/com/guru/composecookbook/carousel/InfiniteCarouselTest.kt
new file mode 100644
index 00000000..f5124fdf
--- /dev/null
+++ b/components/carousel/src/androidTest/java/com/guru/composecookbook/carousel/InfiniteCarouselTest.kt
@@ -0,0 +1,75 @@
+package com.guru.composecookbook.carousel
+
+import androidx.compose.material3.Text
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.swipeLeft
+import androidx.compose.ui.test.swipeRight
+import com.guru.composecookbook.data.DemoDataProvider
+import org.junit.Rule
+import org.junit.Test
+
+class InfiniteCarouselTest {
+
+ @get:Rule val composeTestRule = createComposeRule()
+
+ private val items = DemoDataProvider.itemList.take(3)
+
+ // Auto advance is effectively disabled so the test controls paging deterministically.
+ private fun setCarouselContent() {
+ composeTestRule.setContent {
+ InfiniteCarousel(items = items, autoAdvanceMillis = Long.MAX_VALUE) { item ->
+ Text(text = item.title)
+ }
+ }
+ }
+
+ @Test
+ fun firstItemIsDisplayedInitially() {
+ setCarouselContent()
+
+ composeTestRule.onNodeWithText(items[0].title).assertIsDisplayed()
+ }
+
+ @Test
+ fun swipingForwardLoopsThroughItems() {
+ setCarouselContent()
+
+ // The gesture must span the pager, not the narrow Text node inside the page.
+ composeTestRule.onRoot().performTouchInput { swipeLeft() }
+ composeTestRule.waitForIdle()
+
+ composeTestRule.onNodeWithText(items[1].title).assertIsDisplayed()
+ }
+
+ @Test
+ fun swipingBackwardFromFirstItemShowsLastItem() {
+ setCarouselContent()
+
+ // Looping backwards past the first item must show the last item.
+ composeTestRule.onRoot().performTouchInput { swipeRight() }
+ composeTestRule.waitForIdle()
+
+ composeTestRule.onNodeWithText(items[2].title).assertIsDisplayed()
+ }
+
+ @Test
+ fun emptyListRendersNothingInsteadOfCrashing() {
+ // Composing at all is the real assertion: without the guard the page-index modulo
+ // divides by zero and this call throws before any assertion is reached.
+ composeTestRule.setContent {
+ InfiniteCarousel(items = emptyList(), autoAdvanceMillis = Long.MAX_VALUE) {
+ Text(text = PAGE_CONTENT)
+ }
+ }
+
+ composeTestRule.onNodeWithText(PAGE_CONTENT).assertDoesNotExist()
+ }
+
+ private companion object {
+ const val PAGE_CONTENT = "page content"
+ }
+}
diff --git a/components/carousel/src/main/java/com/guru/composecookbook/carousel/CarouselLayout.kt b/components/carousel/src/main/java/com/guru/composecookbook/carousel/CarouselLayout.kt
index 807ab9ae..cbb25524 100644
--- a/components/carousel/src/main/java/com/guru/composecookbook/carousel/CarouselLayout.kt
+++ b/components/carousel/src/main/java/com/guru/composecookbook/carousel/CarouselLayout.kt
@@ -38,6 +38,15 @@ fun CarouselLayout() {
val pagerState: PagerState = run { remember { PagerState(1, 0, items.size - 1) } }
val selectedPage = remember { mutableStateOf(2) }
+ Text(
+ text = "Infinite looping carousel (auto advance)",
+ style = typography.body2,
+ modifier = Modifier.padding(horizontal = 24.dp, vertical = 8.dp),
+ )
+ InfiniteCarousel(items = items, modifier = Modifier.height(230.dp)) { item ->
+ CarouselItem(item)
+ }
+
PrepareFirstPager(pagerState, items, selectedPage)
PrepareSecondPager(pagerState, items, selectedPage)
PrepareThirdPager(pagerState, items, selectedPage)
diff --git a/components/carousel/src/main/java/com/guru/composecookbook/carousel/InfiniteCarousel.kt b/components/carousel/src/main/java/com/guru/composecookbook/carousel/InfiniteCarousel.kt
new file mode 100644
index 00000000..db025eeb
--- /dev/null
+++ b/components/carousel/src/main/java/com/guru/composecookbook/carousel/InfiniteCarousel.kt
@@ -0,0 +1,67 @@
+package com.guru.composecookbook.carousel
+
+import androidx.compose.foundation.interaction.collectIsDraggedAsState
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.pager.HorizontalPager
+import androidx.compose.foundation.pager.rememberPagerState
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Lens
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import com.guru.composecookbook.data.model.Item
+import kotlinx.coroutines.delay
+
+/**
+ * An infinitely looping carousel built on top of foundation's [HorizontalPager].
+ *
+ * The pager is given a very large virtual page count and starts in the middle, so the user can
+ * swipe endlessly in both directions; the virtual page index is mapped back onto the real items
+ * with a modulo. While idle it auto-advances every [autoAdvanceMillis]; auto-advance pauses while
+ * the user is dragging.
+ */
+@Composable
+fun InfiniteCarousel(
+ items: List- ,
+ modifier: Modifier = Modifier,
+ autoAdvanceMillis: Long = 3000L,
+ content: @Composable (Item) -> Unit,
+) {
+ // Nothing to page through: render nothing rather than dividing by zero below.
+ if (items.isEmpty()) return
+
+ // Start in the middle of the virtual range, aligned to the first real item.
+ val virtualCount = Int.MAX_VALUE
+ val startPage = remember(items) { virtualCount / 2 - ((virtualCount / 2) % items.size) }
+ val pagerState = rememberPagerState(initialPage = startPage) { virtualCount }
+ val isDragged by pagerState.interactionSource.collectIsDraggedAsState()
+
+ // Auto advance while the user is not interacting with the pager.
+ LaunchedEffect(isDragged, items) {
+ if (!isDragged) {
+ while (true) {
+ delay(autoAdvanceMillis)
+ pagerState.animateScrollToPage(pagerState.currentPage + 1)
+ }
+ }
+ }
+
+ Column(modifier = modifier) {
+ HorizontalPager(state = pagerState) { page -> content(items[page % items.size]) }
+ Row(modifier = Modifier.align(Alignment.CenterHorizontally)) {
+ val selectedIndex = pagerState.currentPage % items.size
+ items.forEachIndexed { index, _ ->
+ CarouselDot(
+ selected = index == selectedIndex,
+ color = MaterialTheme.colorScheme.primary,
+ icon = Icons.Filled.Lens,
+ )
+ }
+ }
+ }
+}
diff --git a/components/charts/build.gradle.kts b/components/charts/build.gradle.kts
index 9f39a0ff..e00b187e 100644
--- a/components/charts/build.gradle.kts
+++ b/components/charts/build.gradle.kts
@@ -1,6 +1,3 @@
-import com.guru.composecookbook.build.dependencies.addComposeOfficialDependencies
-import com.guru.composecookbook.build.dependencies.addCoreAndroidUiDependencies
-
plugins {
/** See [common-compose-module-configs-script-plugin.gradle.kts] file */
id("common-compose-module-configs-script-plugin")
@@ -11,6 +8,7 @@ android { namespace = "com.guru.composecookbook.charts" }
dependencies {
implementation(project(":theme"))
- addComposeOfficialDependencies()
- addCoreAndroidUiDependencies()
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.bundles.compose.official)
+ implementation(libs.bundles.core.android.ui)
}
diff --git a/components/colorpicker/build.gradle.kts b/components/colorpicker/build.gradle.kts
index 7ad277f2..69a99802 100644
--- a/components/colorpicker/build.gradle.kts
+++ b/components/colorpicker/build.gradle.kts
@@ -1,5 +1,3 @@
-import com.guru.composecookbook.build.dependencies.addComposeOfficialDependencies
-
plugins {
/** See [common-compose-module-configs-script-plugin.gradle.kts] file */
id("common-compose-module-configs-script-plugin")
@@ -10,5 +8,6 @@ android { namespace = "com.guru.composecookbook.colorpicker" }
dependencies {
implementation(project(":theme"))
- addComposeOfficialDependencies()
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.bundles.compose.official)
}
diff --git a/components/comingsoon/build.gradle.kts b/components/comingsoon/build.gradle.kts
index 7da56f3c..d73ead82 100644
--- a/components/comingsoon/build.gradle.kts
+++ b/components/comingsoon/build.gradle.kts
@@ -1,5 +1,3 @@
-import com.guru.composecookbook.build.dependencies.addComposeOfficialDependencies
-
plugins {
/** See [common-compose-module-configs-script-plugin.gradle.kts] file */
id("common-compose-module-configs-script-plugin")
@@ -11,5 +9,6 @@ dependencies {
implementation(project(":animations:lottie"))
implementation(project(":theme"))
- addComposeOfficialDependencies()
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.bundles.compose.official)
}
diff --git a/components/fab/build.gradle.kts b/components/fab/build.gradle.kts
index 66b4bb01..611b8635 100644
--- a/components/fab/build.gradle.kts
+++ b/components/fab/build.gradle.kts
@@ -1,5 +1,3 @@
-import com.guru.composecookbook.build.dependencies.addComposeOfficialDependencies
-
plugins {
/** See [common-compose-module-configs-script-plugin.gradle.kts] file */
id("common-compose-module-configs-script-plugin")
@@ -7,4 +5,7 @@ plugins {
android { namespace = "com.guru.composecookbook.fab" }
-dependencies { addComposeOfficialDependencies() }
+dependencies {
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.bundles.compose.official)
+}
diff --git a/components/tags/build.gradle.kts b/components/tags/build.gradle.kts
index e5ba1509..a7a95d54 100644
--- a/components/tags/build.gradle.kts
+++ b/components/tags/build.gradle.kts
@@ -1,5 +1,3 @@
-import com.guru.composecookbook.build.dependencies.addComposeOfficialDependencies
-
plugins {
/** See [common-compose-module-configs-script-plugin.gradle.kts] file */
id("common-compose-module-configs-script-plugin")
@@ -10,5 +8,9 @@ android { namespace = "com.guru.composecookbook.tags" }
dependencies {
implementation(project(":theme"))
- addComposeOfficialDependencies()
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.bundles.compose.official)
+
+ androidTestImplementation(platform(libs.androidx.compose.bom))
+ androidTestImplementation(libs.bundles.instrumentation.test)
}
diff --git a/components/tags/src/androidTest/java/com/guru/composecookbook/tags/InterestTagTest.kt b/components/tags/src/androidTest/java/com/guru/composecookbook/tags/InterestTagTest.kt
new file mode 100644
index 00000000..3d315b55
--- /dev/null
+++ b/components/tags/src/androidTest/java/com/guru/composecookbook/tags/InterestTagTest.kt
@@ -0,0 +1,39 @@
+package com.guru.composecookbook.tags
+
+import androidx.compose.ui.test.assertHasClickAction
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import org.junit.Assert.assertTrue
+import org.junit.Rule
+import org.junit.Test
+
+class InterestTagTest {
+
+ @get:Rule val composeTestRule = createComposeRule()
+
+ @Test
+ fun interestTagDisplaysItsText() {
+ composeTestRule.setContent { InterestTag(text = "Jetpack Compose") }
+
+ composeTestRule.onNodeWithText("Jetpack Compose").assertIsDisplayed()
+ }
+
+ @Test
+ fun interestTagIsClickable() {
+ composeTestRule.setContent { InterestTag(text = "Clickable tag") }
+
+ composeTestRule.onNodeWithText("Clickable tag").assertHasClickAction()
+ }
+
+ @Test
+ fun interestTagInvokesOnClick() {
+ var clicked = false
+ composeTestRule.setContent { InterestTag(text = "Tap me", onClick = { clicked = true }) }
+
+ composeTestRule.onNodeWithText("Tap me").performClick()
+
+ assertTrue(clicked)
+ }
+}
diff --git a/components/verticalgrid/build.gradle.kts b/components/verticalgrid/build.gradle.kts
index 306f95c0..a336b134 100644
--- a/components/verticalgrid/build.gradle.kts
+++ b/components/verticalgrid/build.gradle.kts
@@ -1,5 +1,3 @@
-import com.guru.composecookbook.build.dependencies.addComposeOfficialDependencies
-
plugins {
/** See [common-compose-module-configs-script-plugin.gradle.kts] file */
id("common-compose-module-configs-script-plugin")
@@ -7,4 +5,7 @@ plugins {
android { namespace = "com.guru.composecookbook.verticalgrid" }
-dependencies { addComposeOfficialDependencies() }
+dependencies {
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.bundles.compose.official)
+}
diff --git a/data/build.gradle.kts b/data/build.gradle.kts
index 4e206914..b0e891cb 100644
--- a/data/build.gradle.kts
+++ b/data/build.gradle.kts
@@ -6,5 +6,6 @@ plugins {
android { namespace = "com.guru.composecookbook.data" }
dependencies {
- implementation(com.guru.composecookbook.build.dependencies.Dependencies.composeRuntime)
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.compose.runtime)
}
diff --git a/data/src/main/java/com/guru/composecookbook/data/DemoDataProvider.kt b/data/src/main/java/com/guru/composecookbook/data/DemoDataProvider.kt
index f35390e2..a0590b96 100644
--- a/data/src/main/java/com/guru/composecookbook/data/DemoDataProvider.kt
+++ b/data/src/main/java/com/guru/composecookbook/data/DemoDataProvider.kt
@@ -346,6 +346,8 @@ object DemoDataProvider {
HomeScreenItems.TabLayout,
HomeScreenItems.BottomSheets,
HomeScreenItems.PullRefresh,
+ HomeScreenItems.AdaptiveUI,
+ HomeScreenItems.PredictiveBack,
)
const val longText =
diff --git a/data/src/main/java/com/guru/composecookbook/data/model/HomeScreenItems.kt b/data/src/main/java/com/guru/composecookbook/data/model/HomeScreenItems.kt
index e8303151..515eebd3 100644
--- a/data/src/main/java/com/guru/composecookbook/data/model/HomeScreenItems.kt
+++ b/data/src/main/java/com/guru/composecookbook/data/model/HomeScreenItems.kt
@@ -31,6 +31,10 @@ sealed class HomeScreenItems {
object MotionLayout : HomeScreenItems()
+ object AdaptiveUI : HomeScreenItems()
+
+ object PredictiveBack : HomeScreenItems()
+
val name: String
get() =
when (this) {
@@ -49,5 +53,7 @@ sealed class HomeScreenItems {
AdvanceLists -> "Advance Lists"
PullRefresh -> "Pull refresh demos"
CustomFling -> "Custom Fling"
+ AdaptiveUI -> "Adaptive UI (Large screens)"
+ PredictiveBack -> "Predictive Back gesture"
}
}
diff --git a/demos/cryptoapp/app/build.gradle.kts b/demos/cryptoapp/app/build.gradle.kts
index b3e6ede1..7d2f1282 100644
--- a/demos/cryptoapp/app/build.gradle.kts
+++ b/demos/cryptoapp/app/build.gradle.kts
@@ -1,9 +1,3 @@
-import com.guru.composecookbook.build.dependencies.addComposeOfficialDependencies
-import com.guru.composecookbook.build.dependencies.addComposeThirdPartyDependencies
-import com.guru.composecookbook.build.dependencies.addCoreAndroidDependencies
-import com.guru.composecookbook.build.dependencies.addCoreAndroidUiDependencies
-import com.guru.composecookbook.build.dependencies.addThirdPartyUiDependencies
-
plugins {
/** See [common-compose-module-configs-script-plugin.gradle.kts] file */
id("common-compose-module-configs-script-plugin")
@@ -18,25 +12,17 @@ dependencies {
implementation(project(":demos:cryptoapp:data"))
implementation(project(":theme"))
implementation(project(":data"))
- implementation("androidx.wear:wear:1.2.0")
-
- addComposeOfficialDependencies()
- addComposeThirdPartyDependencies()
-
- addCoreAndroidUiDependencies()
- addThirdPartyUiDependencies()
-
- addCoreAndroidDependencies()
+ // Wear OS (compose-material here is the Wear variant, not the mobile one)
+ implementation(libs.bundles.wear)
- val wear_compose_version = "1.0.0-alpha07"
- implementation("androidx.wear.compose:compose-material:$wear_compose_version")
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.bundles.compose.official)
+ implementation(libs.bundles.compose.thirdparty)
- // Foundation is additive, so you can use the mobile version in your Wear OS app.
- implementation("androidx.wear.compose:compose-foundation:$wear_compose_version")
+ implementation(libs.bundles.core.android.ui)
+ implementation(libs.bundles.thirdparty.ui)
- // If you are using Compose Navigation, use the Wear OS version (NOT THE MOBILE ONE), that is,
- // uncomment the line below and update the version number.
- implementation("androidx.wear.compose:compose-navigation:$wear_compose_version")
+ implementation(libs.bundles.core.android)
}
android { buildFeatures { viewBinding = true } }
diff --git a/demos/cryptoapp/app/src/main/java/com/guru/composecookbook/cryptoapp/ui/composewear/AndroidWearActivity.kt b/demos/cryptoapp/app/src/main/java/com/guru/composecookbook/cryptoapp/ui/composewear/AndroidWearActivity.kt
index c87a1a1f..96c4f286 100644
--- a/demos/cryptoapp/app/src/main/java/com/guru/composecookbook/cryptoapp/ui/composewear/AndroidWearActivity.kt
+++ b/demos/cryptoapp/app/src/main/java/com/guru/composecookbook/cryptoapp/ui/composewear/AndroidWearActivity.kt
@@ -43,7 +43,7 @@ import androidx.wear.compose.material.rememberScalingLazyListState
import androidx.wear.compose.navigation.SwipeDismissableNavHost
import androidx.wear.compose.navigation.composable
import androidx.wear.compose.navigation.rememberSwipeDismissableNavController
-import coil.compose.rememberImagePainter
+import coil3.compose.rememberAsyncImagePainter
import com.guru.composecookbook.charts.LineChart
import com.guru.composecookbook.cryptoapp.data.db.models.Crypto
import com.guru.composecookbook.cryptoapp.ui.home.CryptoHomeViewModel
@@ -129,7 +129,7 @@ fun WearStockListItem(crypto: Crypto, onItemSelected: (String) -> Unit) {
Card(onClick = { onItemSelected.invoke(crypto.symbol) }, shape = MaterialTheme.shapes.small) {
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
Image(
- painter = rememberImagePainter(data = crypto.image),
+ painter = rememberAsyncImagePainter(model = crypto.image),
modifier = Modifier.size(32.dp).padding(4.dp),
contentDescription = null,
contentScale = ContentScale.Crop,
@@ -159,7 +159,7 @@ fun StockDetailScreen(crypto: Crypto) {
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Image(
- painter = rememberImagePainter(data = crypto.image),
+ painter = rememberAsyncImagePainter(model = crypto.image),
modifier = Modifier.size(24.dp).padding(end = 8.dp),
contentDescription = null,
contentScale = ContentScale.Crop,
diff --git a/demos/cryptoapp/app/src/main/java/com/guru/composecookbook/cryptoapp/ui/detail/components/CryptoDetailScreen.kt b/demos/cryptoapp/app/src/main/java/com/guru/composecookbook/cryptoapp/ui/detail/components/CryptoDetailScreen.kt
index 25fe5324..417fab17 100644
--- a/demos/cryptoapp/app/src/main/java/com/guru/composecookbook/cryptoapp/ui/detail/components/CryptoDetailScreen.kt
+++ b/demos/cryptoapp/app/src/main/java/com/guru/composecookbook/cryptoapp/ui/detail/components/CryptoDetailScreen.kt
@@ -48,7 +48,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
-import coil.compose.rememberImagePainter
+import coil3.compose.rememberAsyncImagePainter
import com.guru.composecookbook.charts.BarCharts
import com.guru.composecookbook.charts.LineChart
import com.guru.composecookbook.cryptoapp.R
@@ -102,7 +102,7 @@ fun CryptoTopSection(crypto: Crypto, scrollState: ScrollState) {
Row(modifier = Modifier.padding(top = 20.dp)) {
Text(text = crypto.name, style = typography.h6, modifier = Modifier.padding(end = 8.dp))
Image(
- painter = rememberImagePainter(data = crypto.image),
+ painter = rememberAsyncImagePainter(model = crypto.image),
modifier = Modifier.size(28.dp),
contentDescription = null,
)
diff --git a/demos/cryptoapp/app/src/main/java/com/guru/composecookbook/cryptoapp/ui/detail/components/FavoriteCryptoCard.kt b/demos/cryptoapp/app/src/main/java/com/guru/composecookbook/cryptoapp/ui/detail/components/FavoriteCryptoCard.kt
index 78f19924..7696f18c 100644
--- a/demos/cryptoapp/app/src/main/java/com/guru/composecookbook/cryptoapp/ui/detail/components/FavoriteCryptoCard.kt
+++ b/demos/cryptoapp/app/src/main/java/com/guru/composecookbook/cryptoapp/ui/detail/components/FavoriteCryptoCard.kt
@@ -14,7 +14,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import coil.compose.rememberImagePainter
+import coil3.compose.rememberAsyncImagePainter
import com.guru.composecookbook.cryptoapp.data.CryptoDemoDataProvider
import com.guru.composecookbook.cryptoapp.data.db.models.Crypto
import com.guru.composecookbook.cryptoapp.ui.internal.extensions.roundToTwoDecimals
@@ -27,7 +27,7 @@ fun FavoriteCryptoCard(crypto: Crypto) {
Column(modifier = Modifier.padding(16.dp).width(120.dp).height(180.dp)) {
Row(modifier = Modifier.weight(1f)) {
Image(
- painter = rememberImagePainter(data = crypto.image),
+ painter = rememberAsyncImagePainter(model = crypto.image),
modifier = Modifier.size(24.dp),
contentDescription = null,
)
diff --git a/demos/cryptoapp/app/src/main/java/com/guru/composecookbook/cryptoapp/ui/home/components/CryptoHomeScreen.kt b/demos/cryptoapp/app/src/main/java/com/guru/composecookbook/cryptoapp/ui/home/components/CryptoHomeScreen.kt
index 6abeeac6..15b2b8e7 100644
--- a/demos/cryptoapp/app/src/main/java/com/guru/composecookbook/cryptoapp/ui/home/components/CryptoHomeScreen.kt
+++ b/demos/cryptoapp/app/src/main/java/com/guru/composecookbook/cryptoapp/ui/home/components/CryptoHomeScreen.kt
@@ -47,7 +47,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.paging.LoadState
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems
-import coil.compose.rememberImagePainter
+import coil3.compose.rememberAsyncImagePainter
import com.guru.composecookbook.cryptoapp.data.CryptoDemoDataProvider
import com.guru.composecookbook.cryptoapp.data.db.models.Crypto
import com.guru.composecookbook.cryptoapp.ui.home.CryptoHomeInteractionEvents
@@ -147,7 +147,7 @@ fun FavoriteItem(crypto: Crypto, openCryptoDetail: () -> Unit) {
verticalAlignment = Alignment.CenterVertically,
) {
Image(
- painter = rememberImagePainter(data = crypto.image),
+ painter = rememberAsyncImagePainter(model = crypto.image),
modifier = Modifier.size(24.dp),
contentDescription = null,
)
diff --git a/demos/cryptoapp/app/src/main/java/com/guru/composecookbook/cryptoapp/ui/home/components/CryptoListItem.kt b/demos/cryptoapp/app/src/main/java/com/guru/composecookbook/cryptoapp/ui/home/components/CryptoListItem.kt
index 8b4b1740..198dd235 100644
--- a/demos/cryptoapp/app/src/main/java/com/guru/composecookbook/cryptoapp/ui/home/components/CryptoListItem.kt
+++ b/demos/cryptoapp/app/src/main/java/com/guru/composecookbook/cryptoapp/ui/home/components/CryptoListItem.kt
@@ -25,7 +25,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
-import coil.compose.AsyncImage
+import coil3.compose.AsyncImage
import com.guru.composecookbook.charts.LineChart
import com.guru.composecookbook.cryptoapp.data.CryptoDemoDataProvider
import com.guru.composecookbook.cryptoapp.data.db.models.Crypto
diff --git a/demos/cryptoapp/data/build.gradle.kts b/demos/cryptoapp/data/build.gradle.kts
index dc2390d4..d0af3dad 100644
--- a/demos/cryptoapp/data/build.gradle.kts
+++ b/demos/cryptoapp/data/build.gradle.kts
@@ -1,7 +1,3 @@
-import com.guru.composecookbook.build.dependencies.addCoreAndroidDependencies
-import com.guru.composecookbook.build.dependencies.addDataDependencies
-import com.guru.composecookbook.build.dependencies.addNetworkingDependencies
-
plugins {
/** See [common-kotlin-module-configs-script-plugin.gradle.kts] file */
id("common-kotlin-module-configs-script-plugin")
@@ -10,7 +6,9 @@ plugins {
android { namespace = "com.guru.composecookbook.cryptoapp.data" }
dependencies {
- addCoreAndroidDependencies()
- addNetworkingDependencies()
- addDataDependencies()
+ implementation(libs.bundles.core.android)
+ implementation(libs.bundles.networking)
+ implementation(platform(libs.androidx.compose.bom))
+ ksp(libs.androidx.room.compiler)
+ implementation(libs.bundles.data)
}
diff --git a/demos/datingapp/build.gradle.kts b/demos/datingapp/build.gradle.kts
index 68a5f29d..7cb8642f 100644
--- a/demos/datingapp/build.gradle.kts
+++ b/demos/datingapp/build.gradle.kts
@@ -1,6 +1,3 @@
-import com.guru.composecookbook.build.dependencies.addComposeOfficialDependencies
-import com.guru.composecookbook.build.dependencies.addCoreAndroidUiDependencies
-
plugins {
/** See [common-compose-module-configs-script-plugin.gradle.kts] file */
id("common-compose-module-configs-script-plugin")
@@ -14,6 +11,7 @@ dependencies {
implementation(project(":data"))
implementation(project(":theme"))
- addComposeOfficialDependencies()
- addCoreAndroidUiDependencies()
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.bundles.compose.official)
+ implementation(libs.bundles.core.android.ui)
}
diff --git a/demos/datingapp/src/main/java/com/guru/composecookbook/datingapp/DatingHomeActivity.kt b/demos/datingapp/src/main/java/com/guru/composecookbook/datingapp/DatingHomeActivity.kt
index e37eb3d8..659b7501 100644
--- a/demos/datingapp/src/main/java/com/guru/composecookbook/datingapp/DatingHomeActivity.kt
+++ b/demos/datingapp/src/main/java/com/guru/composecookbook/datingapp/DatingHomeActivity.kt
@@ -21,8 +21,8 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold
-import androidx.compose.material3.SmallTopAppBar
import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
@@ -115,7 +115,7 @@ fun DatingHomeAppbar(navType: MutableState) {
DatingNavType.CHATS -> "Chats"
DatingNavType.PROFILE -> "My Profile"
}
- SmallTopAppBar(
+ TopAppBar(
title = { Text(title, style = typography.h6) },
actions = { IconButton(onClick = {}) { Icons.Default.MyLocation } },
)
diff --git a/demos/gmail/build.gradle.kts b/demos/gmail/build.gradle.kts
index 8007c68a..fd38254c 100644
--- a/demos/gmail/build.gradle.kts
+++ b/demos/gmail/build.gradle.kts
@@ -1,9 +1,7 @@
-import com.guru.composecookbook.build.dependencies.addComposeOfficialDependencies
-import com.guru.composecookbook.build.dependencies.addCoreAndroidUiDependencies
-
plugins {
/** See [common-compose-module-configs-script-plugin.gradle.kts] file */
id("common-compose-module-configs-script-plugin")
+ id("org.jetbrains.kotlin.plugin.serialization")
}
android { namespace = "com.guru.composecookbook.gmail" }
@@ -13,6 +11,8 @@ dependencies {
implementation(project(":data"))
implementation(project(":theme"))
- addComposeOfficialDependencies()
- addCoreAndroidUiDependencies()
+ implementation(libs.bundles.kotlin)
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.bundles.compose.official)
+ implementation(libs.bundles.core.android.ui)
}
diff --git a/demos/gmail/src/main/java/com/guru/composecookbook/gmail/ui/home/GmailHome.kt b/demos/gmail/src/main/java/com/guru/composecookbook/gmail/ui/home/GmailHome.kt
index c3010872..494fffb6 100644
--- a/demos/gmail/src/main/java/com/guru/composecookbook/gmail/ui/home/GmailHome.kt
+++ b/demos/gmail/src/main/java/com/guru/composecookbook/gmail/ui/home/GmailHome.kt
@@ -68,16 +68,24 @@ import com.guru.composecookbook.gmail.ui.details.MessageDetailScreen
import com.guru.composecookbook.theme.graySurface
import com.guru.composecookbook.theme.green500
import kotlin.math.absoluteValue
+import kotlinx.serialization.Serializable
+
+/** Type-safe navigation routes for the Gmail demo. */
+@Serializable object GmailHomeRoute
+
+@Serializable object GmailDetailRoute
+
+@Serializable object GmailCreateRoute
@Composable
fun GmailScreen() {
val navController = rememberNavController()
- NavHost(navController = navController, startDestination = "home") {
- composable("home") { GmailHome(navController = navController) }
+ NavHost(navController = navController, startDestination = GmailHomeRoute) {
+ composable { GmailHome(navController = navController) }
- composable("detail") { MessageDetailScreen(navController = navController) }
+ composable { MessageDetailScreen(navController = navController) }
- composable("create") { CreateMessageScreen(navController = navController) }
+ composable { CreateMessageScreen(navController = navController) }
}
}
@@ -157,7 +165,7 @@ fun IconWithBadge(badge: Int, icon: ImageVector, modifier: Modifier = Modifier)
fun GmailFloatingActionButton(navController: NavHostController, expandState: Boolean) {
FloatingActionButton(
- onClick = { navController.navigate("create") },
+ onClick = { navController.navigate(GmailCreateRoute) },
modifier = Modifier.padding(16.dp).height(48.dp).widthIn(min = 48.dp),
backgroundColor = MaterialTheme.colors.surface,
contentColor = MaterialTheme.colors.primary,
@@ -241,14 +249,14 @@ fun GmailContent(
AnimatedVisibility(visible = visible.value) {
Box(modifier = Modifier.background(green500)) {
GmailListActionItems(modifier = Modifier.align(Alignment.CenterEnd))
- GmailListItem(it) { navController.navigate("detail") }
+ GmailListItem(it) { navController.navigate(GmailDetailRoute) }
}
}
}
}
SearchLayout(searchOffsetY.value, scaffoldState.drawerState, showUserDialog) {
- navController.navigate("create")
+ navController.navigate(GmailCreateRoute)
}
}
}
diff --git a/demos/instagram/build.gradle.kts b/demos/instagram/build.gradle.kts
index 8903bc93..0524875a 100644
--- a/demos/instagram/build.gradle.kts
+++ b/demos/instagram/build.gradle.kts
@@ -1,7 +1,3 @@
-import com.guru.composecookbook.build.dependencies.addComposeOfficialDependencies
-import com.guru.composecookbook.build.dependencies.addComposeThirdPartyDependencies
-import com.guru.composecookbook.build.dependencies.addCoreAndroidUiDependencies
-
plugins {
/** See [common-compose-module-configs-script-plugin.gradle.kts] file */
id("common-compose-module-configs-script-plugin")
@@ -13,7 +9,8 @@ dependencies {
implementation(project(":data"))
implementation(project(":theme"))
- addComposeOfficialDependencies()
- addComposeThirdPartyDependencies()
- addCoreAndroidUiDependencies()
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.bundles.compose.official)
+ implementation(libs.bundles.compose.thirdparty)
+ implementation(libs.bundles.core.android.ui)
}
diff --git a/demos/instagram/src/main/java/com/guru/composecookbook/instagram/components/stories/StoryItem.kt b/demos/instagram/src/main/java/com/guru/composecookbook/instagram/components/stories/StoryItem.kt
index 3ee365fa..01e0e3d3 100644
--- a/demos/instagram/src/main/java/com/guru/composecookbook/instagram/components/stories/StoryItem.kt
+++ b/demos/instagram/src/main/java/com/guru/composecookbook/instagram/components/stories/StoryItem.kt
@@ -8,8 +8,8 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.MaterialTheme
-import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.Text
+import androidx.compose.material3.ripple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
@@ -59,7 +59,7 @@ fun StoryItem(
)
.clickable(
interactionSource = interactionSource,
- indication = rememberRipple(bounded = false, radius = ProfileSizes.large / 2),
+ indication = ripple(bounded = false, radius = ProfileSizes.large / 2),
enabled = true,
onClickLabel = null,
onClick = onClick,
diff --git a/demos/meditation/build.gradle.kts b/demos/meditation/build.gradle.kts
index 16e51963..5c5f380e 100644
--- a/demos/meditation/build.gradle.kts
+++ b/demos/meditation/build.gradle.kts
@@ -1,6 +1,3 @@
-import com.guru.composecookbook.build.dependencies.addComposeOfficialDependencies
-import com.guru.composecookbook.build.dependencies.addCoreAndroidUiDependencies
-
plugins {
/** See [common-compose-module-configs-script-plugin.gradle.kts] file */
id("common-compose-module-configs-script-plugin")
@@ -12,10 +9,11 @@ dependencies {
implementation(project(":theme"))
implementation(project(":components:verticalgrid"))
- addComposeOfficialDependencies()
- addCoreAndroidUiDependencies()
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.bundles.compose.official)
+ implementation(libs.bundles.core.android.ui)
// Test dependencies
- testImplementation("junit:junit:4.13.2")
- androidTestImplementation("androidx.test.ext:junit:1.1.5")
+ testImplementation(libs.junit)
+ androidTestImplementation(libs.androidx.test.junit)
}
diff --git a/demos/moviesapp/app/build.gradle.kts b/demos/moviesapp/app/build.gradle.kts
index 8dda942d..9b0c301c 100644
--- a/demos/moviesapp/app/build.gradle.kts
+++ b/demos/moviesapp/app/build.gradle.kts
@@ -1,9 +1,3 @@
-import com.guru.composecookbook.build.dependencies.addComposeOfficialDependencies
-import com.guru.composecookbook.build.dependencies.addComposeThirdPartyDependencies
-import com.guru.composecookbook.build.dependencies.addCoreAndroidDependencies
-import com.guru.composecookbook.build.dependencies.addCoreAndroidUiDependencies
-import com.guru.composecookbook.build.dependencies.addThirdPartyUiDependencies
-
plugins {
/** See [common-compose-module-configs-script-plugin.gradle.kts] file */
id("common-compose-module-configs-script-plugin")
@@ -18,11 +12,12 @@ dependencies {
implementation(project(":theme"))
implementation(project(":data"))
- addComposeOfficialDependencies()
- addComposeThirdPartyDependencies()
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.bundles.compose.official)
+ implementation(libs.bundles.compose.thirdparty)
- addCoreAndroidUiDependencies()
- addThirdPartyUiDependencies()
+ implementation(libs.bundles.core.android.ui)
+ implementation(libs.bundles.thirdparty.ui)
- addCoreAndroidDependencies()
+ implementation(libs.bundles.core.android)
}
diff --git a/demos/moviesapp/app/src/main/java/com/guru/composecookbook/moviesapp/ui/details/MovieDetailActivity.kt b/demos/moviesapp/app/src/main/java/com/guru/composecookbook/moviesapp/ui/details/MovieDetailActivity.kt
index 8d71bb93..4f29f0cb 100644
--- a/demos/moviesapp/app/src/main/java/com/guru/composecookbook/moviesapp/ui/details/MovieDetailActivity.kt
+++ b/demos/moviesapp/app/src/main/java/com/guru/composecookbook/moviesapp/ui/details/MovieDetailActivity.kt
@@ -6,7 +6,7 @@ import android.os.Bundle
import android.view.WindowManager
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
-import coil.annotation.ExperimentalCoilApi
+import coil3.annotation.ExperimentalCoilApi
import com.guru.composecookbook.moviesapp.data.db.models.Movie
import com.guru.composecookbook.moviesapp.ui.details.components.MovieDetailContent
import com.guru.composecookbook.theme.ComposeCookBookTheme
diff --git a/demos/moviesapp/app/src/main/java/com/guru/composecookbook/moviesapp/ui/details/components/MovieDetailContent.kt b/demos/moviesapp/app/src/main/java/com/guru/composecookbook/moviesapp/ui/details/components/MovieDetailContent.kt
index 947c50e8..c7982651 100644
--- a/demos/moviesapp/app/src/main/java/com/guru/composecookbook/moviesapp/ui/details/components/MovieDetailContent.kt
+++ b/demos/moviesapp/app/src/main/java/com/guru/composecookbook/moviesapp/ui/details/components/MovieDetailContent.kt
@@ -1,118 +1,118 @@
-package com.guru.composecookbook.moviesapp.ui.details.components
-
-import androidx.compose.animation.core.animateDpAsState
-import androidx.compose.animation.core.tween
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.material.Button
-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.LibraryAdd
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.ImageBitmap
-import androidx.compose.ui.graphics.asAndroidBitmap
-import androidx.compose.ui.layout.ContentScale
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.imageResource
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import androidx.lifecycle.viewmodel.compose.viewModel
-import coil.compose.AsyncImagePainter
-import coil.compose.rememberAsyncImagePainter
-import com.guru.composecookbook.moviesapp.data.db.models.Movie
-import com.guru.composecookbook.moviesapp.ui.details.MovieDetailViewModel
-import com.guru.composecookbook.moviesapp.ui.details.MovieDetailViewModelFactory
-import com.guru.composecookbook.theme.extensions.generateDominantColorState
-import com.guru.composecookbook.theme.graySurface
-import com.guru.composecookbook.theme.modifiers.verticalGradientBackground
-import com.guru.composecookbook.theme.typography
-
-@Composable
-fun MovieDetailContent(movie: Movie, imageId: Int) {
- val expand = remember { mutableStateOf(false) }
- val viewModel: MovieDetailViewModel =
- viewModel(factory = MovieDetailViewModelFactory(LocalContext.current))
- var dominantColors = listOf(graySurface, Color.Black)
-
- if (imageId != 0) {
- val context = LocalContext.current
- val currentBitmap = ImageBitmap.imageResource(context.resources, imageId)
-
- val swatch = currentBitmap.asAndroidBitmap().generateDominantColorState()
- dominantColors = listOf(Color(swatch.rgb), Color.Black)
- }
-
- LazyColumn(
- modifier =
- Modifier.verticalGradientBackground(dominantColors)
- .padding(animateDpAsState(if (expand.value) 1.dp else 120.dp, tween(350)).value)
- ) {
- item {
- val painter =
- rememberAsyncImagePainter(model = "https://image.tmdb.org/t/p/w500/${movie.poster_path}")
- Image(
- painter = painter,
- contentScale = ContentScale.Crop,
- contentDescription = null,
- modifier = Modifier.height(600.dp).fillMaxWidth(),
- )
- when (painter.state) {
- is AsyncImagePainter.State.Success -> expand.value = true
- else -> expand.value = false
- }
- }
- item {
- Column(modifier = Modifier.background(MaterialTheme.colors.onSurface)) {
- Row(
- horizontalArrangement = Arrangement.SpaceBetween,
- verticalAlignment = Alignment.CenterVertically,
- modifier = Modifier.fillMaxWidth(),
- ) {
- Text(text = movie.title, modifier = Modifier.padding(8.dp), style = typography.h6)
- IconButton(onClick = {}) {
- Icon(
- imageVector = Icons.Default.LibraryAdd,
- contentDescription = null,
- tint = MaterialTheme.colors.primary,
- )
- }
- }
- GenreSection(viewModel, movie.genre_ids)
- Text(
- text = "Release: ${movie.release_date}",
- modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp),
- style = typography.h6.copy(fontSize = 12.sp),
- )
- Text(
- text = "PG13 • ${movie.vote_average}/10",
- modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp),
- style = typography.h6.copy(fontSize = 12.sp, fontWeight = FontWeight.Medium),
- )
- Text(text = movie.overview, modifier = Modifier.padding(8.dp), style = typography.subtitle2)
- Spacer(modifier = Modifier.height(20.dp))
- SimilarMoviesSection(movie, viewModel)
- Spacer(modifier = Modifier.height(50.dp))
- Button(onClick = {}, modifier = Modifier.fillMaxWidth()) {
- Text(text = "Get Tickets", modifier = Modifier.padding(8.dp))
- }
- }
- }
- }
-}
+package com.guru.composecookbook.moviesapp.ui.details.components
+
+import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.material.Button
+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.LibraryAdd
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.asAndroidBitmap
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.imageResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import coil3.compose.AsyncImagePainter
+import coil3.compose.rememberAsyncImagePainter
+import com.guru.composecookbook.moviesapp.data.db.models.Movie
+import com.guru.composecookbook.moviesapp.ui.details.MovieDetailViewModel
+import com.guru.composecookbook.moviesapp.ui.details.MovieDetailViewModelFactory
+import com.guru.composecookbook.theme.extensions.generateDominantColorState
+import com.guru.composecookbook.theme.graySurface
+import com.guru.composecookbook.theme.modifiers.verticalGradientBackground
+import com.guru.composecookbook.theme.typography
+
+@Composable
+fun MovieDetailContent(movie: Movie, imageId: Int) {
+ val expand = remember { mutableStateOf(false) }
+ val viewModel: MovieDetailViewModel =
+ viewModel(factory = MovieDetailViewModelFactory(LocalContext.current))
+ var dominantColors = listOf(graySurface, Color.Black)
+
+ if (imageId != 0) {
+ val context = LocalContext.current
+ val currentBitmap = ImageBitmap.imageResource(context.resources, imageId)
+
+ val swatch = currentBitmap.asAndroidBitmap().generateDominantColorState()
+ dominantColors = listOf(Color(swatch.rgb), Color.Black)
+ }
+
+ LazyColumn(
+ modifier =
+ Modifier.verticalGradientBackground(dominantColors)
+ .padding(animateDpAsState(if (expand.value) 1.dp else 120.dp, tween(350)).value)
+ ) {
+ item {
+ val painter =
+ rememberAsyncImagePainter(model = "https://image.tmdb.org/t/p/w500/${movie.poster_path}")
+ Image(
+ painter = painter,
+ contentScale = ContentScale.Crop,
+ contentDescription = null,
+ modifier = Modifier.height(600.dp).fillMaxWidth(),
+ )
+ when (painter.state) {
+ is AsyncImagePainter.State.Success -> expand.value = true
+ else -> expand.value = false
+ }
+ }
+ item {
+ Column(modifier = Modifier.background(MaterialTheme.colors.onSurface)) {
+ Row(
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.fillMaxWidth(),
+ ) {
+ Text(text = movie.title, modifier = Modifier.padding(8.dp), style = typography.h6)
+ IconButton(onClick = {}) {
+ Icon(
+ imageVector = Icons.Default.LibraryAdd,
+ contentDescription = null,
+ tint = MaterialTheme.colors.primary,
+ )
+ }
+ }
+ GenreSection(viewModel, movie.genre_ids)
+ Text(
+ text = "Release: ${movie.release_date}",
+ modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp),
+ style = typography.h6.copy(fontSize = 12.sp),
+ )
+ Text(
+ text = "PG13 • ${movie.vote_average}/10",
+ modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp),
+ style = typography.h6.copy(fontSize = 12.sp, fontWeight = FontWeight.Medium),
+ )
+ Text(text = movie.overview, modifier = Modifier.padding(8.dp), style = typography.subtitle2)
+ Spacer(modifier = Modifier.height(20.dp))
+ SimilarMoviesSection(movie, viewModel)
+ Spacer(modifier = Modifier.height(50.dp))
+ Button(onClick = {}, modifier = Modifier.fillMaxWidth()) {
+ Text(text = "Get Tickets", modifier = Modifier.padding(8.dp))
+ }
+ }
+ }
+ }
+}
diff --git a/demos/moviesapp/app/src/main/java/com/guru/composecookbook/moviesapp/ui/details/components/SimilarMoviesSection.kt b/demos/moviesapp/app/src/main/java/com/guru/composecookbook/moviesapp/ui/details/components/SimilarMoviesSection.kt
index 2da4e797..7da79a33 100644
--- a/demos/moviesapp/app/src/main/java/com/guru/composecookbook/moviesapp/ui/details/components/SimilarMoviesSection.kt
+++ b/demos/moviesapp/app/src/main/java/com/guru/composecookbook/moviesapp/ui/details/components/SimilarMoviesSection.kt
@@ -1,45 +1,47 @@
-package com.guru.composecookbook.moviesapp.ui.details.components
-
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.lazy.LazyRow
-import androidx.compose.foundation.lazy.items
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.livedata.observeAsState
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.layout.ContentScale
-import androidx.compose.ui.unit.dp
-import coil.compose.rememberImagePainter
-import com.guru.composecookbook.moviesapp.data.db.models.Movie
-import com.guru.composecookbook.moviesapp.ui.details.MovieDetailViewModel
-import com.guru.composecookbook.theme.typography
-
-@Composable
-fun SimilarMoviesSection(currentMovie: Movie?, viewModel: MovieDetailViewModel) {
- viewModel.getSimilarMovies(currentMovie?.id.toString())
- val similarMovies by viewModel.similarMoviesLiveData.observeAsState()
- similarMovies?.let { movies ->
- Text(text = "Similar Movies", style = typography.h5, modifier = Modifier.padding(8.dp))
- LazyRow {
- items(
- items = movies,
- itemContent = { movie: Movie ->
- Image(
- painter =
- rememberImagePainter(data = "https://image.tmdb.org/t/p/w500/${movie.poster_path}"),
- contentDescription = null,
- modifier =
- Modifier.width(200.dp).height(300.dp).padding(12.dp).clip(RoundedCornerShape(12.dp)),
- contentScale = ContentScale.Crop,
- )
- },
- )
- }
- }
-}
+package com.guru.composecookbook.moviesapp.ui.details.components
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.unit.dp
+import coil3.compose.rememberAsyncImagePainter
+import com.guru.composecookbook.moviesapp.data.db.models.Movie
+import com.guru.composecookbook.moviesapp.ui.details.MovieDetailViewModel
+import com.guru.composecookbook.theme.typography
+
+@Composable
+fun SimilarMoviesSection(currentMovie: Movie?, viewModel: MovieDetailViewModel) {
+ viewModel.getSimilarMovies(currentMovie?.id.toString())
+ val similarMovies by viewModel.similarMoviesLiveData.observeAsState()
+ similarMovies?.let { movies ->
+ Text(text = "Similar Movies", style = typography.h5, modifier = Modifier.padding(8.dp))
+ LazyRow {
+ items(
+ items = movies,
+ itemContent = { movie: Movie ->
+ Image(
+ painter =
+ rememberAsyncImagePainter(
+ model = "https://image.tmdb.org/t/p/w500/${movie.poster_path}"
+ ),
+ contentDescription = null,
+ modifier =
+ Modifier.width(200.dp).height(300.dp).padding(12.dp).clip(RoundedCornerShape(12.dp)),
+ contentScale = ContentScale.Crop,
+ )
+ },
+ )
+ }
+ }
+}
diff --git a/demos/moviesapp/app/src/main/java/com/guru/composecookbook/moviesapp/ui/home/MoviesHomeActivity.kt b/demos/moviesapp/app/src/main/java/com/guru/composecookbook/moviesapp/ui/home/MoviesHomeActivity.kt
index 535da41b..93efedf0 100644
--- a/demos/moviesapp/app/src/main/java/com/guru/composecookbook/moviesapp/ui/home/MoviesHomeActivity.kt
+++ b/demos/moviesapp/app/src/main/java/com/guru/composecookbook/moviesapp/ui/home/MoviesHomeActivity.kt
@@ -26,7 +26,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.viewmodel.compose.viewModel
-import coil.annotation.ExperimentalCoilApi
+import coil3.annotation.ExperimentalCoilApi
import com.guru.composecookbook.moviesapp.data.db.models.Movie
import com.guru.composecookbook.moviesapp.ui.details.MovieDetailActivity
import com.guru.composecookbook.moviesapp.ui.home.components.MovieHomeScreen
diff --git a/demos/moviesapp/app/src/main/java/com/guru/composecookbook/moviesapp/ui/home/components/MoviePagerItem.kt b/demos/moviesapp/app/src/main/java/com/guru/composecookbook/moviesapp/ui/home/components/MoviePagerItem.kt
index 334e5281..cbe3f0ec 100644
--- a/demos/moviesapp/app/src/main/java/com/guru/composecookbook/moviesapp/ui/home/components/MoviePagerItem.kt
+++ b/demos/moviesapp/app/src/main/java/com/guru/composecookbook/moviesapp/ui/home/components/MoviePagerItem.kt
@@ -33,7 +33,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
-import coil.compose.rememberImagePainter
+import coil3.compose.rememberAsyncImagePainter
import com.guru.composecookbook.moviesapp.data.DemoMovieDataProvider
import com.guru.composecookbook.moviesapp.data.db.models.Genre
import com.guru.composecookbook.moviesapp.data.db.models.Movie
@@ -94,7 +94,7 @@ fun MoviePagerItem(
) {
Column {
Image(
- painter = rememberImagePainter(data = posterFullPath),
+ painter = rememberAsyncImagePainter(model = posterFullPath),
contentScale = ContentScale.Crop,
contentDescription = null,
modifier = Modifier.fillMaxWidth().height(360.dp),
diff --git a/demos/moviesapp/app/src/main/java/com/guru/composecookbook/moviesapp/ui/trending/components/MoviesLaneItem.kt b/demos/moviesapp/app/src/main/java/com/guru/composecookbook/moviesapp/ui/trending/components/MoviesLaneItem.kt
index e23ea3dd..864b77a9 100644
--- a/demos/moviesapp/app/src/main/java/com/guru/composecookbook/moviesapp/ui/trending/components/MoviesLaneItem.kt
+++ b/demos/moviesapp/app/src/main/java/com/guru/composecookbook/moviesapp/ui/trending/components/MoviesLaneItem.kt
@@ -14,7 +14,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp
-import coil.compose.rememberImagePainter
+import coil3.compose.rememberAsyncImagePainter
import com.guru.composecookbook.moviesapp.data.db.models.Movie
import com.guru.composecookbook.theme.typography
@@ -33,7 +33,9 @@ fun MoviesLaneItem(movies: List, title: String = "", onMovieSelected: (Mo
itemContent = { movie: Movie ->
Image(
painter =
- rememberImagePainter(data = "https://image.tmdb.org/t/p/w500/${movie.poster_path}"),
+ rememberAsyncImagePainter(
+ model = "https://image.tmdb.org/t/p/w500/${movie.poster_path}"
+ ),
contentDescription = null,
modifier =
Modifier.width(190.dp)
diff --git a/demos/moviesapp/app/src/main/java/com/guru/composecookbook/moviesapp/ui/watch/components/MovieWatchlistItem.kt b/demos/moviesapp/app/src/main/java/com/guru/composecookbook/moviesapp/ui/watch/components/MovieWatchlistItem.kt
index f7ee868b..a9086710 100644
--- a/demos/moviesapp/app/src/main/java/com/guru/composecookbook/moviesapp/ui/watch/components/MovieWatchlistItem.kt
+++ b/demos/moviesapp/app/src/main/java/com/guru/composecookbook/moviesapp/ui/watch/components/MovieWatchlistItem.kt
@@ -1,56 +1,58 @@
-package com.guru.composecookbook.moviesapp.ui.watch.components
-
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.background
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.material.Icon
-import androidx.compose.material.IconButton
-import androidx.compose.material.Text
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.RemoveCircleOutline
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.layout.ContentScale
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.unit.dp
-import coil.compose.rememberImagePainter
-import com.guru.composecookbook.moviesapp.data.db.models.Movie
-import com.guru.composecookbook.theme.typography
-
-@Composable
-fun MovieWatchlistItem(
- movie: Movie,
- onMovieSelected: () -> Unit,
- onRemoveFromWatchlist: () -> Unit,
-) {
- Box(modifier = Modifier.clickable(onClick = onMovieSelected)) {
- Image(
- painter =
- rememberImagePainter(data = "https://image.tmdb.org/t/p/original/${movie.backdrop_path}"),
- contentDescription = null,
- modifier = Modifier.fillMaxWidth().height(280.dp),
- contentScale = ContentScale.Crop,
- )
- Spacer( // overlay
- modifier = Modifier.fillMaxWidth().height(280.dp).background(Color.Black.copy(alpha = 0.1f))
- )
- Text(
- text = movie.title,
- style = typography.h6.copy(fontWeight = FontWeight.ExtraBold),
- modifier = Modifier.align(Alignment.BottomStart).padding(8.dp),
- )
- IconButton(
- onClick = { onRemoveFromWatchlist.invoke() },
- modifier = Modifier.align(Alignment.BottomEnd),
- ) {
- Icon(imageVector = Icons.Default.RemoveCircleOutline, contentDescription = null)
- }
- }
-}
+package com.guru.composecookbook.moviesapp.ui.watch.components
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
+import androidx.compose.material.Text
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.RemoveCircleOutline
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import coil3.compose.rememberAsyncImagePainter
+import com.guru.composecookbook.moviesapp.data.db.models.Movie
+import com.guru.composecookbook.theme.typography
+
+@Composable
+fun MovieWatchlistItem(
+ movie: Movie,
+ onMovieSelected: () -> Unit,
+ onRemoveFromWatchlist: () -> Unit,
+) {
+ Box(modifier = Modifier.clickable(onClick = onMovieSelected)) {
+ Image(
+ painter =
+ rememberAsyncImagePainter(
+ model = "https://image.tmdb.org/t/p/original/${movie.backdrop_path}"
+ ),
+ contentDescription = null,
+ modifier = Modifier.fillMaxWidth().height(280.dp),
+ contentScale = ContentScale.Crop,
+ )
+ Spacer( // overlay
+ modifier = Modifier.fillMaxWidth().height(280.dp).background(Color.Black.copy(alpha = 0.1f))
+ )
+ Text(
+ text = movie.title,
+ style = typography.h6.copy(fontWeight = FontWeight.ExtraBold),
+ modifier = Modifier.align(Alignment.BottomStart).padding(8.dp),
+ )
+ IconButton(
+ onClick = { onRemoveFromWatchlist.invoke() },
+ modifier = Modifier.align(Alignment.BottomEnd),
+ ) {
+ Icon(imageVector = Icons.Default.RemoveCircleOutline, contentDescription = null)
+ }
+ }
+}
diff --git a/demos/moviesapp/data/build.gradle.kts b/demos/moviesapp/data/build.gradle.kts
index f6325bd1..2a084a48 100644
--- a/demos/moviesapp/data/build.gradle.kts
+++ b/demos/moviesapp/data/build.gradle.kts
@@ -1,7 +1,3 @@
-import com.guru.composecookbook.build.dependencies.addCoreAndroidDependencies
-import com.guru.composecookbook.build.dependencies.addDataDependencies
-import com.guru.composecookbook.build.dependencies.addNetworkingDependencies
-
plugins {
/** See [common-kotlin-module-configs-script-plugin.gradle.kts] file */
id("common-kotlin-module-configs-script-plugin")
@@ -10,7 +6,9 @@ plugins {
android { namespace = "com.guru.composecookbook.moviesapp.data" }
dependencies {
- addCoreAndroidDependencies()
- addNetworkingDependencies()
- addDataDependencies()
+ implementation(libs.bundles.core.android)
+ implementation(libs.bundles.networking)
+ implementation(platform(libs.androidx.compose.bom))
+ ksp(libs.androidx.room.compiler)
+ implementation(libs.bundles.data)
}
diff --git a/demos/paint/build.gradle.kts b/demos/paint/build.gradle.kts
index adaec39c..2a94d6c5 100644
--- a/demos/paint/build.gradle.kts
+++ b/demos/paint/build.gradle.kts
@@ -1,6 +1,3 @@
-import com.guru.composecookbook.build.dependencies.addComposeOfficialDependencies
-import com.guru.composecookbook.build.dependencies.addCoreAndroidUiDependencies
-
plugins {
/** See [common-compose-module-configs-script-plugin.gradle.kts] file */
id("common-compose-module-configs-script-plugin")
@@ -12,6 +9,7 @@ dependencies {
implementation(project(":theme"))
implementation(project(":components:colorpicker"))
- addComposeOfficialDependencies()
- addCoreAndroidUiDependencies()
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.bundles.compose.official)
+ implementation(libs.bundles.core.android.ui)
}
diff --git a/demos/spotify/build.gradle.kts b/demos/spotify/build.gradle.kts
index fccc36c8..288ce693 100644
--- a/demos/spotify/build.gradle.kts
+++ b/demos/spotify/build.gradle.kts
@@ -1,6 +1,3 @@
-import com.guru.composecookbook.build.dependencies.addComposeOfficialDependencies
-import com.guru.composecookbook.build.dependencies.addCoreAndroidUiDependencies
-
plugins {
/** See [common-compose-module-configs-script-plugin.gradle.kts] file */
id("common-compose-module-configs-script-plugin")
@@ -13,6 +10,7 @@ dependencies {
implementation(project(":data"))
implementation(project(":theme"))
- addComposeOfficialDependencies()
- addCoreAndroidUiDependencies()
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.bundles.compose.official)
+ implementation(libs.bundles.core.android.ui)
}
diff --git a/demos/tiktok/build.gradle.kts b/demos/tiktok/build.gradle.kts
index 05f45d1c..a8c19b39 100644
--- a/demos/tiktok/build.gradle.kts
+++ b/demos/tiktok/build.gradle.kts
@@ -1,11 +1,7 @@
-import com.guru.composecookbook.build.dependencies.addComposeOfficialDependencies
-import com.guru.composecookbook.build.dependencies.addCoreAndroidUiDependencies
-import com.guru.composecookbook.build.dependencies.addGoogleAndroidDependencies
-import com.guru.composecookbook.build.dependencies.addThirdPartyUiDependencies
-
plugins {
/** See [common-compose-module-configs-script-plugin.gradle.kts] file */
id("common-compose-module-configs-script-plugin")
+ id("org.jetbrains.kotlin.plugin.serialization")
}
android { namespace = "com.guru.composecookbook.tiktok" }
@@ -16,8 +12,10 @@ dependencies {
implementation(project(":data"))
implementation(project(":theme"))
- addComposeOfficialDependencies()
- addCoreAndroidUiDependencies()
- addThirdPartyUiDependencies()
- addGoogleAndroidDependencies()
+ implementation(libs.bundles.kotlin)
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.bundles.compose.official)
+ implementation(libs.bundles.core.android.ui)
+ implementation(libs.bundles.thirdparty.ui)
+ implementation(libs.bundles.google.android)
}
diff --git a/demos/tiktok/src/main/java/com/guru/composecookbook/tiktok/TikTokScreen.kt b/demos/tiktok/src/main/java/com/guru/composecookbook/tiktok/TikTokScreen.kt
index ae9df718..86af936d 100644
--- a/demos/tiktok/src/main/java/com/guru/composecookbook/tiktok/TikTokScreen.kt
+++ b/demos/tiktok/src/main/java/com/guru/composecookbook/tiktok/TikTokScreen.kt
@@ -1,15 +1,41 @@
package com.guru.composecookbook.tiktok
-sealed class TikTokScreen(val route: String) {
- object Home : TikTokScreen("Home")
+import kotlinx.serialization.Serializable
- object Discover : TikTokScreen("Discover")
+/**
+ * Type-safe navigation routes for the TikTok demo. Each screen is a serializable route; [route] is
+ * kept as the human readable label shown in the bottom bar.
+ */
+sealed interface TikTokScreen {
+ val route: String
- object Create : TikTokScreen("Create")
+ @Serializable
+ data object Home : TikTokScreen {
+ override val route = "Home"
+ }
- object Inbox : TikTokScreen("Inbox")
+ @Serializable
+ data object Discover : TikTokScreen {
+ override val route = "Discover"
+ }
- object Me : TikTokScreen("Me")
+ @Serializable
+ data object Create : TikTokScreen {
+ override val route = "Create"
+ }
- object Profile : TikTokScreen("Profile")
+ @Serializable
+ data object Inbox : TikTokScreen {
+ override val route = "Inbox"
+ }
+
+ @Serializable
+ data object Me : TikTokScreen {
+ override val route = "Me"
+ }
+
+ @Serializable
+ data class Profile(val userId: String) : TikTokScreen {
+ override val route = "Profile"
+ }
}
diff --git a/demos/tiktok/src/main/java/com/guru/composecookbook/tiktok/TiktokActivity.kt b/demos/tiktok/src/main/java/com/guru/composecookbook/tiktok/TiktokActivity.kt
index 57438e1d..58319dae 100644
--- a/demos/tiktok/src/main/java/com/guru/composecookbook/tiktok/TiktokActivity.kt
+++ b/demos/tiktok/src/main/java/com/guru/composecookbook/tiktok/TiktokActivity.kt
@@ -20,11 +20,13 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.core.content.ContextCompat
+import androidx.navigation.NavDestination.Companion.hasRoute
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
+import androidx.navigation.toRoute
import com.guru.composecookbook.theme.ComposeCookBookTheme
import com.guru.composecookbook.theme.tiktokBlack
import com.guru.composecookbook.tiktok.components.discovers.DiscoverScreen
@@ -60,17 +62,18 @@ fun TiktokAppContent() {
@Composable
fun TikTokBottomNavigation(navController: NavHostController) {
val navBackStackEntry by navController.currentBackStackEntryAsState()
- val currentRoute = navBackStackEntry?.arguments?.getString("route")
+ val currentDestination = navBackStackEntry?.destination
BottomNavigation(backgroundColor = tiktokBlack) {
TiktokDemoDataProvider.bottomBarList.forEach { tiktokScreen ->
+ val isSelected = currentDestination?.hasRoute(tiktokScreen::class) == true
BottomNavigationItem(
icon = { BottomBarIcon(tiktokScreen) },
- selected = currentRoute == tiktokScreen.route,
+ selected = isSelected,
onClick = {
navController.popBackStack(navController.graph.startDestinationId, false)
- if (currentRoute != tiktokScreen.route) {
- navController.navigate(tiktokScreen.route)
+ if (!isSelected) {
+ navController.navigate(tiktokScreen)
}
},
label = {
@@ -91,7 +94,7 @@ fun BottomBarIcon(screen: TikTokScreen) {
TikTokScreen.Create -> TiktokCreateIcon()
TikTokScreen.Inbox -> Icon(imageVector = Icons.Filled.Inbox, contentDescription = null)
TikTokScreen.Me -> Icon(imageVector = Icons.Filled.Person, contentDescription = null)
- TikTokScreen.Profile -> {
+ is TikTokScreen.Profile -> {
/* this branch added to satisfy exhaustive when */
}
}
@@ -101,19 +104,19 @@ fun BottomBarIcon(screen: TikTokScreen) {
fun TikTokBodyContent(navController: NavHostController, modifier: Modifier = Modifier) {
NavHost(
navController = navController,
- startDestination = TikTokScreen.Home.route,
+ startDestination = TikTokScreen.Home,
modifier = modifier,
) {
- composable(TikTokScreen.Home.route) {
+ composable {
HomeScreen(tiktokInteractionEvents = { handleInteractionEvent(it, navController) })
}
- composable(TikTokScreen.Discover.route) { DiscoverScreen() }
- composable(TikTokScreen.Create.route) { Text(text = "Create:TODO") }
- composable(TikTokScreen.Inbox.route) { Text(text = "Inbox:TODO") }
- composable(TikTokScreen.Me.route) { ProfileScreen("10", navController) }
+ composable { DiscoverScreen() }
+ composable { Text(text = "Create:TODO") }
+ composable { Text(text = "Inbox:TODO") }
+ composable { ProfileScreen("10", navController) }
// This navigation is for going to user profile but it should be moved to separate place
- composable("${TikTokScreen.Profile.route}/{userId}") { backStackEntry ->
- ProfileScreen(backStackEntry.arguments?.getString("userId")!!, navController)
+ composable { backStackEntry ->
+ ProfileScreen(backStackEntry.toRoute().userId, navController)
}
}
}
@@ -125,7 +128,7 @@ fun handleInteractionEvent(
when (tiktokHomeInteractionEvents) {
is TiktokHomeInteractionEvents.OpenProfile -> {
navController.navigate(
- "${TikTokScreen.Profile.route}/${tiktokHomeInteractionEvents.album.id}"
+ TikTokScreen.Profile(userId = tiktokHomeInteractionEvents.album.id.toString())
)
}
else -> {
diff --git a/demos/tiktok/src/main/java/com/guru/composecookbook/tiktok/components/discovers/MediaItem.kt b/demos/tiktok/src/main/java/com/guru/composecookbook/tiktok/components/discovers/MediaItem.kt
index a9fa1275..ac401e9e 100644
--- a/demos/tiktok/src/main/java/com/guru/composecookbook/tiktok/components/discovers/MediaItem.kt
+++ b/demos/tiktok/src/main/java/com/guru/composecookbook/tiktok/components/discovers/MediaItem.kt
@@ -1,21 +1,21 @@
-package com.guru.composecookbook.tiktok.components.discovers
-
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.layout.ContentScale
-import androidx.compose.ui.unit.dp
-import coil.compose.rememberImagePainter
-
-@Composable
-fun MediaItem(id: Int, modifier: Modifier = Modifier) {
- Image(
- painter = rememberImagePainter(data = "https://picsum.photos/id/${id}/200/200"),
- contentDescription = null,
- contentScale = ContentScale.Crop,
- modifier = modifier.height(150.dp).width(120.dp).padding(2.dp),
- )
-}
+package com.guru.composecookbook.tiktok.components.discovers
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.unit.dp
+import coil3.compose.rememberAsyncImagePainter
+
+@Composable
+fun MediaItem(id: Int, modifier: Modifier = Modifier) {
+ Image(
+ painter = rememberAsyncImagePainter(model = "https://picsum.photos/id/${id}/200/200"),
+ contentDescription = null,
+ contentScale = ContentScale.Crop,
+ modifier = modifier.height(150.dp).width(120.dp).padding(2.dp),
+ )
+}
diff --git a/demos/tiktok/src/main/java/com/guru/composecookbook/tiktok/components/home/TikTokPlayer.kt b/demos/tiktok/src/main/java/com/guru/composecookbook/tiktok/components/home/TikTokPlayer.kt
index 0a1fd66f..82ae0a8c 100644
--- a/demos/tiktok/src/main/java/com/guru/composecookbook/tiktok/components/home/TikTokPlayer.kt
+++ b/demos/tiktok/src/main/java/com/guru/composecookbook/tiktok/components/home/TikTokPlayer.kt
@@ -1,30 +1,26 @@
package com.guru.composecookbook.tiktok.components.home
import android.content.Context
-import android.net.Uri
+import androidx.annotation.OptIn
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.viewinterop.AndroidView
-import com.google.android.exoplayer2.C
-import com.google.android.exoplayer2.MediaItem
-import com.google.android.exoplayer2.Player
-import com.google.android.exoplayer2.SimpleExoPlayer
-import com.google.android.exoplayer2.source.ProgressiveMediaSource
-import com.google.android.exoplayer2.ui.AspectRatioFrameLayout
-import com.google.android.exoplayer2.ui.PlayerView
-import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
+import androidx.media3.common.C
+import androidx.media3.common.MediaItem
+import androidx.media3.common.Player
+import androidx.media3.common.util.UnstableApi
+import androidx.media3.exoplayer.ExoPlayer
+import androidx.media3.ui.AspectRatioFrameLayout
+import androidx.media3.ui.PlayerView
+@OptIn(UnstableApi::class)
@Composable
fun TikTokPlayer(context: Context, url: String, selected: Boolean) {
val tiktokPlayer = remember {
- SimpleExoPlayer.Builder(context).build().apply {
- val mediaSource =
- ProgressiveMediaSource.Factory(DefaultDataSourceFactory(context, "composeCookBook"))
- .createMediaSource(MediaItem.fromUri(Uri.parse("asset:///${url}")))
-
- this.setMediaSource(mediaSource, true)
- this.prepare()
+ ExoPlayer.Builder(context).build().apply {
+ setMediaItem(MediaItem.fromUri("asset:///$url"))
+ prepare()
}
}
tiktokPlayer.videoScalingMode = C.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING
diff --git a/demos/twitter/build.gradle.kts b/demos/twitter/build.gradle.kts
index b79080a0..f9667a0a 100644
--- a/demos/twitter/build.gradle.kts
+++ b/demos/twitter/build.gradle.kts
@@ -1,6 +1,3 @@
-import com.guru.composecookbook.build.dependencies.addComposeOfficialDependencies
-import com.guru.composecookbook.build.dependencies.addCoreAndroidUiDependencies
-
plugins {
/** See [common-compose-module-configs-script-plugin.gradle.kts] file */
id("common-compose-module-configs-script-plugin")
@@ -12,6 +9,7 @@ dependencies {
implementation(project(":data"))
implementation(project(":theme"))
- addComposeOfficialDependencies()
- addCoreAndroidUiDependencies()
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.bundles.compose.official)
+ implementation(libs.bundles.core.android.ui)
}
diff --git a/demos/youtube/build.gradle.kts b/demos/youtube/build.gradle.kts
index f4c51474..60586b5a 100644
--- a/demos/youtube/build.gradle.kts
+++ b/demos/youtube/build.gradle.kts
@@ -1,6 +1,3 @@
-import com.guru.composecookbook.build.dependencies.addComposeOfficialDependencies
-import com.guru.composecookbook.build.dependencies.addCoreAndroidUiDependencies
-
plugins {
/** See [common-compose-module-configs-script-plugin.gradle.kts] file */
id("common-compose-module-configs-script-plugin")
@@ -12,6 +9,7 @@ dependencies {
implementation(project(":data"))
implementation(project(":theme"))
- addComposeOfficialDependencies()
- addCoreAndroidUiDependencies()
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.bundles.compose.official)
+ implementation(libs.bundles.core.android.ui)
}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
new file mode 100644
index 00000000..09baf9b6
--- /dev/null
+++ b/gradle/libs.versions.toml
@@ -0,0 +1,178 @@
+[versions]
+agp = "8.7.3"
+kotlin = "2.1.20"
+ksp = "2.1.20-1.0.31"
+ktfmt = "0.22.0"
+
+activity-compose = "1.10.1"
+appcompat = "1.7.1"
+biometric = "1.2.0-alpha05"
+coil = "3.1.0"
+compose-bom = "2024.12.01"
+constraintlayout-compose = "1.1.0"
+core-ktx = "1.16.0"
+coroutines = "1.10.2"
+flinger = "1.0.5"
+fontawesome-compose = "1.1.0"
+gson = "2.10.1"
+junit-jupiter = "5.10.1"
+junit4 = "4.13.2"
+junit-androidx = "1.1.5"
+kotlinx-serialization-json = "1.8.0"
+lifecycle = "2.8.7"
+logging-interceptor = "4.12.0"
+lottie = "6.3.0"
+material = "1.12.0"
+media3 = "1.5.1"
+multidex = "2.0.1"
+navigation-compose = "2.8.9"
+paging = "3.3.6"
+palette-ktx = "1.0.0"
+play-services-ads = "22.5.0"
+play-services-maps = "18.2.0"
+retrofit = "2.11.0"
+room = "2.7.1"
+truth = "1.1.3"
+wear = "1.2.0"
+wear-compose = "1.0.0-alpha07"
+
+[libraries]
+# Kotlin
+kotlin-stdlib-jdk8 = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" }
+kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }
+kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
+kotlin-test-junit5 = { module = "org.jetbrains.kotlin:kotlin-test-junit5", version.ref = "kotlin" }
+kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
+kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
+kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization-json" }
+
+# Compose (versions managed by the BOM)
+androidx-compose-bom = { module = "androidx.compose:compose-bom", version.ref = "compose-bom" }
+compose-ui = { module = "androidx.compose.ui:ui" }
+compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" }
+compose-material = { module = "androidx.compose.material:material" }
+compose-material3 = { module = "androidx.compose.material3:material3" }
+compose-material3-window-size = { module = "androidx.compose.material3:material3-window-size-class" }
+compose-material3-adaptive = { module = "androidx.compose.material3.adaptive:adaptive" }
+compose-material3-adaptive-layout = { module = "androidx.compose.material3.adaptive:adaptive-layout" }
+compose-material3-adaptive-navigation = { module = "androidx.compose.material3.adaptive:adaptive-navigation" }
+compose-material3-adaptive-navigation-suite = { module = "androidx.compose.material3:material3-adaptive-navigation-suite" }
+compose-material-icons-extended = { module = "androidx.compose.material:material-icons-extended" }
+compose-runtime = { module = "androidx.compose.runtime:runtime" }
+compose-runtime-livedata = { module = "androidx.compose.runtime:runtime-livedata" }
+compose-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4" }
+compose-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest" }
+
+# Compose adjacent
+compose-activity = { module = "androidx.activity:activity-compose", version.ref = "activity-compose" }
+compose-constraintlayout = { module = "androidx.constraintlayout:constraintlayout-compose", version.ref = "constraintlayout-compose" }
+compose-navigation = { module = "androidx.navigation:navigation-compose", version.ref = "navigation-compose" }
+compose-paging = { module = "androidx.paging:paging-compose", version.ref = "paging" }
+compose-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycle" }
+
+# AndroidX
+androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
+androidx-biometric = { module = "androidx.biometric:biometric", version.ref = "biometric" }
+androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "core-ktx" }
+androidx-lifecycle-livedata-ktx = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "lifecycle" }
+androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle" }
+androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycle" }
+androidx-lifecycle-viewmodel-savedstate = { module = "androidx.lifecycle:lifecycle-viewmodel-savedstate", version.ref = "lifecycle" }
+androidx-multidex = { module = "androidx.multidex:multidex", version.ref = "multidex" }
+androidx-paging-runtime-ktx = { module = "androidx.paging:paging-runtime-ktx", version.ref = "paging" }
+androidx-palette-ktx = { module = "androidx.palette:palette-ktx", version.ref = "palette-ktx" }
+androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
+androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" }
+androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
+androidx-wear = { module = "androidx.wear:wear", version.ref = "wear" }
+androidx-wear-compose-material = { module = "androidx.wear.compose:compose-material", version.ref = "wear-compose" }
+androidx-wear-compose-foundation = { module = "androidx.wear.compose:compose-foundation", version.ref = "wear-compose" }
+androidx-wear-compose-navigation = { module = "androidx.wear.compose:compose-navigation", version.ref = "wear-compose" }
+
+# Media3
+media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3" }
+media3-ui = { module = "androidx.media3:media3-ui", version.ref = "media3" }
+
+# Google
+google-material = { module = "com.google.android.material:material", version.ref = "material" }
+play-services-ads = { module = "com.google.android.gms:play-services-ads", version.ref = "play-services-ads" }
+play-services-maps = { module = "com.google.android.gms:play-services-maps", version.ref = "play-services-maps" }
+gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
+
+# Networking
+retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
+retrofit-converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit" }
+okhttp-logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "logging-interceptor" }
+
+# Third party UI
+coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" }
+coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil" }
+lottie = { module = "com.airbnb.android:lottie", version.ref = "lottie" }
+lottie-compose = { module = "com.airbnb.android:lottie-compose", version.ref = "lottie" }
+fontawesome-compose = { module = "com.github.Gurupreet:FontAwesomeCompose", version.ref = "fontawesome-compose" }
+flinger = { module = "com.github.iamjosephmj:flinger", version.ref = "flinger" }
+
+# Testing
+junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit-jupiter" }
+junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit-jupiter" }
+truth = { module = "com.google.truth:truth", version.ref = "truth" }
+junit = { module = "junit:junit", version.ref = "junit4" }
+androidx-test-junit = { module = "androidx.test.ext:junit", version.ref = "junit-androidx" }
+
+# Build plugins used on classpaths
+android-gradle-plugin = { module = "com.android.tools.build:gradle", version.ref = "agp" }
+android-gradle-api = { module = "com.android.tools.build:gradle-api", version.ref = "agp" }
+kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
+compose-compiler-gradle-plugin = { module = "org.jetbrains.kotlin:compose-compiler-gradle-plugin", version.ref = "kotlin" }
+kotlin-serialization-plugin = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin" }
+ksp-gradle-plugin = { module = "com.google.devtools.ksp:symbol-processing-gradle-plugin", version.ref = "ksp" }
+
+[bundles]
+kotlin = ["kotlin-stdlib-jdk8", "kotlinx-coroutines-core", "kotlinx-coroutines-android", "kotlinx-serialization-json"]
+kotlin-test = ["kotlin-test", "kotlin-test-junit5"]
+compose-official = [
+ "compose-ui",
+ "compose-ui-tooling",
+ "compose-material",
+ "compose-material3",
+ "compose-material-icons-extended",
+ "compose-runtime-livedata",
+ "compose-constraintlayout",
+ "compose-paging",
+ "compose-viewmodel",
+ "compose-activity",
+ "compose-navigation",
+ "compose-runtime",
+ "compose-material3-adaptive",
+ "compose-material3-adaptive-layout",
+ "compose-material3-adaptive-navigation",
+ "compose-material3-adaptive-navigation-suite",
+]
+compose-thirdparty = ["fontawesome-compose", "lottie-compose", "flinger"]
+compose-debug = ["kotlin-reflect", "compose-ui-tooling", "compose-ui-test-manifest"]
+data = ["androidx-room-runtime", "androidx-room-ktx", "androidx-paging-runtime-ktx", "compose-runtime"]
+core-android = ["androidx-multidex", "androidx-core-ktx", "androidx-appcompat"]
+core-android-ui = [
+ "google-material",
+ "androidx-palette-ktx",
+ "androidx-paging-runtime-ktx",
+ "androidx-lifecycle-runtime-ktx",
+ "androidx-lifecycle-viewmodel-savedstate",
+ "androidx-lifecycle-livedata-ktx",
+ "androidx-lifecycle-viewmodel-ktx",
+]
+google-android = ["media3-exoplayer", "media3-ui", "play-services-ads", "play-services-maps"]
+networking = ["retrofit", "okhttp-logging-interceptor", "gson", "retrofit-converter-gson"]
+junit5-test = ["junit-jupiter-api", "junit-jupiter-engine"]
+instrumentation-test = ["compose-ui-test-junit4", "compose-activity", "androidx-test-junit", "compose-ui-test-manifest"]
+thirdparty-ui = ["coil-compose", "coil-network-okhttp", "lottie", "lottie-compose"]
+wear = ["androidx-wear", "androidx-wear-compose-material", "androidx-wear-compose-foundation", "androidx-wear-compose-navigation"]
+
+[plugins]
+android-application = { id = "com.android.application", version.ref = "agp" }
+android-library = { id = "com.android.library", version.ref = "agp" }
+kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
+kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
+kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
+ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
+ktfmt = { id = "com.ncorti.ktfmt.gradle", version.ref = "ktfmt" }
diff --git a/templates/cascademenu/build.gradle.kts b/templates/cascademenu/build.gradle.kts
index 0b9c7b3f..49f4694e 100644
--- a/templates/cascademenu/build.gradle.kts
+++ b/templates/cascademenu/build.gradle.kts
@@ -1,6 +1,3 @@
-import com.guru.composecookbook.build.dependencies.addComposeOfficialDependencies
-import com.guru.composecookbook.build.dependencies.addComposeThirdPartyDependencies
-
plugins {
/** See [common-compose-module-configs-script-plugin.gradle.kts] file */
id("common-compose-module-configs-script-plugin")
@@ -13,6 +10,7 @@ dependencies {
implementation(project(":data"))
implementation(project(":components:tags"))
- addComposeOfficialDependencies()
- addComposeThirdPartyDependencies()
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.bundles.compose.official)
+ implementation(libs.bundles.compose.thirdparty)
}
diff --git a/templates/login/build.gradle.kts b/templates/login/build.gradle.kts
index f27aa2d8..2b6a8940 100644
--- a/templates/login/build.gradle.kts
+++ b/templates/login/build.gradle.kts
@@ -1,6 +1,3 @@
-import com.guru.composecookbook.build.dependencies.addComposeOfficialDependencies
-import com.guru.composecookbook.build.dependencies.addComposeThirdPartyDependencies
-
plugins {
/** See [common-compose-module-configs-script-plugin.gradle.kts] file */
id("common-compose-module-configs-script-plugin")
@@ -15,6 +12,10 @@ dependencies {
implementation(project(":components:tags"))
implementation(project(":templates:onboarding"))
- addComposeOfficialDependencies()
- addComposeThirdPartyDependencies()
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.bundles.compose.official)
+ implementation(libs.bundles.compose.thirdparty)
+
+ androidTestImplementation(platform(libs.androidx.compose.bom))
+ androidTestImplementation(libs.bundles.instrumentation.test)
}
diff --git a/templates/login/src/androidTest/java/com/guru/composecookbook/login/LoginScreenTest.kt b/templates/login/src/androidTest/java/com/guru/composecookbook/login/LoginScreenTest.kt
new file mode 100644
index 00000000..db15c9de
--- /dev/null
+++ b/templates/login/src/androidTest/java/com/guru/composecookbook/login/LoginScreenTest.kt
@@ -0,0 +1,42 @@
+package com.guru.composecookbook.login
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performScrollTo
+import androidx.compose.ui.test.performTextInput
+import org.junit.Rule
+import org.junit.Test
+
+class LoginScreenTest {
+
+ @get:Rule val composeTestRule = createComposeRule()
+
+ private fun setLoginContent(onLoginSuccess: () -> Unit = {}) {
+ composeTestRule.setContent { LoginScreen(onLoginSuccess = onLoginSuccess) }
+ }
+
+ @Test
+ fun loginFieldsAreDisplayed() {
+ setLoginContent()
+
+ composeTestRule.onNodeWithText("Email address").assertIsDisplayed()
+ composeTestRule.onNodeWithText("Password").assertIsDisplayed()
+ }
+
+ @Test
+ fun loginButtonIsDisplayed() {
+ setLoginContent()
+
+ composeTestRule.onNodeWithText("Log In").performScrollTo().assertIsDisplayed()
+ }
+
+ @Test
+ fun typedEmailIsReflectedInTheField() {
+ setLoginContent()
+
+ composeTestRule.onNodeWithText("Email address").performTextInput("user@example.com")
+
+ composeTestRule.onNodeWithText("user@example.com").assertIsDisplayed()
+ }
+}
diff --git a/templates/onboarding/build.gradle.kts b/templates/onboarding/build.gradle.kts
index 4e7d65de..141f1ea0 100644
--- a/templates/onboarding/build.gradle.kts
+++ b/templates/onboarding/build.gradle.kts
@@ -1,5 +1,3 @@
-import com.guru.composecookbook.build.dependencies.addComposeOfficialDependencies
-
plugins {
/** See [common-compose-module-configs-script-plugin.gradle.kts] file */
id("common-compose-module-configs-script-plugin")
@@ -11,5 +9,6 @@ dependencies {
implementation(project(":components:carousel"))
implementation(project(":animations:lottie"))
- addComposeOfficialDependencies()
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.bundles.compose.official)
}
diff --git a/templates/paymentcard/build.gradle.kts b/templates/paymentcard/build.gradle.kts
index 842070a3..967511be 100644
--- a/templates/paymentcard/build.gradle.kts
+++ b/templates/paymentcard/build.gradle.kts
@@ -1,5 +1,3 @@
-import com.guru.composecookbook.build.dependencies.addComposeOfficialDependencies
-
plugins {
/** See [common-compose-module-configs-script-plugin.gradle.kts] file */
id("common-compose-module-configs-script-plugin")
@@ -10,5 +8,6 @@ android { namespace = "com.guru.composecookbook.paymentcard" }
dependencies {
implementation(project(":theme"))
- addComposeOfficialDependencies()
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.bundles.compose.official)
}
diff --git a/templates/pinlock/build.gradle.kts b/templates/pinlock/build.gradle.kts
index 2e336576..4d5d2919 100644
--- a/templates/pinlock/build.gradle.kts
+++ b/templates/pinlock/build.gradle.kts
@@ -1,7 +1,3 @@
-import com.guru.composecookbook.build.dependencies.addBiometricDependency
-import com.guru.composecookbook.build.dependencies.addComposeOfficialDependencies
-import com.guru.composecookbook.build.dependencies.addComposeThirdPartyDependencies
-
plugins {
/** See [common-compose-module-configs-script-plugin.gradle.kts] file */
id("common-compose-module-configs-script-plugin")
@@ -16,7 +12,8 @@ dependencies {
implementation(project(":components:tags"))
implementation(project(":templates:onboarding"))
- addComposeOfficialDependencies()
- addComposeThirdPartyDependencies()
- addBiometricDependency()
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.bundles.compose.official)
+ implementation(libs.bundles.compose.thirdparty)
+ implementation(libs.androidx.biometric)
}
diff --git a/templates/profile/build.gradle.kts b/templates/profile/build.gradle.kts
index f40ac089..38571a2f 100644
--- a/templates/profile/build.gradle.kts
+++ b/templates/profile/build.gradle.kts
@@ -1,5 +1,3 @@
-import com.guru.composecookbook.build.dependencies.addComposeOfficialDependencies
-
plugins {
/** See [common-compose-module-configs-script-plugin.gradle.kts] file */
id("common-compose-module-configs-script-plugin")
@@ -12,5 +10,6 @@ dependencies {
implementation(project(":data"))
implementation(project(":components:tags"))
- addComposeOfficialDependencies()
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.bundles.compose.official)
}
diff --git a/theme/build.gradle.kts b/theme/build.gradle.kts
index c25cff6a..12f96c61 100644
--- a/theme/build.gradle.kts
+++ b/theme/build.gradle.kts
@@ -1,7 +1,3 @@
-import com.guru.composecookbook.build.dependencies.addComposeOfficialDependencies
-import com.guru.composecookbook.build.dependencies.addComposeThirdPartyDependencies
-import com.guru.composecookbook.build.dependencies.addCoreAndroidUiDependencies
-
plugins {
/** See [common-compose-module-configs-script-plugin.gradle.kts] file */
id("common-compose-module-configs-script-plugin")
@@ -10,7 +6,8 @@ plugins {
android { namespace = "com.guru.composecookbook.theme" }
dependencies {
- addComposeOfficialDependencies()
- addComposeThirdPartyDependencies()
- addCoreAndroidUiDependencies()
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.bundles.compose.official)
+ implementation(libs.bundles.compose.thirdparty)
+ implementation(libs.bundles.core.android.ui)
}