mirror of https://github.com/readrops/Readrops.git
Add base of pagination in TimelineTab
This commit is contained in:
parent
413dba4db5
commit
304f3c02e0
|
@ -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"
|
||||||
|
|
|
@ -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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -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()
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue