11package dev.shorthouse.coinwatch.ui.screen.favourites
22
33import androidx.compose.animation.AnimatedVisibility
4+ import androidx.compose.animation.core.FastOutSlowInEasing
5+ import androidx.compose.animation.core.tween
46import androidx.compose.animation.scaleIn
57import androidx.compose.animation.scaleOut
68import androidx.compose.foundation.LocalOverscrollFactory
79import androidx.compose.foundation.horizontalScroll
810import androidx.compose.foundation.layout.Arrangement
911import androidx.compose.foundation.layout.PaddingValues
1012import androidx.compose.foundation.layout.Row
11- import androidx.compose.foundation.layout.fillMaxHeight
1213import androidx.compose.foundation.layout.fillMaxSize
1314import androidx.compose.foundation.layout.padding
14- import androidx.compose.foundation.lazy.LazyColumn
15- import androidx.compose.foundation.lazy.LazyListState
1615import androidx.compose.foundation.lazy.grid.GridCells
1716import androidx.compose.foundation.lazy.grid.GridItemSpan
1817import androidx.compose.foundation.lazy.grid.LazyGridState
1918import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
2019import androidx.compose.foundation.lazy.grid.rememberLazyGridState
21- import androidx.compose.foundation.lazy.rememberLazyListState
2220import androidx.compose.foundation.rememberScrollState
2321import androidx.compose.foundation.shape.CornerSize
2422import androidx.compose.foundation.shape.RoundedCornerShape
@@ -52,6 +50,7 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
5250import androidx.compose.ui.res.stringResource
5351import androidx.compose.ui.tooling.preview.Preview
5452import androidx.compose.ui.tooling.preview.PreviewParameter
53+ import androidx.compose.ui.tooling.preview.PreviewWrapper
5554import androidx.compose.ui.unit.dp
5655import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
5756import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -62,14 +61,13 @@ import dev.shorthouse.coinwatch.ui.component.CoinSortChip
6261import dev.shorthouse.coinwatch.ui.component.LoadingIndicator
6362import dev.shorthouse.coinwatch.ui.component.ScrollToTopFab
6463import dev.shorthouse.coinwatch.ui.insets.AppWindowInsets
64+ import dev.shorthouse.coinwatch.ui.preview.AppPreviewWrapper
6565import dev.shorthouse.coinwatch.ui.preview.FavouritesUiStatePreviewProvider
6666import dev.shorthouse.coinwatch.ui.screen.favourites.component.FavouriteCondensedItem
6767import dev.shorthouse.coinwatch.ui.screen.favourites.component.FavouriteItem
6868import dev.shorthouse.coinwatch.ui.screen.favourites.component.FavouritesEmptyState
6969import kotlinx.collections.immutable.ImmutableList
7070import kotlinx.coroutines.launch
71- import androidx.compose.ui.tooling.preview.PreviewWrapper
72- import dev.shorthouse.coinwatch.ui.preview.AppPreviewWrapper
7371
7472@Composable
7573fun FavouritesScreen (
@@ -111,21 +109,12 @@ fun FavouriteScreen(
111109) {
112110 val scope = rememberCoroutineScope()
113111 val scrollBehavior = TopAppBarDefaults .pinnedScrollBehavior()
114- val listState = rememberLazyListState()
115112 val gridState = rememberLazyGridState()
116113 val snackbarHostState = remember { SnackbarHostState () }
117114 val pullRefreshState = rememberPullToRefreshState()
118115 val showScrollToTopFab by remember {
119116 derivedStateOf {
120- gridState.firstVisibleItemIndex > 0 || listState.firstVisibleItemIndex > 0
121- }
122- }
123-
124- LaunchedEffect (uiState.isFavouritesCondensed) {
125- if (uiState.isFavouritesCondensed) {
126- gridState.scrollToItem(0 )
127- } else {
128- listState.scrollToItem(0 )
117+ gridState.firstVisibleItemIndex > 0
129118 }
130119 }
131120
@@ -149,11 +138,7 @@ fun FavouriteScreen(
149138 ScrollToTopFab (
150139 onClick = {
151140 scope.launch {
152- if (uiState.isFavouritesCondensed) {
153- listState.animateScrollToItem(0 )
154- } else {
155- gridState.animateScrollToItem(0 )
156- }
141+ gridState.animateScrollToItem(0 )
157142 }
158143 }
159144 )
@@ -192,8 +177,7 @@ fun FavouriteScreen(
192177 isFavouritesCondensed = uiState.isFavouritesCondensed,
193178 coinSort = uiState.coinSort,
194179 onUpdateCoinSort = onUpdateCoinSort,
195- gridState = gridState,
196- listState = listState
180+ gridState = gridState
197181 )
198182 }
199183 }
@@ -263,7 +247,6 @@ fun FavouritesContent(
263247 coinSort : CoinSort ,
264248 onUpdateCoinSort : (CoinSort ) -> Unit ,
265249 gridState : LazyGridState ,
266- listState : LazyListState ,
267250 modifier : Modifier = Modifier ,
268251) {
269252 CompositionLocalProvider (LocalOverscrollFactory provides null ) {
@@ -272,21 +255,11 @@ fun FavouritesContent(
272255 FavouritesEmptyState ()
273256 }
274257
275- isFavouritesCondensed -> {
276- FavouritesList (
277- favouriteCoins = favouriteCoins,
278- onCoinClick = onCoinClick,
279- coinSort = coinSort,
280- onUpdateCoinSort = onUpdateCoinSort,
281- listState = listState,
282- modifier = modifier
283- )
284- }
285-
286258 else -> {
287259 FavouritesGrid (
288260 favouriteCoins = favouriteCoins,
289261 onCoinClick = onCoinClick,
262+ isFavouritesCondensed = isFavouritesCondensed,
290263 coinSort = coinSort,
291264 onUpdateCoinSort = onUpdateCoinSort,
292265 gridState = gridState,
@@ -301,70 +274,42 @@ fun FavouritesContent(
301274fun FavouritesGrid (
302275 favouriteCoins : ImmutableList <FavouriteCoin >,
303276 onCoinClick : (FavouriteCoin ) -> Unit ,
277+ isFavouritesCondensed : Boolean ,
304278 coinSort : CoinSort ,
305279 onUpdateCoinSort : (CoinSort ) -> Unit ,
306280 gridState : LazyGridState ,
307281 modifier : Modifier = Modifier ,
308282) {
309283 LazyVerticalGrid (
310284 state = gridState,
311- columns = GridCells .Adaptive (minSize = 140 .dp),
312- horizontalArrangement = Arrangement .spacedBy(16 .dp),
313- verticalArrangement = Arrangement .spacedBy(16 .dp),
285+ columns = if (isFavouritesCondensed) {
286+ GridCells .Fixed (1 )
287+ } else {
288+ GridCells .Adaptive (minSize = 140 .dp)
289+ },
290+ horizontalArrangement = Arrangement .spacedBy(
291+ if (isFavouritesCondensed) 0 .dp else 16 .dp
292+ ),
293+ verticalArrangement = Arrangement .spacedBy(
294+ if (isFavouritesCondensed) 0 .dp else 16 .dp
295+ ),
314296 contentPadding = PaddingValues (start = 12 .dp, end = 12 .dp, bottom = 80 .dp),
315- modifier = modifier.fillMaxHeight()
297+ modifier = modifier,
316298 ) {
317299 item(
318300 span = { GridItemSpan (maxLineSpan) },
319301 ) {
320- Row (
321- horizontalArrangement = Arrangement .spacedBy(8 .dp),
322- modifier = Modifier .horizontalScroll(rememberScrollState())
323- ) {
324- CoinSort .entries.forEach { coinSortEntry ->
325- CoinSortChip (
326- coinSort = coinSortEntry,
327- selected = coinSortEntry == coinSort,
328- onClick = { onUpdateCoinSort(coinSortEntry) }
329- )
330- }
331- }
332- }
333- items(
334- count = favouriteCoins.size,
335- key = { favouriteCoins[it].id },
336- itemContent = { index ->
337- val favouriteCoinItem = favouriteCoins[index]
338-
339- FavouriteItem (
340- favouriteCoin = favouriteCoinItem,
341- onCoinClick = { onCoinClick(favouriteCoinItem) }
342- )
343- }
344- )
345- }
346- }
347-
348- @Composable
349- fun FavouritesList (
350- favouriteCoins : ImmutableList <FavouriteCoin >,
351- onCoinClick : (FavouriteCoin ) -> Unit ,
352- coinSort : CoinSort ,
353- onUpdateCoinSort : (CoinSort ) -> Unit ,
354- listState : LazyListState ,
355- modifier : Modifier = Modifier ,
356- ) {
357- LazyColumn (
358- state = listState,
359- contentPadding = PaddingValues (start = 12 .dp, end = 12 .dp, bottom = 80 .dp),
360- modifier = modifier.fillMaxHeight()
361- ) {
362- item {
363302 Row (
364303 horizontalArrangement = Arrangement .spacedBy(8 .dp),
365304 modifier = Modifier
366305 .horizontalScroll(rememberScrollState())
367- .padding(bottom = 8 .dp)
306+ .padding(
307+ bottom = if (isFavouritesCondensed) {
308+ 16 .dp
309+ } else {
310+ 0 .dp
311+ }
312+ )
368313 ) {
369314 CoinSort .entries.forEach { coinSortEntry ->
370315 CoinSortChip (
@@ -381,27 +326,46 @@ fun FavouritesList(
381326 itemContent = { index ->
382327 val favouriteCoinItem = favouriteCoins[index]
383328
384- val cardShape = when {
385- favouriteCoins.size == 1 -> MaterialTheme .shapes.medium
329+ if (isFavouritesCondensed) {
330+ val cardShape = when {
331+ favouriteCoins.size == 1 -> MaterialTheme .shapes.medium
386332
387- index == 0 -> MaterialTheme .shapes.medium.copy(
388- bottomStart = CornerSize (0 .dp),
389- bottomEnd = CornerSize (0 .dp)
390- )
333+ index == 0 -> MaterialTheme .shapes.medium.copy(
334+ bottomStart = CornerSize (0 .dp),
335+ bottomEnd = CornerSize (0 .dp)
336+ )
391337
392- index == favouriteCoins.lastIndex -> MaterialTheme .shapes.medium.copy(
393- topStart = CornerSize (0 .dp),
394- topEnd = CornerSize (0 .dp)
395- )
338+ index == favouriteCoins.lastIndex -> MaterialTheme .shapes.medium.copy(
339+ topStart = CornerSize (0 .dp),
340+ topEnd = CornerSize (0 .dp)
341+ )
396342
397- else -> RoundedCornerShape (0 .dp)
398- }
343+ else -> RoundedCornerShape (0 .dp)
344+ }
399345
400- FavouriteCondensedItem (
401- favouriteCoin = favouriteCoinItem,
402- onCoinClick = { onCoinClick(favouriteCoinItem) },
403- cardShape = cardShape
404- )
346+ FavouriteCondensedItem (
347+ favouriteCoin = favouriteCoinItem,
348+ onCoinClick = { onCoinClick(favouriteCoinItem) },
349+ cardShape = cardShape,
350+ modifier = Modifier .animateItem(
351+ placementSpec = tween(
352+ durationMillis = 200 ,
353+ easing = FastOutSlowInEasing
354+ )
355+ )
356+ )
357+ } else {
358+ FavouriteItem (
359+ favouriteCoin = favouriteCoinItem,
360+ onCoinClick = { onCoinClick(favouriteCoinItem) },
361+ modifier = Modifier .animateItem(
362+ placementSpec = tween(
363+ durationMillis = 200 ,
364+ easing = FastOutSlowInEasing
365+ )
366+ )
367+ )
368+ }
405369 }
406370 )
407371 }
0 commit comments