feat(android): add support for identifying read posts

This commit is contained in:
Harsh Shandilya 2023-08-16 00:02:24 +05:30
parent 9c32d6721b
commit bc2365b08b
No known key found for this signature in database
11 changed files with 62 additions and 10 deletions

View file

@ -218,6 +218,7 @@ fun LobstersApp(
lazyPagingItems = hottestPosts, lazyPagingItems = hottestPosts,
listState = hottestListState, listState = hottestListState,
isPostSaved = viewModel::isPostSaved, isPostSaved = viewModel::isPostSaved,
isPostRead = viewModel::isPostRead,
postActions = postActions, postActions = postActions,
) )
} }
@ -227,6 +228,7 @@ fun LobstersApp(
lazyPagingItems = newestPosts, lazyPagingItems = newestPosts,
listState = newestListState, listState = newestListState,
isPostSaved = viewModel::isPostSaved, isPostSaved = viewModel::isPostSaved,
isPostRead = viewModel::isPostRead,
postActions = postActions, postActions = postActions,
) )
} }

View file

@ -36,11 +36,13 @@ fun rememberPostActions(
): PostActions { ): PostActions {
return remember { return remember {
object : PostActions { object : PostActions {
override fun viewPost(postUrl: String, commentsUrl: String) { override fun viewPost(postId: String, postUrl: String, commentsUrl: String) {
viewModel.markPostAsRead(postId)
urlLauncher.openUri(postUrl.ifEmpty { commentsUrl }) urlLauncher.openUri(postUrl.ifEmpty { commentsUrl })
} }
override fun viewComments(postId: String) { override fun viewComments(postId: String) {
viewModel.markPostAsRead(postId)
val currentRoute = navController.currentDestination?.route val currentRoute = navController.currentDestination?.route
val newRoute = val newRoute =
Destinations.Comments.route.replace(Destinations.Comments.placeholder, postId) Destinations.Comments.route.replace(Destinations.Comments.placeholder, postId)

View file

@ -62,6 +62,7 @@ fun DatabasePosts(
ListItem( ListItem(
item = item, item = item,
isSaved = { true }, isSaved = { true },
isRead = { false },
postActions = postActions, postActions = postActions,
) )
HorizontalDivider() HorizontalDivider()

View file

@ -24,10 +24,12 @@ import me.saket.swipe.SwipeableActionsBox
fun ListItem( fun ListItem(
item: SavedPost, item: SavedPost,
isSaved: suspend (SavedPost) -> Boolean, isSaved: suspend (SavedPost) -> Boolean,
isRead: suspend (String) -> Boolean,
postActions: PostActions, postActions: PostActions,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val saved by produceState(false, item) { value = isSaved(item) } val saved by produceState(false, item) { value = isSaved(item) }
val read by produceState(false, item.shortId) { value = isRead(item.shortId) }
val commentsAction = val commentsAction =
SwipeAction( SwipeAction(
icon = rememberVectorPainter(Icons.Filled.Reply), icon = rememberVectorPainter(Icons.Filled.Reply),
@ -40,6 +42,7 @@ fun ListItem(
LobstersCard( LobstersCard(
post = item, post = item,
isSaved = saved, isSaved = saved,
isRead = read,
postActions = postActions, postActions = postActions,
modifier = modifier, modifier = modifier,
) )

View file

@ -42,6 +42,7 @@ fun NetworkPosts(
lazyPagingItems: LazyPagingItems<LobstersPost>, lazyPagingItems: LazyPagingItems<LobstersPost>,
listState: LazyListState, listState: LazyListState,
isPostSaved: suspend (SavedPost) -> Boolean, isPostSaved: suspend (SavedPost) -> Boolean,
isPostRead: suspend (String) -> Boolean,
postActions: PostActions, postActions: PostActions,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
@ -71,6 +72,7 @@ fun NetworkPosts(
ListItem( ListItem(
item = dbModel, item = dbModel,
isSaved = isPostSaved, isSaved = isPostSaved,
isRead = isPostRead,
postActions = postActions, postActions = postActions,
) )

View file

@ -75,6 +75,7 @@ fun SearchList(
lazyPagingItems = lazyPagingItems, lazyPagingItems = lazyPagingItems,
listState = listState, listState = listState,
isPostSaved = isPostSaved, isPostSaved = isPostSaved,
isPostRead = { false },
postActions = postActions, postActions = postActions,
modifier = modifier, modifier = modifier,
) )

View file

@ -48,8 +48,9 @@ class ClawViewModel
constructor( constructor(
private val api: LobstersApi, private val api: LobstersApi,
private val searchApi: LobstersSearchApi, private val searchApi: LobstersSearchApi,
private val savedPostsRepository: SavedPostsRepository,
private val commentsRepository: CommentsRepository, private val commentsRepository: CommentsRepository,
private val readPostsRepository: ReadPostsRepository,
private val savedPostsRepository: SavedPostsRepository,
private val linkMetadataRepository: LinkMetadataRepository, private val linkMetadataRepository: LinkMetadataRepository,
private val dataTransferRepository: DataTransferRepository, private val dataTransferRepository: DataTransferRepository,
private val pagingSourceFactory: LobstersPagingSource.Factory, private val pagingSourceFactory: LobstersPagingSource.Factory,
@ -147,6 +148,12 @@ constructor(
suspend fun exportPosts(output: OutputStream) = dataTransferRepository.exportPosts(output) suspend fun exportPosts(output: OutputStream) = dataTransferRepository.exportPosts(output)
fun markPostAsRead(postId: String) {
viewModelScope.launch { readPostsRepository.markRead(postId) }
}
suspend fun isPostRead(postId: String) = readPostsRepository.isRead(postId)
/** /**
* Parses a given [String] into a [LocalDateTime]. This method is only intended to be used for * Parses a given [String] into a [LocalDateTime]. This method is only intended to be used for
* dates in the format returned by the Lobsters API, and is not a general purpose parsing * dates in the format returned by the Lobsters API, and is not a general purpose parsing

View file

@ -0,0 +1,28 @@
/*
* Copyright © 2021-2023 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.viewmodel
import dev.msfjarvis.claw.core.injection.DatabaseDispatcher
import dev.msfjarvis.claw.database.local.ReadPostsQueries
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
class ReadPostsRepository
@Inject
constructor(
private val readPostsQueries: ReadPostsQueries,
@DatabaseDispatcher private val dbDispatcher: CoroutineDispatcher,
) {
suspend fun markRead(postId: String) {
withContext(dbDispatcher) { readPostsQueries.markRead(postId) }
}
suspend fun isRead(postId: String): Boolean =
withContext(dbDispatcher) { readPostsQueries.isRead(postId).executeAsOneOrNull() != null }
}

View file

@ -70,7 +70,7 @@ internal fun CommentsHeader(
modifier = Modifier.padding(16.dp).fillMaxWidth(), modifier = Modifier.padding(16.dp).fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp),
) { ) {
PostTitle(title = postDetails.title) PostTitle(title = postDetails.title, isRead = false)
TagRow(tags = postDetails.tags.toImmutableList()) TagRow(tags = postDetails.tags.toImmutableList())
Spacer(Modifier.height(4.dp)) Spacer(Modifier.height(4.dp))
@ -78,7 +78,9 @@ internal fun CommentsHeader(
PostLink( PostLink(
linkMetadata = linkMetadata, linkMetadata = linkMetadata,
modifier = modifier =
Modifier.clickable { postActions.viewPost(linkMetadata.url, postDetails.commentsUrl) }, Modifier.clickable {
postActions.viewPost(postDetails.shortId, linkMetadata.url, postDetails.commentsUrl)
},
) )
Spacer(Modifier.height(4.dp)) Spacer(Modifier.height(4.dp))
} }

View file

@ -62,6 +62,7 @@ import kotlinx.collections.immutable.toImmutableList
fun LobstersCard( fun LobstersCard(
post: SavedPost, post: SavedPost,
isSaved: Boolean, isSaved: Boolean,
isRead: Boolean,
postActions: PostActions, postActions: PostActions,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
@ -70,7 +71,7 @@ fun LobstersCard(
modifier = modifier =
modifier modifier
.fillMaxWidth() .fillMaxWidth()
.clickable { postActions.viewPost(post.url, post.commentsUrl) } .clickable { postActions.viewPost(post.shortId, post.url, post.commentsUrl) }
.background(MaterialTheme.colorScheme.background) .background(MaterialTheme.colorScheme.background)
.padding(start = 16.dp, top = 16.dp, end = 4.dp, bottom = 16.dp), .padding(start = 16.dp, top = 16.dp, end = 4.dp, bottom = 16.dp),
) { ) {
@ -81,6 +82,7 @@ fun LobstersCard(
PostDetails( PostDetails(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
post = post, post = post,
isRead = isRead,
) )
Column( Column(
modifier = Modifier.wrapContentHeight(), modifier = Modifier.wrapContentHeight(),
@ -110,9 +112,9 @@ fun LobstersCard(
} }
@Composable @Composable
fun PostDetails(post: SavedPost, modifier: Modifier = Modifier) { fun PostDetails(post: SavedPost, isRead: Boolean, modifier: Modifier = Modifier) {
Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(8.dp)) { Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(8.dp)) {
PostTitle(title = post.title) PostTitle(title = post.title, isRead = isRead)
TagRow(tags = post.tags.toImmutableList()) TagRow(tags = post.tags.toImmutableList())
Spacer(Modifier.height(4.dp)) Spacer(Modifier.height(4.dp))
Submitter( Submitter(
@ -126,13 +128,14 @@ fun PostDetails(post: SavedPost, modifier: Modifier = Modifier) {
@Composable @Composable
internal fun PostTitle( internal fun PostTitle(
title: String, title: String,
isRead: Boolean,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
Text( Text(
text = title, text = title,
modifier = modifier, modifier = modifier,
style = MaterialTheme.typography.titleMedium, style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold, fontWeight = if (isRead) FontWeight.Normal else FontWeight.Bold,
color = MaterialTheme.colorScheme.onBackground, color = MaterialTheme.colorScheme.onBackground,
) )
} }
@ -261,10 +264,11 @@ fun LobstersCardPreview() {
tags = listOf("databases", "apis"), tags = listOf("databases", "apis"),
description = "", description = "",
), ),
isRead = true,
isSaved = true, isSaved = true,
postActions = postActions =
object : PostActions { object : PostActions {
override fun viewPost(postUrl: String, commentsUrl: String) {} override fun viewPost(postId: String, postUrl: String, commentsUrl: String) {}
override fun viewComments(postId: String) {} override fun viewComments(postId: String) {}

View file

@ -13,7 +13,7 @@ import dev.msfjarvis.claw.model.LobstersPostDetails
@Stable @Stable
interface PostActions { interface PostActions {
fun viewPost(postUrl: String, commentsUrl: String) fun viewPost(postId: String, postUrl: String, commentsUrl: String)
fun viewComments(postId: String) fun viewComments(postId: String)