diff --git a/app/src/main/java/com/google/samples/apps/sunflower/compose/gallery/GalleryScreen.kt b/app/src/main/java/com/google/samples/apps/sunflower/compose/gallery/GalleryScreen.kt index eaf0c8c7c..0409ae390 100644 --- a/app/src/main/java/com/google/samples/apps/sunflower/compose/gallery/GalleryScreen.kt +++ b/app/src/main/java/com/google/samples/apps/sunflower/compose/gallery/GalleryScreen.kt @@ -16,21 +16,27 @@ package com.google.samples.apps.sunflower.compose.gallery +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar +import androidx.compose.material3.pulltorefresh.PullToRefreshContainer +import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -59,41 +65,74 @@ fun GalleryScreen( plantPictures = viewModel.plantPictures, onPhotoClick = onPhotoClick, onUpClick = onUpClick, + onPullToRefresh = viewModel::refreshData, + isRefreshing = viewModel.isRefreshing.value ) } +@OptIn(ExperimentalMaterial3Api::class) @Composable private fun GalleryScreen( plantPictures: Flow>, onPhotoClick: (UnsplashPhoto) -> Unit = {}, onUpClick: () -> Unit = {}, + onPullToRefresh: () -> Unit, + isRefreshing: Boolean ) { Scaffold( topBar = { GalleryTopBar(onUpClick = onUpClick) }, ) { padding -> - val pagingItems: LazyPagingItems = plantPictures.collectAsLazyPagingItems() - LazyVerticalGrid( - columns = GridCells.Fixed(2), - modifier = Modifier.padding(padding), - contentPadding = PaddingValues(all = dimensionResource(id = R.dimen.card_side_margin)) + + val pullToRefreshState = rememberPullToRefreshState() + + if (pullToRefreshState.isRefreshing){ + LaunchedEffect(Unit){ + onPullToRefresh() + } + } + LaunchedEffect(isRefreshing){ + if (!isRefreshing){ + pullToRefreshState.endRefresh() + } + } + + + Box( + modifier = Modifier + .padding(padding) + .nestedScroll(pullToRefreshState.nestedScrollConnection) ) { - // TODO update this implementation once paging Compose supports LazyGridScope - // See: https://issuetracker.google.com/issues/178087310 - items( - count = pagingItems.itemCount, - key = { index -> - val photo = pagingItems[index] - "${ photo?.id ?: ""}${index}" - } - ) { index -> - val photo = pagingItems[index] ?: return@items - PhotoListItem(photo = photo) { - onPhotoClick(photo) + val pagingItems: LazyPagingItems = + plantPictures.collectAsLazyPagingItems() + LazyVerticalGrid( + columns = GridCells.Fixed(2), + contentPadding = PaddingValues(all = dimensionResource(id = R.dimen.card_side_margin)) + ) { + // TODO update this implementation once paging Compose supports LazyGridScope + // See: https://issuetracker.google.com/issues/178087310 + items( + count = pagingItems.itemCount, + key = { index -> + val photo = pagingItems[index] + "${photo?.id ?: ""}${index}" + } + ) { index -> + val photo = pagingItems[index] ?: return@items + PhotoListItem(photo = photo) { + onPhotoClick(photo) + } } } + + PullToRefreshContainer( + modifier = Modifier.align(Alignment.TopCenter), + state = pullToRefreshState + ) } + + } } @@ -111,7 +150,7 @@ private fun GalleryTopBar( navigationIcon = { IconButton(onClick = onUpClick) { Icon( - Icons.Filled.ArrowBack, + Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null ) } @@ -124,7 +163,7 @@ private fun GalleryTopBar( private fun GalleryScreenPreview( @PreviewParameter(GalleryScreenPreviewParamProvider::class) plantPictures: Flow> ) { - GalleryScreen(plantPictures = plantPictures) + GalleryScreen(plantPictures = plantPictures, onPullToRefresh = {}, isRefreshing = false) } private class GalleryScreenPreviewParamProvider : diff --git a/app/src/main/java/com/google/samples/apps/sunflower/compose/home/HomeScreen.kt b/app/src/main/java/com/google/samples/apps/sunflower/compose/home/HomeScreen.kt index 299c21467..ed792bb86 100644 --- a/app/src/main/java/com/google/samples/apps/sunflower/compose/home/HomeScreen.kt +++ b/app/src/main/java/com/google/samples/apps/sunflower/compose/home/HomeScreen.kt @@ -16,7 +16,6 @@ package com.google.samples.apps.sunflower.compose.home -import android.util.Log import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.compose.foundation.ExperimentalFoundationApi @@ -73,7 +72,8 @@ fun HomeScreen( onPlantClick: (Plant) -> Unit = {}, viewModel: PlantListViewModel = hiltViewModel() ) { - val pagerState = rememberPagerState() + val pages : Array = SunflowerPage.values() + val pagerState = rememberPagerState(pageCount = {pages.count()}) val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() Scaffold( @@ -89,7 +89,8 @@ fun HomeScreen( HomePagerScreen( onPlantClick = onPlantClick, pagerState = pagerState, - Modifier.padding(top = contentPadding.calculateTopPadding()) + Modifier.padding(top = contentPadding.calculateTopPadding()), + pages = pages ) } } @@ -100,7 +101,7 @@ fun HomePagerScreen( onPlantClick: (Plant) -> Unit, pagerState: PagerState, modifier: Modifier = Modifier, - pages: Array = SunflowerPage.values() + pages: Array ) { Column(modifier) { val coroutineScope = rememberCoroutineScope() @@ -129,7 +130,6 @@ fun HomePagerScreen( // Pages HorizontalPager( modifier = Modifier.background(MaterialTheme.colorScheme.background), - pageCount = pages.size, state = pagerState, verticalAlignment = Alignment.Top ) { index -> @@ -202,7 +202,8 @@ private fun HomeScreenPreview() { SunflowerTheme { HomePagerScreen( onPlantClick = {}, - pagerState = PagerState(), + pagerState = rememberPagerState(pageCount = {2}), + pages = SunflowerPage.values() ) } } diff --git a/app/src/main/java/com/google/samples/apps/sunflower/viewmodels/GalleryViewModel.kt b/app/src/main/java/com/google/samples/apps/sunflower/viewmodels/GalleryViewModel.kt index c58b8ac32..e3268d7e8 100644 --- a/app/src/main/java/com/google/samples/apps/sunflower/viewmodels/GalleryViewModel.kt +++ b/app/src/main/java/com/google/samples/apps/sunflower/viewmodels/GalleryViewModel.kt @@ -16,22 +16,56 @@ package com.google.samples.apps.sunflower.viewmodels +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import androidx.paging.PagingData import androidx.paging.cachedIn +import com.google.samples.apps.sunflower.data.UnsplashPhoto import com.google.samples.apps.sunflower.data.UnsplashRepository import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class GalleryViewModel @Inject constructor( savedStateHandle: SavedStateHandle, - repository: UnsplashRepository + private val repository: UnsplashRepository ) : ViewModel() { private var queryString: String? = savedStateHandle["plantName"] - val plantPictures = - repository.getSearchResultStream(queryString ?: "").cachedIn(viewModelScope) + + private val _plantPictures = MutableStateFlow?>(null) + val plantPictures: Flow> get() = _plantPictures.filterNotNull() + + private val _isRefreshing = mutableStateOf(false) + val isRefreshing: State get() = _isRefreshing + + init { + refreshData() + } + + + fun refreshData() { + _isRefreshing.value = true + + viewModelScope.launch { + delay(1000) + try { + _plantPictures.value = repository.getSearchResultStream(queryString ?: "").cachedIn(viewModelScope).first() + } catch (e: Exception) { + e.printStackTrace() + } finally { + _isRefreshing.value = false + } + } + } } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 05832e41d..6b9f6d7a9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -39,7 +39,7 @@ ktlint = "0.40.0" ktx = "1.7.0" lifecycle = "2.6.0-alpha04" material = "1.8.0-rc01" -material3 = "1.0.1" +material3 = "1.2.0-alpha11" # @keep minSdk = "23" monitor = "1.6.0"