Add base of pagination in TimelineTab

This commit is contained in:
Shinokuni 2024-01-11 22:16:30 +01:00
parent 413dba4db5
commit 304f3c02e0
5 changed files with 100 additions and 65 deletions

View File

@ -68,7 +68,7 @@ dependencies {
androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' 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 implementation composeBom
androidTestImplementation composeBom androidTestImplementation composeBom
@ -107,7 +107,9 @@ dependencies {
implementation "io.coil-kt:coil:2.4.0" implementation "io.coil-kt:coil:2.4.0"
implementation "io.coil-kt:coil-compose: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-ktx:2.6.1"
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1" implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1"

View File

@ -1,15 +1,12 @@
package com.readrops.app.compose.timelime package com.readrops.app.compose.timelime
import android.util.Log
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState 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.Icons
import androidx.compose.material.icons.filled.Menu import androidx.compose.material.icons.filled.Menu
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
@ -27,11 +24,12 @@ import androidx.compose.material3.rememberDrawerState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.paging.LoadState
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems
import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow 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.R
import com.readrops.app.compose.item.ItemScreen import com.readrops.app.compose.item.ItemScreen
import com.readrops.app.compose.timelime.drawer.TimelineDrawer 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 com.readrops.app.compose.util.theme.spacing
import org.koin.androidx.compose.getViewModel import org.koin.androidx.compose.getViewModel
@ -60,11 +59,14 @@ object TimelineTab : Tab {
override fun Content() { override fun Content() {
val viewModel = getViewModel<TimelineViewModel>() val viewModel = getViewModel<TimelineViewModel>()
val state by viewModel.timelineState.collectAsStateWithLifecycle() val state by viewModel.timelineState.collectAsStateWithLifecycle()
val items = state.itemState.collectAsLazyPagingItems()
val navigator = LocalNavigator.currentOrThrow val navigator = LocalNavigator.currentOrThrow
val context = LocalContext.current val context = LocalContext.current
val scrollState = rememberLazyListState() val scrollState = rememberLazyListState()
// Use the depreciated refresh swipe as the material 3 one isn't available yet
val swipeState = rememberSwipeRefreshState(state.isRefreshing) val swipeState = rememberSwipeRefreshState(state.isRefreshing)
val drawerState = rememberDrawerState( val drawerState = rememberDrawerState(
initialValue = DrawerValue.Closed, initialValue = DrawerValue.Closed,
@ -131,7 +133,9 @@ object TimelineTab : Tab {
) )
} }
IconButton(onClick = { }) { IconButton(
onClick = { viewModel.refreshTimeline() }
) {
Icon( Icon(
painter = painterResource(id = R.drawable.ic_sync), painter = painterResource(id = R.drawable.ic_sync),
contentDescription = null contentDescription = null
@ -154,21 +158,16 @@ object TimelineTab : Tab {
onRefresh = { viewModel.refreshTimeline() }, onRefresh = { viewModel.refreshTimeline() },
modifier = Modifier.padding(paddingValues) modifier = Modifier.padding(paddingValues)
) { ) {
when (val itemState = state.items) { when {
is ItemState.Loading -> { items.isLoading() -> {
Column( Log.d("TAG", "loading")
horizontalAlignment = Alignment.CenterHorizontally, CenteredColumn {
verticalArrangement = Arrangement.Center,
modifier = Modifier.fillMaxSize()
) {
CircularProgressIndicator() CircularProgressIndicator()
} }
} }
is ItemState.Error -> TODO() items.isError() -> Text(text = "error")
is ItemState.Loaded -> { else -> {
val items = itemState.items.collectAsLazyPagingItems()
LazyColumn( LazyColumn(
state = scrollState, state = scrollState,
contentPadding = PaddingValues(vertical = MaterialTheme.spacing.shortSpacing), contentPadding = PaddingValues(vertical = MaterialTheme.spacing.shortSpacing),
@ -176,7 +175,7 @@ object TimelineTab : Tab {
) { ) {
items( items(
count = items.itemCount, count = items.itemCount,
key = { items[it]!!.item.id }, //key = { items[it]!! },
contentType = { "item_with_feed" } contentType = { "item_with_feed" }
) { itemCount -> ) { itemCount ->
val itemWithFeed = items[itemCount]!! val itemWithFeed = items[itemCount]!!
@ -192,6 +191,7 @@ object TimelineTab : Tab {
onShare = { onShare = {
viewModel.shareItem(itemWithFeed.item, context) viewModel.shareItem(itemWithFeed.item, context)
}, },
compactLayout = true
) )
} }
} }
@ -203,20 +203,11 @@ object TimelineTab : Tab {
} }
} }
@Composable
fun NoItemPlaceholder() {
val scrollState = rememberScrollState()
Column( fun <T : Any> LazyPagingItems<T>.isLoading(): Boolean {
verticalArrangement = Arrangement.Center, return loadState.append is LoadState.Loading //|| loadState.refresh is LoadState.Loading
horizontalAlignment = Alignment.CenterHorizontally, }
modifier = Modifier
.fillMaxSize() fun <T : Any> LazyPagingItems<T>.isError(): Boolean {
.verticalScroll(scrollState) return loadState.append is LoadState.Error //|| loadState.refresh is LoadState.Error
) {
Text(
text = "No item",
style = MaterialTheme.typography.displayMedium
)
}
} }

View File

@ -26,6 +26,7 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.consumeAsFlow import kotlinx.coroutines.flow.consumeAsFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -46,24 +47,23 @@ class TimelineViewModel(
accountEvent.consumeAsFlow(), accountEvent.consumeAsFlow(),
filters filters
) { account, filters -> ) { account, filters ->
filters.accountId = account.id
Pair(account, filters) Pair(account, filters)
}.collectLatest { (account, filters) -> }.collectLatest { (account, filters) ->
val query = ItemsQueryBuilder.buildItemsQuery(filters.copy(accountId = account.id)) val query = ItemsQueryBuilder.buildItemsQuery(filters)
_timelineState.update { _timelineState.update {
it.copy( it.copy(
items = ItemState.Loaded( itemState = Pager(
items = Pager( config = PagingConfig(
config = PagingConfig( pageSize = 10,
pageSize = 100, prefetchDistance = 10
prefetchDistance = 150 ),
), pagingSourceFactory = {
pagingSourceFactory = { database.newItemDao().selectAll(query)
database.newItemDao().selectAll(query) },
}, ).flow
).flow .cachedIn(viewModelScope)
.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( data class TimelineState(
val isRefreshing: Boolean = false, val isRefreshing: Boolean = false,
val isDrawerOpen: Boolean = false, val isDrawerOpen: Boolean = false,
val endSynchronizing: Boolean = false,
val filters: QueryFilters = QueryFilters(), val filters: QueryFilters = QueryFilters(),
val foldersAndFeeds: Map<Folder?, List<Feed>> = emptyMap(), val foldersAndFeeds: Map<Folder?, List<Feed>> = emptyMap(),
val items: ItemState = ItemState.Loading val itemState: Flow<PagingData<ItemWithFeed>> = emptyFlow()
) )
sealed class ItemState {
@Immutable
object Loading : ItemState()
@Immutable
data class Error(val exception: Exception) : ItemState()
@Immutable
data class Loaded(val items: Flow<PagingData<ItemWithFeed>>) : ItemState()
}

View File

@ -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
)
}
}

View File

@ -97,9 +97,9 @@ dependencies {
api "io.insert-koin:koin-androidx-compose:3.4.2" api "io.insert-koin:koin-androidx-compose:3.4.2"
api "io.insert-koin:koin-android-compat:$rootProject.ext.koin_version" 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-android:1.7.3"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3'
api "androidx.paging:paging-runtime-ktx:3.2.0" api "androidx.paging:paging-runtime-ktx:3.2.1"
api "androidx.paging:paging-compose:3.2.0" api "androidx.paging:paging-compose:3.2.1"
} }