From 304f3c02e0fd996d05b34082d0f0230b236aa206 Mon Sep 17 00:00:00 2001 From: Shinokuni Date: Thu, 11 Jan 2024 22:16:30 +0100 Subject: [PATCH] Add base of pagination in TimelineTab --- appcompose/build.gradle | 6 +- .../app/compose/timelime/TimelineTab.kt | 57 ++++++++----------- .../app/compose/timelime/TimelineViewModel.kt | 47 +++++++-------- .../compose/util/components/Placeholder.kt | 47 +++++++++++++++ db/build.gradle | 8 +-- 5 files changed, 100 insertions(+), 65 deletions(-) create mode 100644 appcompose/src/main/java/com/readrops/app/compose/util/components/Placeholder.kt diff --git a/appcompose/build.gradle b/appcompose/build.gradle index a8c9d9bc..a30d5e98 100644 --- a/appcompose/build.gradle +++ b/appcompose/build.gradle @@ -68,7 +68,7 @@ dependencies { androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' - def composeBom = platform('androidx.compose:compose-bom:2023.08.00') + def composeBom = platform('androidx.compose:compose-bom:2023.10.01') implementation composeBom androidTestImplementation composeBom @@ -107,7 +107,9 @@ dependencies { implementation "io.coil-kt:coil:2.4.0" implementation "io.coil-kt:coil-compose:2.4.0" - androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3" + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3' + androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1" implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1" diff --git a/appcompose/src/main/java/com/readrops/app/compose/timelime/TimelineTab.kt b/appcompose/src/main/java/com/readrops/app/compose/timelime/TimelineTab.kt index f5d7c727..5b643202 100644 --- a/appcompose/src/main/java/com/readrops/app/compose/timelime/TimelineTab.kt +++ b/appcompose/src/main/java/com/readrops/app/compose/timelime/TimelineTab.kt @@ -1,15 +1,12 @@ package com.readrops.app.compose.timelime +import android.util.Log import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Menu import androidx.compose.material3.CircularProgressIndicator @@ -27,11 +24,12 @@ import androidx.compose.material3.rememberDrawerState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.paging.LoadState +import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow @@ -42,6 +40,7 @@ import com.google.accompanist.swiperefresh.rememberSwipeRefreshState import com.readrops.app.compose.R import com.readrops.app.compose.item.ItemScreen import com.readrops.app.compose.timelime.drawer.TimelineDrawer +import com.readrops.app.compose.util.components.CenteredColumn import com.readrops.app.compose.util.theme.spacing import org.koin.androidx.compose.getViewModel @@ -60,11 +59,14 @@ object TimelineTab : Tab { override fun Content() { val viewModel = getViewModel() val state by viewModel.timelineState.collectAsStateWithLifecycle() + val items = state.itemState.collectAsLazyPagingItems() val navigator = LocalNavigator.currentOrThrow val context = LocalContext.current val scrollState = rememberLazyListState() + + // Use the depreciated refresh swipe as the material 3 one isn't available yet val swipeState = rememberSwipeRefreshState(state.isRefreshing) val drawerState = rememberDrawerState( initialValue = DrawerValue.Closed, @@ -131,7 +133,9 @@ object TimelineTab : Tab { ) } - IconButton(onClick = { }) { + IconButton( + onClick = { viewModel.refreshTimeline() } + ) { Icon( painter = painterResource(id = R.drawable.ic_sync), contentDescription = null @@ -154,21 +158,16 @@ object TimelineTab : Tab { onRefresh = { viewModel.refreshTimeline() }, modifier = Modifier.padding(paddingValues) ) { - when (val itemState = state.items) { - is ItemState.Loading -> { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - modifier = Modifier.fillMaxSize() - ) { + when { + items.isLoading() -> { + Log.d("TAG", "loading") + CenteredColumn { CircularProgressIndicator() } } - is ItemState.Error -> TODO() - is ItemState.Loaded -> { - val items = itemState.items.collectAsLazyPagingItems() - + items.isError() -> Text(text = "error") + else -> { LazyColumn( state = scrollState, contentPadding = PaddingValues(vertical = MaterialTheme.spacing.shortSpacing), @@ -176,7 +175,7 @@ object TimelineTab : Tab { ) { items( count = items.itemCount, - key = { items[it]!!.item.id }, + //key = { items[it]!! }, contentType = { "item_with_feed" } ) { itemCount -> val itemWithFeed = items[itemCount]!! @@ -192,6 +191,7 @@ object TimelineTab : Tab { onShare = { viewModel.shareItem(itemWithFeed.item, context) }, + compactLayout = true ) } } @@ -203,20 +203,11 @@ object TimelineTab : Tab { } } -@Composable -fun NoItemPlaceholder() { - val scrollState = rememberScrollState() - Column( - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier - .fillMaxSize() - .verticalScroll(scrollState) - ) { - Text( - text = "No item", - style = MaterialTheme.typography.displayMedium - ) - } +fun LazyPagingItems.isLoading(): Boolean { + return loadState.append is LoadState.Loading //|| loadState.refresh is LoadState.Loading +} + +fun LazyPagingItems.isError(): Boolean { + return loadState.append is LoadState.Error //|| loadState.refresh is LoadState.Error } \ No newline at end of file diff --git a/appcompose/src/main/java/com/readrops/app/compose/timelime/TimelineViewModel.kt b/appcompose/src/main/java/com/readrops/app/compose/timelime/TimelineViewModel.kt index dd41b6e5..c0b05173 100644 --- a/appcompose/src/main/java/com/readrops/app/compose/timelime/TimelineViewModel.kt +++ b/appcompose/src/main/java/com/readrops/app/compose/timelime/TimelineViewModel.kt @@ -26,6 +26,7 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.consumeAsFlow +import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -46,24 +47,23 @@ class TimelineViewModel( accountEvent.consumeAsFlow(), filters ) { account, filters -> + filters.accountId = account.id Pair(account, filters) }.collectLatest { (account, filters) -> - val query = ItemsQueryBuilder.buildItemsQuery(filters.copy(accountId = account.id)) + val query = ItemsQueryBuilder.buildItemsQuery(filters) _timelineState.update { it.copy( - items = ItemState.Loaded( - items = Pager( - config = PagingConfig( - pageSize = 100, - prefetchDistance = 150 - ), - pagingSourceFactory = { - database.newItemDao().selectAll(query) - }, - ).flow - .cachedIn(viewModelScope) - ) + itemState = Pager( + config = PagingConfig( + pageSize = 10, + prefetchDistance = 10 + ), + pagingSourceFactory = { + database.newItemDao().selectAll(query) + }, + ).flow + .cachedIn(viewModelScope) ) } @@ -86,7 +86,12 @@ class TimelineViewModel( } - _timelineState.update { it.copy(isRefreshing = false) } + _timelineState.update { + it.copy( + isRefreshing = false, + endSynchronizing = true + ) + } } } @@ -183,18 +188,8 @@ class TimelineViewModel( data class TimelineState( val isRefreshing: Boolean = false, val isDrawerOpen: Boolean = false, + val endSynchronizing: Boolean = false, val filters: QueryFilters = QueryFilters(), val foldersAndFeeds: Map> = emptyMap(), - val items: ItemState = ItemState.Loading + val itemState: Flow> = emptyFlow() ) - -sealed class ItemState { - @Immutable - object Loading : ItemState() - - @Immutable - data class Error(val exception: Exception) : ItemState() - - @Immutable - data class Loaded(val items: Flow>) : ItemState() -} diff --git a/appcompose/src/main/java/com/readrops/app/compose/util/components/Placeholder.kt b/appcompose/src/main/java/com/readrops/app/compose/util/components/Placeholder.kt new file mode 100644 index 00000000..38b6d933 --- /dev/null +++ b/appcompose/src/main/java/com/readrops/app/compose/util/components/Placeholder.kt @@ -0,0 +1,47 @@ +package com.readrops.app.compose.util.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.painter.Painter +import com.readrops.app.compose.util.toDp + + +@Composable +fun CenteredColumn( + content: @Composable () -> Unit +) { + Column( + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxSize() + ) { + content() + } +} + +@Composable +fun Placeholder( + text: String, + painter: Painter, +) { + CenteredColumn { + Icon( + painter = painter, + contentDescription = null, + modifier = Modifier.size(MaterialTheme.typography.displayMedium.toDp() * 1.5f) + ) + + Text( + text = text, + style = MaterialTheme.typography.displayMedium + ) + } +} \ No newline at end of file diff --git a/db/build.gradle b/db/build.gradle index f4a17b4c..302f00ca 100644 --- a/db/build.gradle +++ b/db/build.gradle @@ -97,9 +97,9 @@ dependencies { api "io.insert-koin:koin-androidx-compose:3.4.2" api "io.insert-koin:koin-android-compat:$rootProject.ext.koin_version" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4" - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4' + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3" + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3' - api "androidx.paging:paging-runtime-ktx:3.2.0" - api "androidx.paging:paging-compose:3.2.0" + api "androidx.paging:paging-runtime-ktx:3.2.1" + api "androidx.paging:paging-compose:3.2.1" }