From eeedee5555dbe4cc1c2c3251ae2e9c69f91d419b Mon Sep 17 00:00:00 2001 From: Torsten Grote <t@grobox.de> Date: Tue, 11 Jul 2023 16:43:04 -0300 Subject: [PATCH] Mark blog posts read when visible for some time --- .../briar/desktop/blog/BlogPostItem.kt | 4 ++- .../briar/desktop/blog/BlogScreen.kt | 7 +++- .../briar/desktop/blog/FeedScreen.kt | 11 ++++-- .../briar/desktop/blog/FeedViewModel.kt | 35 ++++++++++++++++++- 4 files changed, 51 insertions(+), 6 deletions(-) diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/BlogPostItem.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/BlogPostItem.kt index 2c4fb589b7..3596fd7a38 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/BlogPostItem.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/BlogPostItem.kt @@ -30,7 +30,7 @@ sealed class BlogPost( open val text: String?, ) : Comparable<BlogPost> { abstract val postHeader: BlogPostHeader - val isRead: Boolean get() = header.isRead + abstract val isRead: Boolean val id: MessageId get() = header.id val groupId: GroupId get() = header.groupId val timestamp: Long get() = header.timestamp @@ -46,6 +46,7 @@ sealed class BlogPost( data class BlogPostItem( override val header: BlogPostHeader, override val text: String, + override val isRead: Boolean = header.isRead, ) : BlogPost(header, text) { override val postHeader: BlogPostHeader get() = header } @@ -54,6 +55,7 @@ data class BlogCommentItem( override val header: BlogCommentHeader, override val postHeader: BlogPostHeader, override val text: String?, + override val isRead: Boolean = header.isRead, ) : BlogPost(header, text) { companion object { diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/BlogScreen.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/BlogScreen.kt index 657b084b14..f50ec84823 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/BlogScreen.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/BlogScreen.kt @@ -51,7 +51,12 @@ fun BlogScreen(viewModel: FeedViewModel = viewModel()) { Text(i18n("blog.empty.state"), Modifier.padding(16.dp)) } } else { - FeedScreen(viewModel.posts, viewModel::selectPost, Modifier.padding(padding)) + FeedScreen( + posts = viewModel.posts, + onItemSelected = viewModel::selectPost, + onBlogPostsVisible = viewModel::onPostsVisible, + modifier = Modifier.padding(padding) + ) } } }, diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/FeedScreen.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/FeedScreen.kt index 629ead9061..832dce0980 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/FeedScreen.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/FeedScreen.kt @@ -54,11 +54,16 @@ fun main() = preview( add(getRandomBlogPost(getStringParameter("text"), getLongParameter("timestamp"))) } } - FeedScreen(items, {}) + FeedScreen(items, {}, {}) } @Composable -fun FeedScreen(posts: List<BlogPost>, onItemSelected: (BlogPost) -> Unit, modifier: Modifier = Modifier) { +fun FeedScreen( + posts: List<BlogPost>, + onItemSelected: (BlogPost) -> Unit, + onBlogPostsVisible: (List<MessageId>) -> Unit, + modifier: Modifier = Modifier, +) { val scrollState = rememberLazyListState() // scroll to first unread item if needed val lastUnreadIndex = posts.indexOfLast { item -> !item.isRead } @@ -101,7 +106,7 @@ fun FeedScreen(posts: List<BlogPost>, onItemSelected: (BlogPost) -> Unit, modifi val visibleMessageIds = scrollState.layoutInfo.reallyVisibleItemsInfo.map { it.key as MessageId } - // TODO onBlogPostsVisible(visibleMessageIds) + onBlogPostsVisible(visibleMessageIds) } } } diff --git a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/FeedViewModel.kt b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/FeedViewModel.kt index 99ff47bd5d..fb5978fda5 100644 --- a/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/FeedViewModel.kt +++ b/briar-desktop/src/main/kotlin/org/briarproject/briar/desktop/blog/FeedViewModel.kt @@ -38,6 +38,7 @@ import org.briarproject.briar.api.blog.BlogPostHeader import org.briarproject.briar.api.blog.event.BlogPostAddedEvent import org.briarproject.briar.desktop.threading.BriarExecutors import org.briarproject.briar.desktop.threading.UiExecutor +import org.briarproject.briar.desktop.utils.replaceIf import org.briarproject.briar.desktop.viewmodel.EventListenerDbViewModel import org.briarproject.briar.desktop.viewmodel.asList import org.briarproject.briar.desktop.viewmodel.asState @@ -51,7 +52,7 @@ class FeedViewModel @Inject constructor( briarExecutors: BriarExecutors, lifecycleManager: LifecycleManager, db: TransactionManager, - eventBus: EventBus, + private val eventBus: EventBus, ) : EventListenerDbViewModel(briarExecutors, lifecycleManager, db, eventBus) { companion object { @@ -169,4 +170,36 @@ class FeedViewModel @Inject constructor( private fun getPostText(txn: Transaction, m: MessageId): String { return HtmlUtils.cleanArticle(blogManager.getPostText(txn, m)) } + + @UiExecutor + fun onPostsVisible(messageIds: List<MessageId>) = markBlogPostRead { item -> + messageIds.contains(item.id) + } + + /** + * Marks the [BlogPost]s as read for those the given [predicate] returns true. + */ + @UiExecutor + private fun markBlogPostRead(predicate: (BlogPost) -> Boolean) { + val readIds = posts.filter { item -> + predicate(item) && !item.isRead + }.map { item -> + item.id + } + if (readIds.isNotEmpty()) { + runOnDbThread { + readIds.forEach { id -> + blogManager.setReadFlag(id, false) + } + // TODO introduce Transaction for BlogManager#setReadFlag() and attach event there +// eventBus.broadcast() + } + _posts.replaceIf({ it.id in readIds }) { + when (it) { + is BlogPostItem -> it.copy(isRead = true) + is BlogCommentItem -> it.copy(isRead = true) + } + } + } + } } -- GitLab