refactor: lazily query saved and read state in UI

Having this always be read from the UI avoids values going stale inside data models

Fixes #641
This commit is contained in:
Harsh Shandilya 2024-08-28 13:11:42 +05:30
parent 6f424ae2d5
commit 8651a4f66b
12 changed files with 56 additions and 100 deletions

View file

@ -14,16 +14,12 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dev.msfjarvis.claw.android.ui.toError
import dev.msfjarvis.claw.android.viewmodel.ReadPostsRepository
import dev.msfjarvis.claw.android.viewmodel.SavedPostsRepository
import dev.msfjarvis.claw.core.injection.IODispatcher
import dev.msfjarvis.claw.database.local.SavedPost
import dev.msfjarvis.claw.model.LobstersPost
import dev.msfjarvis.claw.model.UIPost
import dev.msfjarvis.claw.model.toUIPost
import java.io.IOException
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext
class LobstersPagingSource
@ -31,33 +27,14 @@ class LobstersPagingSource
constructor(
@Assisted private val remoteFetcher: RemoteFetcher<LobstersPost>,
@IODispatcher private val ioDispatcher: CoroutineDispatcher,
private val savedPostsRepository: SavedPostsRepository,
private val readPostsRepository: ReadPostsRepository,
) : PagingSource<Int, UIPost>() {
private lateinit var savedPosts: List<String>
private lateinit var readPosts: List<String>
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, UIPost> {
if (!::savedPosts.isInitialized) {
savedPosts = savedPostsRepository.savedPosts.first().map(SavedPost::shortId)
}
if (!::readPosts.isInitialized) {
readPosts = readPostsRepository.readPosts.first()
}
val page = params.key ?: STARTING_PAGE_INDEX
return when (val result = withContext(ioDispatcher) { remoteFetcher.getItemsAtPage(page) }) {
is Success ->
LoadResult.Page(
itemsBefore = (page - 1) * PAGE_SIZE,
data =
result.value.map {
it
.toUIPost()
.copy(
isSaved = savedPosts.contains(it.shortId),
isRead = readPosts.contains(it.shortId),
)
},
data = result.value.map(LobstersPost::toUIPost),
prevKey = if (page == STARTING_PAGE_INDEX) null else page - 1,
nextKey = page + 1,
)

View file

@ -15,16 +15,13 @@ import dagger.assisted.AssistedInject
import dev.msfjarvis.claw.android.paging.LobstersPagingSource.Companion.PAGE_SIZE
import dev.msfjarvis.claw.android.paging.LobstersPagingSource.Companion.STARTING_PAGE_INDEX
import dev.msfjarvis.claw.android.ui.toError
import dev.msfjarvis.claw.android.viewmodel.ReadPostsRepository
import dev.msfjarvis.claw.android.viewmodel.SavedPostsRepository
import dev.msfjarvis.claw.api.LobstersSearchApi
import dev.msfjarvis.claw.core.injection.IODispatcher
import dev.msfjarvis.claw.database.local.SavedPost
import dev.msfjarvis.claw.model.LobstersPost
import dev.msfjarvis.claw.model.UIPost
import dev.msfjarvis.claw.model.toUIPost
import java.io.IOException
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext
/**
@ -39,19 +36,8 @@ constructor(
private val searchApi: LobstersSearchApi,
@Assisted private val queryProvider: () -> String,
@IODispatcher private val ioDispatcher: CoroutineDispatcher,
private val savedPostsRepository: SavedPostsRepository,
private val readPostsRepository: ReadPostsRepository,
) : PagingSource<Int, UIPost>() {
private lateinit var savedPosts: List<String>
private lateinit var readPosts: List<String>
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, UIPost> {
if (!::savedPosts.isInitialized) {
savedPosts = savedPostsRepository.savedPosts.first().map(SavedPost::shortId)
}
if (!::readPosts.isInitialized) {
readPosts = readPostsRepository.readPosts.first()
}
val searchQuery = queryProvider()
// If there is no query, we don't need to call the API at all.
if (searchQuery.isEmpty()) {
@ -66,15 +52,7 @@ constructor(
val nextKey = if (result.value.isEmpty()) null else page + 1
LoadResult.Page(
itemsBefore = (page - 1) * PAGE_SIZE,
data =
result.value.map {
it
.toUIPost()
.copy(
isSaved = savedPosts.contains(it.shortId),
isRead = readPosts.contains(it.shortId),
)
},
data = result.value.map(LobstersPost::toUIPost),
prevKey = if (page == STARTING_PAGE_INDEX) null else page - 1,
nextKey = nextKey,
)

View file

@ -70,6 +70,10 @@ fun rememberPostActions(
context.startActivity(shareIntent)
}
override fun isPostRead(post: UIPost): Boolean = viewModel.isPostRead(post)
override fun isPostSaved(post: UIPost): Boolean = viewModel.isPostSaved(post)
override suspend fun getComments(postId: String): UIPost {
return viewModel.getPostComments(postId)
}

View file

@ -54,7 +54,7 @@ fun DatabasePosts(
items.forEach { (month, posts) ->
stickyHeader(contentType = "month-header") { MonthHeader(label = month) }
items(items = posts, key = { it.shortId }, contentType = { "LobstersItem" }) { item ->
LobstersListItem(item = item, refresh = {}, postActions = postActions)
LobstersListItem(item = item, postActions = postActions)
HorizontalDivider()
}
}

View file

@ -20,12 +20,7 @@ import me.saket.swipe.SwipeAction
import me.saket.swipe.SwipeableActionsBox
@Composable
fun LobstersListItem(
item: UIPost,
postActions: PostActions,
refresh: () -> Unit,
modifier: Modifier = Modifier,
) {
fun LobstersListItem(item: UIPost, postActions: PostActions, modifier: Modifier = Modifier) {
val commentsAction =
SwipeAction(
icon = rememberVectorPainter(Icons.AutoMirrored.Filled.Reply),
@ -39,6 +34,6 @@ fun LobstersListItem(
onSwipe = { postActions.share(item) },
)
SwipeableActionsBox(startActions = listOf(shareAction), endActions = listOf(commentsAction)) {
LobstersCard(post = item, postActions = postActions, refresh = refresh, modifier = modifier)
LobstersCard(post = item, postActions = postActions, modifier = modifier)
}
}

View file

@ -73,11 +73,7 @@ fun NetworkPosts(
) { index ->
val item = lazyPagingItems[index]
if (item != null) {
LobstersListItem(
item = item,
postActions = postActions,
refresh = { lazyPagingItems.refresh() },
)
LobstersListItem(item = item, postActions = postActions)
HorizontalDivider()
}
}

View file

@ -133,6 +133,14 @@ constructor(
}
}
fun isPostRead(post: UIPost): Boolean {
return _readPosts.contains(post.shortId)
}
fun isPostSaved(post: UIPost): Boolean {
return _savedPosts.contains(post.shortId)
}
suspend fun getPostComments(postId: String) =
withContext(ioDispatcher) {
when (val result = api.getPostDetails(postId)) {

View file

@ -16,6 +16,7 @@ import dev.msfjarvis.claw.model.toSavedPost
import io.github.aakira.napier.Napier
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.withContext
class SavedPostsRepository
@ -27,7 +28,7 @@ constructor(
val savedPosts = savedPostQueries.selectAllPosts().asFlow().mapToList(dbDispatcher)
suspend fun toggleSave(post: UIPost) {
if (post.isSaved) {
if (savedPosts.firstOrNull().orEmpty().any { it.shortId == post.shortId }) {
Napier.d(tag = TAG) { "Removing post: ${post.shortId}" }
withContext(dbDispatcher) { savedPostQueries.deletePost(post.shortId) }
} else {