Skip to content
This repository has been archived by the owner on Aug 20, 2024. It is now read-only.

Commit

Permalink
NodeScreen: Implement paging support
Browse files Browse the repository at this point in the history
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
  • Loading branch information
theimpulson committed Mar 23, 2024
1 parent a2d2c46 commit 7452a18
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 48 deletions.
5 changes: 5 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -150,4 +150,9 @@ dependencies {
val coroutinesVersion = "1.8.0"
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion")

// Paging3
val pagingVersion = "3.2.1"
implementation("androidx.paging:paging-runtime-ktx:$pagingVersion")
implementation("androidx.paging:paging-compose:$pagingVersion")
}
5 changes: 4 additions & 1 deletion app/src/main/java/io/aayush/relabs/network/XDAInterface.kt
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ interface XDAInterface {
suspend fun getWatchedThreads(): Response<Threads>

@GET("forums/{id}/threads/")
suspend fun getThreadsByNode(@Path("id") nodeID: Int): Response<Threads>
suspend fun getThreadsByNode(
@Path("id") nodeID: Int,
@Query("page") page: Int? = null,
): Response<Threads>

@POST("threads/{id}/audapp-watch")
suspend fun watchThread(@Path("id") threadID: Int): Response<Success>
Expand Down
11 changes: 9 additions & 2 deletions app/src/main/java/io/aayush/relabs/network/XDARepository.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.aayush.relabs.network

import android.util.Log
import androidx.paging.PagingData
import io.aayush.relabs.network.data.common.Success
import io.aayush.relabs.network.data.alert.Alerts
import io.aayush.relabs.network.data.expo.ExpoData
Expand All @@ -9,9 +10,12 @@ import io.aayush.relabs.network.data.post.PostInfo
import io.aayush.relabs.network.data.post.PostReply
import io.aayush.relabs.network.data.react.PostReact
import io.aayush.relabs.network.data.react.React
import io.aayush.relabs.network.data.thread.Thread
import io.aayush.relabs.network.data.thread.ThreadInfo
import io.aayush.relabs.network.data.thread.Threads
import io.aayush.relabs.network.data.user.Me
import io.aayush.relabs.network.paging.GenericPagingSource.Companion.createPager
import kotlinx.coroutines.flow.Flow
import java.util.UUID
import okhttp3.MultipartBody
import retrofit2.Response
Expand Down Expand Up @@ -71,8 +75,11 @@ class XDARepository @Inject constructor(
return safeExecute { xdaInterface.getWatchedThreads() }
}

suspend fun getThreadsByNode(nodeID: Int): Threads? {
return safeExecute { xdaInterface.getThreadsByNode(nodeID) }
fun getThreadsByNode(nodeID: Int): Flow<PagingData<Thread>> {
return createPager { page ->
val threads = safeExecute { xdaInterface.getThreadsByNode(nodeID, page) }
threads?.let { if (page == 1) it.sticky + it.threads else it.threads }.orEmpty()
}.flow
}

suspend fun watchThread(threadID: Int): Success? {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package io.aayush.relabs.network.paging

import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingSource
import androidx.paging.PagingState
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

class GenericPagingSource<T : Any>(
private val totalPages: Int? = null,
private val block: suspend (Int) -> List<T>
) : PagingSource<Int, T>() {

companion object {
private const val DEFAULT_PAGE_SIZE = 20

fun <T : Any> createPager(
totalPages: Int? = null,
pageSize: Int = DEFAULT_PAGE_SIZE,
enablePlaceholders: Boolean = false,
block: suspend (Int) -> List<T>
): Pager<Int, T> = Pager(
config = PagingConfig(enablePlaceholders = enablePlaceholders, pageSize = pageSize),
pagingSourceFactory = { GenericPagingSource(totalPages, block) }
)
}

override suspend fun load(params: LoadParams<Int>): LoadResult<Int, T> {
val page = params.key ?: 1
return try {
withContext(Dispatchers.IO) {
val response = block(page)
LoadResult.Page(
data = response,
prevKey = if (page == 1) null else page - 1,
nextKey = if (totalPages != null && page == totalPages) null else page + 1
)
}
} catch (e: Exception) {
LoadResult.Error(e)
}
}

override fun getRefreshKey(state: PagingState<Int, T>): Int? {
return state.anchorPosition?.let { anchorPosition ->
state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
}
}
}
39 changes: 22 additions & 17 deletions app/src/main/java/io/aayush/relabs/ui/screens/node/NodeScreen.kt
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
package io.aayush.relabs.ui.screens.node

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavHostController
import io.aayush.relabs.network.data.thread.Thread
import androidx.paging.LoadState
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.itemContentType
import androidx.paging.compose.itemKey
import io.aayush.relabs.ui.components.MainTopAppBar
import io.aayush.relabs.ui.components.ThreadPreviewItem
import io.aayush.relabs.ui.navigation.Screen
Expand All @@ -27,12 +25,8 @@ fun NodeScreen(
nodeTitle: String = String(),
viewModel: NodeViewModel = hiltViewModel()
) {
val loading: Boolean by viewModel.loading.collectAsStateWithLifecycle()
val threads: List<Thread>? by viewModel.threads.collectAsStateWithLifecycle()

LaunchedEffect(key1 = Unit) {
viewModel.getThreads(nodeID)
}
val threads = viewModel.getThreads(nodeID).collectAsLazyPagingItems()

Scaffold(
modifier = Modifier.fillMaxSize(),
Expand All @@ -44,16 +38,27 @@ fun NodeScreen(
)
}
) {
Column(modifier = Modifier.padding(it)) {
if (loading) {
LazyColumn(modifier = Modifier.fillMaxHeight()) {
LazyColumn(
modifier = Modifier
.fillMaxHeight()
.padding(it)
) {
when (threads.loadState.refresh) {
is LoadState.Error -> {
// TODO: Handle first load error
}
is LoadState.Loading -> {
items(20) {
ThreadPreviewItem(modifier = Modifier.padding(10.dp), loading = true)
}
}
} else {
LazyColumn(modifier = Modifier.fillMaxHeight()) {
items(items = threads ?: emptyList(), key = { t -> t.id }) { thread ->
else -> {
items(
count = threads.itemCount,
key = threads.itemKey { t -> t.id },
contentType = threads.itemContentType { "Threads" }
) { index ->
val thread = threads[index] ?: return@items
ThreadPreviewItem(
modifier = Modifier.padding(10.dp),
avatarURL = thread.user.avatar?.data?.medium ?: String(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,20 @@ package io.aayush.relabs.ui.screens.node

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData
import androidx.paging.cachedIn
import dagger.hilt.android.lifecycle.HiltViewModel
import io.aayush.relabs.network.XDARepository
import io.aayush.relabs.network.data.thread.Thread
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject

@HiltViewModel
class NodeViewModel @Inject constructor(
private val xdaRepository: XDARepository
) : ViewModel() {

private val _loading = MutableStateFlow(false)
val loading = _loading.asStateFlow()

private val _threads = MutableStateFlow<List<Thread>?>(emptyList())
val threads = _threads.asStateFlow()

fun getThreads(nodeID: Int) {
if (!threads.value.isNullOrEmpty()) return
viewModelScope.launch(Dispatchers.IO) {
fetch {
_threads.value = xdaRepository.getThreadsByNode(nodeID)?.let {
it.sticky + it.threads
}?.distinctBy { it.id }
}
}
}

private inline fun <T> fetch(block: () -> T): T? {
return try {
_loading.value = true
block()
} finally {
_loading.value = false
}
fun getThreads(nodeID: Int): Flow<PagingData<Thread>> {
return xdaRepository.getThreadsByNode(nodeID).cachedIn(viewModelScope)
}
}

0 comments on commit 7452a18

Please sign in to comment.