From adaaa153b667a790a9400ff93d53f0b3231a7070 Mon Sep 17 00:00:00 2001 From: Sahil Khan <85223122+sahilsk3333@users.noreply.github.com> Date: Wed, 15 Nov 2023 17:24:15 +0530 Subject: [PATCH 1/6] Replaced TopAppBar with CenterAlignedTopAppBar, Fix issue #857 App title moves --- .../google/samples/apps/sunflower/compose/home/HomeScreen.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 d7b6400e2..5b274d74a 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 @@ -30,6 +30,7 @@ import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.PagerState import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -38,7 +39,6 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.Tab import androidx.compose.material3.TabRow import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable @@ -169,7 +169,7 @@ private fun HomeTopAppBar( scrollBehavior: TopAppBarScrollBehavior, modifier: Modifier = Modifier ) { - TopAppBar( + CenterAlignedTopAppBar( title = { Row( Modifier.fillMaxWidth(), From 7fa3031c24249c46671aa664b0d3366715c3745d Mon Sep 17 00:00:00 2001 From: Sahil Khan <85223122+sahilsk3333@users.noreply.github.com> Date: Sun, 26 Nov 2023 16:41:46 +0530 Subject: [PATCH 2/6] Feature pull to refresh GalleryScreen issue[#866] --- .../compose/gallery/GalleryScreen.kt | 79 ++++++++++++++----- .../apps/sunflower/compose/home/HomeScreen.kt | 13 +-- .../sunflower/viewmodels/GalleryViewModel.kt | 40 +++++++++- gradle/libs.versions.toml | 2 +- 4 files changed, 104 insertions(+), 30 deletions(-) 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" From 8064a5b8e6edeb9ab9d2c3cdfab6d37536696be3 Mon Sep 17 00:00:00 2001 From: Sahil Khan <85223122+sahilsk3333@users.noreply.github.com> Date: Mon, 11 Dec 2023 20:28:04 +0530 Subject: [PATCH 3/6] Moved isRefreshing state to compose --- .../compose/gallery/GalleryScreen.kt | 28 +++++++++++++++---- .../sunflower/viewmodels/GalleryViewModel.kt | 12 +------- 2 files changed, 23 insertions(+), 17 deletions(-) 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 0409ae390..8898ecb80 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 @@ -34,6 +34,10 @@ 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.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.input.nestedscroll.nestedScroll @@ -43,6 +47,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.hilt.navigation.compose.hiltViewModel +import androidx.paging.LoadState import androidx.paging.PagingData import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems @@ -66,10 +71,8 @@ fun GalleryScreen( onPhotoClick = onPhotoClick, onUpClick = onUpClick, onPullToRefresh = viewModel::refreshData, - isRefreshing = viewModel.isRefreshing.value ) } - @OptIn(ExperimentalMaterial3Api::class) @Composable private fun GalleryScreen( @@ -77,7 +80,6 @@ private fun GalleryScreen( onPhotoClick: (UnsplashPhoto) -> Unit = {}, onUpClick: () -> Unit = {}, onPullToRefresh: () -> Unit, - isRefreshing: Boolean ) { Scaffold( topBar = { @@ -85,11 +87,13 @@ private fun GalleryScreen( }, ) { padding -> + var isRefreshing by remember { mutableStateOf(false) } val pullToRefreshState = rememberPullToRefreshState() if (pullToRefreshState.isRefreshing){ LaunchedEffect(Unit){ onPullToRefresh() + isRefreshing = true } } LaunchedEffect(isRefreshing){ @@ -98,14 +102,26 @@ private fun GalleryScreen( } } + val pagingItems: LazyPagingItems = + plantPictures.collectAsLazyPagingItems() + + LaunchedEffect(pagingItems.loadState) { + when (pagingItems.loadState.refresh) { + is LoadState.Loading -> Unit + is LoadState.Error,is LoadState.NotLoading -> { + isRefreshing = false + } + } + } + + Box( modifier = Modifier .padding(padding) .nestedScroll(pullToRefreshState.nestedScrollConnection) ) { - val pagingItems: LazyPagingItems = - plantPictures.collectAsLazyPagingItems() + LazyVerticalGrid( columns = GridCells.Fixed(2), contentPadding = PaddingValues(all = dimensionResource(id = R.dimen.card_side_margin)) @@ -163,7 +179,7 @@ private fun GalleryTopBar( private fun GalleryScreenPreview( @PreviewParameter(GalleryScreenPreviewParamProvider::class) plantPictures: Flow> ) { - GalleryScreen(plantPictures = plantPictures, onPullToRefresh = {}, isRefreshing = false) + GalleryScreen(plantPictures = plantPictures, onPullToRefresh = {}) } private class GalleryScreenPreviewParamProvider : 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 e3268d7e8..5dc35ef12 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,8 +16,6 @@ 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 @@ -26,7 +24,6 @@ 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 @@ -46,25 +43,18 @@ class GalleryViewModel @Inject constructor( 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() + _plantPictures.value = repository.getSearchResultStream(queryString ?: "").cachedIn(viewModelScope).first() } catch (e: Exception) { e.printStackTrace() - } finally { - _isRefreshing.value = false } } } From ec0649992fa7457c7431409a8e14409e6a14a1e9 Mon Sep 17 00:00:00 2001 From: Sahil Khan <85223122+sahilsk3333@users.noreply.github.com> Date: Thu, 21 Dec 2023 09:54:32 +0530 Subject: [PATCH 4/6] Remove unnecessary isLoading state --- .../apps/sunflower/compose/gallery/GalleryScreen.kt | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) 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 8898ecb80..f4ea7fddf 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 @@ -87,19 +87,10 @@ private fun GalleryScreen( }, ) { padding -> - var isRefreshing by remember { mutableStateOf(false) } val pullToRefreshState = rememberPullToRefreshState() if (pullToRefreshState.isRefreshing){ - LaunchedEffect(Unit){ onPullToRefresh() - isRefreshing = true - } - } - LaunchedEffect(isRefreshing){ - if (!isRefreshing){ - pullToRefreshState.endRefresh() - } } val pagingItems: LazyPagingItems = @@ -109,13 +100,11 @@ private fun GalleryScreen( when (pagingItems.loadState.refresh) { is LoadState.Loading -> Unit is LoadState.Error,is LoadState.NotLoading -> { - isRefreshing = false + pullToRefreshState.endRefresh() } } } - - Box( modifier = Modifier .padding(padding) From f3876587993bdc29f83d1b66c37f9acc2ef1dc5f Mon Sep 17 00:00:00 2001 From: Chris Arriola Date: Tue, 26 Dec 2023 09:27:01 -0800 Subject: [PATCH 5/6] Apply suggestions from code review --- .../samples/apps/sunflower/compose/gallery/GalleryScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 f4ea7fddf..6f7b3b4eb 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 @@ -89,7 +89,7 @@ private fun GalleryScreen( val pullToRefreshState = rememberPullToRefreshState() - if (pullToRefreshState.isRefreshing){ + if (pullToRefreshState.isRefreshing) { onPullToRefresh() } From ceb828dc08e1c3b865ee5c64f4848ebf5caebd2d Mon Sep 17 00:00:00 2001 From: Sahil Khan <85223122+sahilsk3333@users.noreply.github.com> Date: Wed, 27 Dec 2023 08:51:35 +0530 Subject: [PATCH 6/6] Remove unnecessary imports --- .../samples/apps/sunflower/compose/gallery/GalleryScreen.kt | 4 ---- .../google/samples/apps/sunflower/compose/home/HomeScreen.kt | 1 - 2 files changed, 5 deletions(-) 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 60f5a7ba0..3df1ffd7f 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 @@ -34,10 +34,6 @@ 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.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.input.nestedscroll.nestedScroll 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 4207f5fad..7a32e18d5 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 @@ -102,7 +102,6 @@ fun HomePagerScreen( pagerState: PagerState, pages: Array, modifier: Modifier = Modifier, - pages: Array ) { Column(modifier) { val coroutineScope = rememberCoroutineScope()