Skip to content
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
<a href="https://github.com/Gurupreet/ComposeCookBook/releases/download/latest-master/app-debug.apk" >
<img src = "https://img.shields.io/badge/Master-Master?color=7885FF&label=Sample%20App&logo=android&style=for-the-badge" />
</a>
<a href = "https://developer.android.com/jetpack/androidx/versions/all-channel#december_16_2020">
<img src = "https://img.shields.io/badge/Jetpack%20Compose-1.0.1-brightgreen" />
<a href = "https://developer.android.com/develop/ui/compose/bom/bom-mapping">
<img src = "https://img.shields.io/badge/Jetpack%20Compose-BOM%202024.12.01-brightgreen" />
</a>
<a href = "https://github.com/Gurupreet/ComposeCookBook/actions/workflows/android.yml">
<img src = "https://github.com/Gurupreet/ComposeCookBook/actions/workflows/android.yml/badge.svg" />
Expand Down Expand Up @@ -102,7 +102,7 @@ Please get **Android Studio Bumblebee latest Canary** [from here](https://develo

## Coming Soon
- Some of the features that will be available in coming weeks
- Advance lists: Pull Refresh, Swipe lists etc
- Advance lists: Swipe lists etc
- Clean Architecture Sample with coroutines.
- Advance canvas drawing.
Much more in pipeline stay tuned!!
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api
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.getValue
import androidx.compose.runtime.mutableStateOf
Expand Down Expand Up @@ -39,7 +39,7 @@ fun AnimationScreen() {
Scaffold(
modifier = Modifier.testTag(TestTags.ANIM_SCREEN_ROOT),
topBar = {
SmallTopAppBar(
TopAppBar(
title = { Text(text = "Animations") },
navigationIcon = {
IconButton(onClick = { animateIcon = !animateIcon }) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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() }) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -139,7 +139,7 @@ fun ListViewContent(
*/
Scaffold(
topBar = {
SmallTopAppBar(
TopAppBar(
title = {
Column(modifier = Modifier.padding(4.dp)) {
/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -77,7 +77,7 @@ class DialogsActivity : AppCompatActivity() {
fun DialogScreen(onBack: () -> Unit) {
Scaffold(
topBar = {
SmallTopAppBar(
TopAppBar(
title = { Text(text = "Dialogs") },
navigationIcon = {
IconButton(onClick = onBack) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
@@ -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) }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -57,7 +57,7 @@ fun AppBars() {
fun TopAppBarsDemo() {
SubtitleText(subtitle = "Top App bar")

SmallTopAppBar(
Material3TopAppBar(
title = { Text(text = "Home") },
navigationIcon = {
IconButton(onClick = {}) {
Expand Down
5 changes: 4 additions & 1 deletion buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ dependencies {
// 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")

// in order to be able to apply "com.google.devtools.ksp" in the common scripts
// in order to be able to apply "org.jetbrains.kotlin.plugin.serialization" in module scripts
implementation("org.jetbrains.kotlin:kotlin-serialization:2.1.20")

// 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")

Expand Down
Loading
Loading