mirror of
https://github.com/msfjarvis/compose-lobsters.git
synced 2024-06-03 04:18:58 +05:30
176 lines
6.8 KiB
Kotlin
176 lines
6.8 KiB
Kotlin
/*
|
|
* Copyright © 2024 Harsh Shandilya.
|
|
* Use of this source code is governed by an MIT-style
|
|
* license that can be found in the LICENSE file or at
|
|
* https://opensource.org/licenses/MIT.
|
|
*/
|
|
package dev.msfjarvis.claw.android.paging
|
|
|
|
import androidx.paging.ExperimentalPagingApi
|
|
import androidx.paging.LoadType
|
|
import androidx.paging.PagingState
|
|
import androidx.paging.RemoteMediator
|
|
import com.slack.eithernet.ApiResult.Failure.ApiFailure
|
|
import com.slack.eithernet.ApiResult.Failure.HttpFailure
|
|
import com.slack.eithernet.ApiResult.Failure.NetworkFailure
|
|
import com.slack.eithernet.ApiResult.Failure.UnknownFailure
|
|
import com.slack.eithernet.ApiResult.Success
|
|
import dagger.assisted.Assisted
|
|
import dagger.assisted.AssistedFactory
|
|
import dagger.assisted.AssistedInject
|
|
import dev.msfjarvis.claw.android.paging.LobstersPagingSource.Companion.STARTING_PAGE_INDEX
|
|
import dev.msfjarvis.claw.android.viewmodel.ReadPostsRepository
|
|
import dev.msfjarvis.claw.android.viewmodel.SavedPostsRepository
|
|
import dev.msfjarvis.claw.core.injection.DatabaseDispatcher
|
|
import dev.msfjarvis.claw.core.injection.IODispatcher
|
|
import dev.msfjarvis.claw.database.local.NewestPosts
|
|
import dev.msfjarvis.claw.database.local.NewestPostsKeys
|
|
import dev.msfjarvis.claw.database.local.NewestPostsKeysQueries
|
|
import dev.msfjarvis.claw.database.local.NewestPostsQueries
|
|
import dev.msfjarvis.claw.database.local.SavedPost
|
|
import dev.msfjarvis.claw.model.LobstersPost
|
|
import dev.msfjarvis.claw.model.UIPost
|
|
import java.io.IOException
|
|
import kotlin.time.Duration.Companion.milliseconds
|
|
import kotlin.time.Duration.Companion.seconds
|
|
import kotlinx.coroutines.CoroutineDispatcher
|
|
import kotlinx.coroutines.flow.first
|
|
import kotlinx.coroutines.withContext
|
|
|
|
@OptIn(ExperimentalPagingApi::class)
|
|
class CachingRemoteMediator
|
|
@AssistedInject
|
|
constructor(
|
|
@Assisted private val remoteFetcher: RemoteFetcher<LobstersPost>,
|
|
@IODispatcher private val ioDispatcher: CoroutineDispatcher,
|
|
@DatabaseDispatcher private val databaseDispatcher: CoroutineDispatcher,
|
|
private val savedPostsRepository: SavedPostsRepository,
|
|
private val readPostsRepository: ReadPostsRepository,
|
|
private val postsDatabase: NewestPostsQueries,
|
|
private val postKeysDatabase: NewestPostsKeysQueries,
|
|
) : RemoteMediator<Int, UIPost>() {
|
|
private val cacheTimeout = 10.seconds
|
|
private lateinit var savedPosts: List<String>
|
|
private lateinit var readPosts: List<String>
|
|
|
|
override suspend fun initialize(): InitializeAction {
|
|
savedPosts = savedPostsRepository.savedPosts.first().map(SavedPost::shortId)
|
|
readPosts = readPostsRepository.readPosts.first()
|
|
val lastAddition =
|
|
postsDatabase.getTimeOfLastAddition().executeAsOneOrNull()?.toLongOrNull()?.milliseconds
|
|
val currentDuration = System.currentTimeMillis().milliseconds
|
|
return if (lastAddition != null && currentDuration - lastAddition < cacheTimeout) {
|
|
InitializeAction.SKIP_INITIAL_REFRESH
|
|
} else {
|
|
InitializeAction.LAUNCH_INITIAL_REFRESH
|
|
}
|
|
}
|
|
|
|
override suspend fun load(loadType: LoadType, state: PagingState<Int, UIPost>): MediatorResult {
|
|
val page =
|
|
when (loadType) {
|
|
LoadType.REFRESH -> {
|
|
val remoteKeys = getRemoteKeyClosestToCurrentPosition(state)
|
|
remoteKeys?.nextKey?.minus(1) ?: STARTING_PAGE_INDEX
|
|
}
|
|
LoadType.PREPEND -> {
|
|
val remoteKeys = getRemoteKeyForFirstItem(state)
|
|
val prevKey =
|
|
remoteKeys?.prevKey
|
|
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
|
|
prevKey
|
|
}
|
|
LoadType.APPEND -> {
|
|
val remoteKeys = getRemoteKeyForLastItem(state)
|
|
val nextKey =
|
|
remoteKeys?.nextKey
|
|
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
|
|
nextKey
|
|
}
|
|
}
|
|
return when (val result = withContext(ioDispatcher) { remoteFetcher.getItemsAtPage(page) }) {
|
|
is Success -> {
|
|
val items = result.value
|
|
val endOfPaginationReached = items.isEmpty()
|
|
withContext(databaseDispatcher) {
|
|
if (loadType == LoadType.REFRESH) {
|
|
postsDatabase.clearCache()
|
|
postKeysDatabase.clearRemoteKeys()
|
|
}
|
|
val prevKey = if (page == STARTING_PAGE_INDEX) null else page - 1
|
|
val nextKey = if (endOfPaginationReached) null else page + 1
|
|
val posts =
|
|
items.map { item ->
|
|
NewestPosts(
|
|
shortId = item.shortId,
|
|
createdAt = item.createdAt,
|
|
title = item.title,
|
|
url = item.url,
|
|
commentCount = item.commentCount,
|
|
description = item.description,
|
|
commentsUrl = item.commentsUrl,
|
|
submitter = item.submitter,
|
|
tags = item.tags,
|
|
isRead = readPosts.contains(item.shortId),
|
|
isSaved = savedPosts.contains(item.shortId),
|
|
insertTimestamp = null,
|
|
)
|
|
}
|
|
val keys =
|
|
items.map { item ->
|
|
NewestPostsKeys(shortId = item.shortId, prevKey = prevKey, nextKey = nextKey)
|
|
}
|
|
postsDatabase.transaction { posts.forEach(postsDatabase::addCachedPost) }
|
|
postKeysDatabase.transaction { keys.forEach(postKeysDatabase::addRemoteKey) }
|
|
}
|
|
MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)
|
|
}
|
|
is NetworkFailure -> MediatorResult.Error(result.error)
|
|
is UnknownFailure -> MediatorResult.Error(result.error)
|
|
is ApiFailure,
|
|
is HttpFailure -> MediatorResult.Error(IOException("API returned an invalid response"))
|
|
}
|
|
}
|
|
|
|
private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, UIPost>): NewestPostsKeys? {
|
|
return state.pages
|
|
.firstOrNull { it.data.isNotEmpty() }
|
|
?.data
|
|
?.firstOrNull()
|
|
?.let { post ->
|
|
withContext(databaseDispatcher) {
|
|
postKeysDatabase.getRemoteKey(post.shortId).executeAsOneOrNull()
|
|
}
|
|
}
|
|
}
|
|
|
|
private suspend fun getRemoteKeyClosestToCurrentPosition(
|
|
state: PagingState<Int, UIPost>
|
|
): NewestPostsKeys? {
|
|
return state.anchorPosition?.let { position ->
|
|
state.closestItemToPosition(position)?.shortId?.let { shortId ->
|
|
withContext(databaseDispatcher) {
|
|
postKeysDatabase.getRemoteKey(shortId).executeAsOneOrNull()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, UIPost>): NewestPostsKeys? {
|
|
return state.pages
|
|
.lastOrNull { it.data.isNotEmpty() }
|
|
?.data
|
|
?.lastOrNull()
|
|
?.let { post ->
|
|
withContext(databaseDispatcher) {
|
|
postKeysDatabase.getRemoteKey(post.shortId).executeAsOneOrNull()
|
|
}
|
|
}
|
|
}
|
|
|
|
@AssistedFactory
|
|
interface Factory {
|
|
fun create(remoteFetcher: RemoteFetcher<LobstersPost>): CachingRemoteMediator
|
|
}
|
|
}
|