diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/glance/SavedPostsWidget.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/glance/SavedPostsWidget.kt index 173df685..88ec9474 100644 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/glance/SavedPostsWidget.kt +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/glance/SavedPostsWidget.kt @@ -28,11 +28,11 @@ import androidx.glance.text.Text import androidx.glance.text.TextStyle import dev.msfjarvis.claw.common.theme.DarkThemeColors import dev.msfjarvis.claw.common.theme.LightThemeColors -import dev.msfjarvis.claw.database.local.SavedPost +import dev.msfjarvis.claw.model.UIPost import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList -class SavedPostsWidget(private val posts: List) : GlanceAppWidget() { +class SavedPostsWidget(private val posts: List) : GlanceAppWidget() { override suspend fun provideGlance(context: Context, id: GlanceId) { provideContent { GlanceTheme( @@ -46,7 +46,7 @@ class SavedPostsWidget(private val posts: List) : GlanceAppWidget() { } @Composable -fun WidgetHost(posts: ImmutableList, modifier: GlanceModifier = GlanceModifier) { +fun WidgetHost(posts: ImmutableList, modifier: GlanceModifier = GlanceModifier) { LazyColumn( modifier = modifier.fillMaxSize().background(GlanceTheme.colors.background).appWidgetBackground(), diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/glance/WidgetListEntry.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/glance/WidgetListEntry.kt index 25c89073..1b08da69 100644 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/glance/WidgetListEntry.kt +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/glance/WidgetListEntry.kt @@ -33,13 +33,13 @@ import androidx.glance.text.TextStyle import dev.msfjarvis.claw.android.MainActivity import dev.msfjarvis.claw.android.MainActivity.Companion.NAVIGATION_KEY import dev.msfjarvis.claw.android.R -import dev.msfjarvis.claw.database.local.SavedPost +import dev.msfjarvis.claw.model.UIPost private val destinationKey = Key(NAVIGATION_KEY) @Composable @GlanceComposable -fun WidgetListEntry(post: SavedPost, modifier: GlanceModifier = GlanceModifier) { +fun WidgetListEntry(post: UIPost, modifier: GlanceModifier = GlanceModifier) { val titleStyle = MaterialTheme.typography.titleMedium val commentsAction = actionStartActivity(actionParametersOf(destinationKey to post.shortId)) @@ -68,7 +68,7 @@ fun WidgetListEntry(post: SavedPost, modifier: GlanceModifier = GlanceModifier) ) Image( provider = ImageProvider(R.drawable.ic_comment), - contentDescription = "${post.commentCount ?: 0} comments", + contentDescription = "${post.commentCount} comments", modifier = GlanceModifier.padding(end = 4.dp).clickable(commentsAction), ) } diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/ext.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/ext.kt index 3764ef21..67314b41 100644 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/ext.kt +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/ext.kt @@ -1,5 +1,5 @@ /* - * Copyright © 2022-2023 Harsh Shandilya. + * Copyright © 2022-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. @@ -16,9 +16,8 @@ import dev.msfjarvis.claw.android.ui.navigation.Destinations import dev.msfjarvis.claw.android.viewmodel.ClawViewModel import dev.msfjarvis.claw.common.posts.PostActions import dev.msfjarvis.claw.common.urllauncher.UrlLauncher -import dev.msfjarvis.claw.database.local.SavedPost import dev.msfjarvis.claw.model.LinkMetadata -import dev.msfjarvis.claw.model.LobstersPostDetails +import dev.msfjarvis.claw.model.UIPost fun Context.getActivity(): ComponentActivity? { return when (this) { @@ -59,11 +58,11 @@ fun rememberPostActions( urlLauncher.openUri(commentsUrl.replaceAfterLast('/', "r")) } - override fun toggleSave(post: SavedPost) { + override fun toggleSave(post: UIPost) { viewModel.toggleSave(post) } - override suspend fun getComments(postId: String): LobstersPostDetails { + override suspend fun getComments(postId: String): UIPost { return viewModel.getPostComments(postId) } diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/DatabasePosts.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/DatabasePosts.kt index d2522cca..48e920fa 100644 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/DatabasePosts.kt +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/DatabasePosts.kt @@ -27,13 +27,13 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import dev.msfjarvis.claw.common.posts.PostActions import dev.msfjarvis.claw.common.ui.decorations.MonthHeader -import dev.msfjarvis.claw.database.local.SavedPost +import dev.msfjarvis.claw.model.UIPost import kotlinx.collections.immutable.ImmutableMap @OptIn(ExperimentalFoundationApi::class) @Composable fun DatabasePosts( - items: ImmutableMap>, + items: ImmutableMap>, listState: LazyListState, postActions: PostActions, modifier: Modifier = Modifier, @@ -54,12 +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, - isSaved = { true }, - isRead = { false }, - postActions = postActions, - ) + LobstersListItem(item = item, postActions = postActions) HorizontalDivider() } } diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/LobstersListItem.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/LobstersListItem.kt index 054f8901..9a66136b 100644 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/LobstersListItem.kt +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/LobstersListItem.kt @@ -11,24 +11,16 @@ import androidx.compose.material.icons.automirrored.filled.Reply import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.produceState import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.rememberVectorPainter import dev.msfjarvis.claw.common.posts.LobstersCard import dev.msfjarvis.claw.common.posts.PostActions -import dev.msfjarvis.claw.database.local.SavedPost +import dev.msfjarvis.claw.model.UIPost import me.saket.swipe.SwipeAction import me.saket.swipe.SwipeableActionsBox @Composable -fun LobstersListItem( - item: SavedPost, - isSaved: (SavedPost) -> Boolean, - isRead: suspend (String) -> Boolean, - postActions: PostActions, - modifier: Modifier = Modifier, -) { - val read by produceState(false, item.shortId) { value = isRead(item.shortId) } +fun LobstersListItem(item: UIPost, postActions: PostActions, modifier: Modifier = Modifier) { val commentsAction = SwipeAction( icon = rememberVectorPainter(Icons.AutoMirrored.Filled.Reply), @@ -36,12 +28,6 @@ fun LobstersListItem( onSwipe = { postActions.viewCommentsPage(item.commentsUrl) }, ) SwipeableActionsBox(endActions = listOf(commentsAction)) { - LobstersCard( - post = item, - isSaved = isSaved(item), - isRead = read, - postActions = postActions, - modifier = modifier, - ) + LobstersCard(post = item, postActions = postActions, modifier = modifier) } } diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/NetworkPosts.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/NetworkPosts.kt index 9b0e42f1..cfd3f137 100644 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/NetworkPosts.kt +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/NetworkPosts.kt @@ -27,9 +27,7 @@ import androidx.paging.compose.itemKey import dev.msfjarvis.claw.common.posts.PostActions import dev.msfjarvis.claw.common.ui.NetworkError import dev.msfjarvis.claw.common.ui.ProgressBar -import dev.msfjarvis.claw.database.local.SavedPost -import dev.msfjarvis.claw.model.LobstersPost -import dev.msfjarvis.claw.model.toSavedPost +import dev.msfjarvis.claw.model.UIPost import eu.bambooapps.material3.pullrefresh.PullRefreshIndicator import eu.bambooapps.material3.pullrefresh.pullRefresh import eu.bambooapps.material3.pullrefresh.rememberPullRefreshState @@ -37,10 +35,8 @@ import eu.bambooapps.material3.pullrefresh.rememberPullRefreshState @OptIn(ExperimentalMaterial3Api::class) @Composable fun NetworkPosts( - lazyPagingItems: LazyPagingItems, + lazyPagingItems: LazyPagingItems, listState: LazyListState, - isPostSaved: (SavedPost) -> Boolean, - isPostRead: suspend (String) -> Boolean, postActions: PostActions, modifier: Modifier = Modifier, ) { @@ -64,13 +60,7 @@ fun NetworkPosts( ) { index -> val item = lazyPagingItems[index] if (item != null) { - val dbModel = item.toSavedPost() - LobstersListItem( - item = dbModel, - isSaved = isPostSaved, - isRead = isPostRead, - postActions = postActions, - ) + LobstersListItem(item = item, postActions = postActions) HorizontalDivider() } diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/SearchList.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/SearchList.kt index ff6ff659..ef06f0aa 100644 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/SearchList.kt +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/SearchList.kt @@ -1,5 +1,5 @@ /* - * Copyright © 2023 Harsh Shandilya. + * Copyright © 2023-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. @@ -21,15 +21,13 @@ import androidx.paging.PagingData import androidx.paging.compose.collectAsLazyPagingItems import dev.msfjarvis.claw.common.posts.PostActions import dev.msfjarvis.claw.common.ui.SearchBar -import dev.msfjarvis.claw.database.local.SavedPost -import dev.msfjarvis.claw.model.LobstersPost +import dev.msfjarvis.claw.model.UIPost import kotlinx.coroutines.flow.Flow @Composable fun SearchList( - items: Flow>, + items: Flow>, listState: LazyListState, - isPostSaved: (SavedPost) -> Boolean, postActions: PostActions, searchQuery: String, setSearchQuery: (String) -> Unit, @@ -50,8 +48,6 @@ fun SearchList( NetworkPosts( lazyPagingItems = lazyPagingItems, listState = listState, - isPostSaved = isPostSaved, - isPostRead = { false }, postActions = postActions, ) } diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/LobstersPostsScreen.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/LobstersPostsScreen.kt index fb08f2a0..a572268f 100644 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/LobstersPostsScreen.kt +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/LobstersPostsScreen.kt @@ -228,8 +228,6 @@ fun LobstersPostsScreen( NetworkPosts( lazyPagingItems = hottestPosts, listState = hottestListState, - isPostSaved = viewModel::isPostSaved, - isPostRead = viewModel::isPostRead, postActions = postActions, ) } @@ -238,8 +236,6 @@ fun LobstersPostsScreen( NetworkPosts( lazyPagingItems = newestPosts, listState = newestListState, - isPostSaved = viewModel::isPostSaved, - isPostRead = viewModel::isPostRead, postActions = postActions, ) } diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/SearchScreen.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/SearchScreen.kt index 46f52f4e..6936186f 100644 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/SearchScreen.kt +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/SearchScreen.kt @@ -1,5 +1,5 @@ /* - * Copyright © 2023 Harsh Shandilya. + * Copyright © 2023-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. @@ -47,7 +47,6 @@ fun SearchScreen( SearchList( items = viewModel.searchResults, listState = listState, - isPostSaved = viewModel::isPostSaved, postActions = postActions, searchQuery = viewModel.searchQuery, setSearchQuery = { query -> viewModel.searchQuery = query }, diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/viewmodel/ClawViewModel.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/viewmodel/ClawViewModel.kt index f56c33af..e96b5608 100644 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/viewmodel/ClawViewModel.kt +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/viewmodel/ClawViewModel.kt @@ -18,6 +18,9 @@ import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope import androidx.paging.Pager import androidx.paging.PagingConfig +import androidx.paging.PagingData +import androidx.paging.cachedIn +import androidx.paging.map import com.deliveryhero.whetstone.app.ApplicationScope import com.deliveryhero.whetstone.viewmodel.ContributesViewModel import com.slack.eithernet.ApiResult.Failure @@ -31,8 +34,12 @@ import dev.msfjarvis.claw.android.paging.SearchPagingSource import dev.msfjarvis.claw.api.LobstersApi import dev.msfjarvis.claw.core.injection.IODispatcher import dev.msfjarvis.claw.core.injection.MainDispatcher -import dev.msfjarvis.claw.database.local.SavedPost import dev.msfjarvis.claw.model.Comment +import dev.msfjarvis.claw.model.LobstersPost +import dev.msfjarvis.claw.model.UIPost +import dev.msfjarvis.claw.model.fromSavedPost +import dev.msfjarvis.claw.model.toSavedPost +import dev.msfjarvis.claw.model.toUIPost import java.io.IOException import java.io.InputStream import java.io.OutputStream @@ -44,9 +51,11 @@ import javax.inject.Inject import kotlinx.collections.immutable.ImmutableMap import kotlinx.collections.immutable.toImmutableMap import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -68,34 +77,43 @@ constructor( @MainDispatcher private val mainDispatcher: CoroutineDispatcher, @ForScope(ApplicationScope::class) context: Context, ) : AndroidViewModel(context as Application) { - private val hottestPostsPager = - Pager(PagingConfig(pageSize = PAGE_SIZE), initialKey = STARTING_PAGE_INDEX) { - pagingSourceFactory.create(api::getHottestPosts) - } - private val newestPostsPager = - Pager(PagingConfig(pageSize = PAGE_SIZE), initialKey = STARTING_PAGE_INDEX) { - pagingSourceFactory.create(api::getNewestPosts) - } - private val searchResultsPager = - Pager(PagingConfig(pageSize = PAGE_SIZE), initialKey = STARTING_PAGE_INDEX) { - searchPagingSourceFactory.create { searchQuery } - } + val hottestPosts = + Pager( + config = PagingConfig(pageSize = PAGE_SIZE), + initialKey = STARTING_PAGE_INDEX, + pagingSourceFactory = { pagingSourceFactory.create(api::getHottestPosts) }, + ) + .flow + .map(::mapUIPost) + .cachedIn(viewModelScope) - val hottestPosts - get() = hottestPostsPager.flow - - val newestPosts - get() = newestPostsPager.flow + val newestPosts = + Pager( + config = PagingConfig(pageSize = PAGE_SIZE), + initialKey = STARTING_PAGE_INDEX, + pagingSourceFactory = { pagingSourceFactory.create(api::getNewestPosts) }, + ) + .flow + .map(::mapUIPost) + .cachedIn(viewModelScope) + val searchResults = + Pager( + PagingConfig(pageSize = PAGE_SIZE), + initialKey = STARTING_PAGE_INDEX, + pagingSourceFactory = { searchPagingSourceFactory.create { searchQuery } }, + ) + .flow + .map(::mapUIPost) val savedPosts - get() = savedPostsRepository.savedPosts + get() = + savedPostsRepository.savedPosts + .map { it.map(UIPost.Companion::fromSavedPost) } + .shareIn(viewModelScope, started = SharingStarted.Lazily, Int.MAX_VALUE) val savedPostsByMonth get() = savedPosts.map(::mapSavedPosts) - val searchResults - get() = searchResultsPager.flow - var searchQuery by mutableStateOf("") private val _savedPostsMutex = Mutex() @@ -104,12 +122,19 @@ constructor( init { viewModelScope.launch { savedPosts.collectLatest { - _savedPostsMutex.withLock { _savedPosts = it.map(SavedPost::shortId) } + _savedPostsMutex.withLock { _savedPosts = it.map(UIPost::shortId) } } } } - private fun mapSavedPosts(items: List): ImmutableMap> { + private fun mapUIPost(pagingData: PagingData): PagingData { + return pagingData.map { post -> + val uiPost = post.toUIPost() + uiPost.copy(isSaved = isPostSaved(uiPost), isRead = isPostRead(uiPost)) + } + } + + private fun mapSavedPosts(items: List): ImmutableMap> { val sorted = items.sortedWith { post1, post2 -> val post1Date = post1.createdAt.toLocalDateTime() @@ -130,17 +155,19 @@ constructor( .toImmutableMap() } - fun isPostSaved(post: SavedPost): Boolean { + private fun isPostSaved(post: UIPost): Boolean { return _savedPosts.contains(post.shortId) } - fun toggleSave(post: SavedPost) { + private fun isPostRead(post: UIPost) = readPostsRepository.isRead(post.shortId) + + fun toggleSave(post: UIPost) { viewModelScope.launch(ioDispatcher) { val saved = isPostSaved(post) if (saved) { - savedPostsRepository.removePost(post) + savedPostsRepository.removePost(post.toSavedPost()) } else { - savedPostsRepository.savePost(post) + savedPostsRepository.savePost(post.toSavedPost()) } val newPosts = savedPosts.first() withContext(mainDispatcher) { @@ -152,7 +179,7 @@ constructor( suspend fun getPostComments(postId: String) = withContext(ioDispatcher) { when (val result = api.getPostDetails(postId)) { - is Success -> result.value + is Success -> result.value.toUIPost() is Failure.NetworkFailure -> throw result.error is Failure.UnknownFailure -> throw result.error is Failure.HttpFailure -> { @@ -198,8 +225,6 @@ constructor( 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 * dates in the format returned by the Lobsters API, and is not a general purpose parsing diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/viewmodel/ReadPostsRepository.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/viewmodel/ReadPostsRepository.kt index 6735dc0c..b1b65ece 100644 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/viewmodel/ReadPostsRepository.kt +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/viewmodel/ReadPostsRepository.kt @@ -1,5 +1,5 @@ /* - * Copyright © 2021-2023 Harsh Shandilya. + * Copyright © 2021-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. @@ -23,6 +23,7 @@ constructor( withContext(dbDispatcher) { readPostsQueries.markRead(postId) } } - suspend fun isRead(postId: String): Boolean = - withContext(dbDispatcher) { readPostsQueries.isRead(postId).executeAsOneOrNull() != null } + fun isRead(postId: String): Boolean { + return readPostsQueries.isRead(postId).executeAsOneOrNull() != null + } } diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/work/SavedPostUpdaterWorker.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/work/SavedPostUpdaterWorker.kt index 04e98139..eeb73858 100644 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/work/SavedPostUpdaterWorker.kt +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/work/SavedPostUpdaterWorker.kt @@ -1,5 +1,5 @@ /* - * Copyright © 2022-2023 Harsh Shandilya. + * Copyright © 2022-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. @@ -18,6 +18,8 @@ import dev.msfjarvis.claw.android.glance.SavedPostsWidget import dev.msfjarvis.claw.android.viewmodel.SavedPostsRepository import dev.msfjarvis.claw.api.LobstersApi import dev.msfjarvis.claw.model.LobstersPostDetails +import dev.msfjarvis.claw.model.UIPost +import dev.msfjarvis.claw.model.fromSavedPost import dev.msfjarvis.claw.model.toSavedPost import javax.inject.Inject import kotlinx.coroutines.flow.first @@ -44,7 +46,10 @@ constructor( .filterIsInstance>() .map { result -> result.value.toSavedPost() } .let { savedPostsRepository.savePosts(it) } - SavedPostsWidget(savedPostsRepository.savedPosts.first().take(50)).updateAll(applicationContext) + SavedPostsWidget( + savedPostsRepository.savedPosts.first().take(50).map(UIPost.Companion::fromSavedPost) + ) + .updateAll(applicationContext) return Result.success() } } diff --git a/common/src/main/kotlin/dev/msfjarvis/claw/common/comments/CommentEntry.kt b/common/src/main/kotlin/dev/msfjarvis/claw/common/comments/CommentEntry.kt index 8fbdcd8b..32b65f27 100644 --- a/common/src/main/kotlin/dev/msfjarvis/claw/common/comments/CommentEntry.kt +++ b/common/src/main/kotlin/dev/msfjarvis/claw/common/comments/CommentEntry.kt @@ -46,22 +46,22 @@ import dev.msfjarvis.claw.common.posts.TagRow import dev.msfjarvis.claw.common.ui.NetworkImage import dev.msfjarvis.claw.common.ui.ThemedRichText import dev.msfjarvis.claw.model.LinkMetadata -import dev.msfjarvis.claw.model.LobstersPostDetails +import dev.msfjarvis.claw.model.UIPost import java.time.Instant import java.time.temporal.TemporalAccessor import kotlinx.collections.immutable.toImmutableList @Composable internal fun CommentsHeader( - postDetails: LobstersPostDetails, + post: UIPost, postActions: PostActions, htmlConverter: HTMLConverter, modifier: Modifier = Modifier, ) { val uriHandler = LocalUriHandler.current val linkMetadata by - produceState(initialValue = LinkMetadata(postDetails.url, null)) { - runSuspendCatching { postActions.getLinkMetadata(postDetails.url) } + produceState(initialValue = LinkMetadata(post.url, null)) { + runSuspendCatching { postActions.getLinkMetadata(post.url) } .onSuccess { metadata -> value = metadata } } @@ -70,8 +70,8 @@ internal fun CommentsHeader( modifier = Modifier.padding(16.dp).fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp), ) { - PostTitle(title = postDetails.title, isRead = false) - TagRow(tags = postDetails.tags.toImmutableList()) + PostTitle(title = post.title, isRead = false) + TagRow(tags = post.tags.toImmutableList()) Spacer(Modifier.height(4.dp)) if (linkMetadata.url.isNotBlank()) { @@ -79,23 +79,23 @@ internal fun CommentsHeader( linkMetadata = linkMetadata, modifier = Modifier.clickable { - postActions.viewPost(postDetails.shortId, linkMetadata.url, postDetails.commentsUrl) + postActions.viewPost(post.shortId, linkMetadata.url, post.commentsUrl) }, ) Spacer(Modifier.height(4.dp)) } - if (postDetails.description.isNotBlank()) { - ThemedRichText(htmlConverter.convertHTMLToMarkdown(postDetails.description)) + if (post.description.isNotBlank()) { + ThemedRichText(htmlConverter.convertHTMLToMarkdown(post.description)) Spacer(Modifier.height(4.dp)) } Submitter( - text = AnnotatedString("Submitted by ${postDetails.submitter.username}"), - avatarUrl = "https://lobste.rs/${postDetails.submitter.avatarUrl}", - contentDescription = "User avatar for ${postDetails.submitter.username}", + text = AnnotatedString("Submitted by ${post.submitter.username}"), + avatarUrl = "https://lobste.rs/${post.submitter.avatarUrl}", + contentDescription = "User avatar for ${post.submitter.username}", modifier = Modifier.clickable { - uriHandler.openUri("https://lobste.rs/u/${postDetails.submitter.username}") + uriHandler.openUri("https://lobste.rs/u/${post.submitter.username}") }, ) } diff --git a/common/src/main/kotlin/dev/msfjarvis/claw/common/comments/Comments.kt b/common/src/main/kotlin/dev/msfjarvis/claw/common/comments/Comments.kt index 043597e7..b073989c 100644 --- a/common/src/main/kotlin/dev/msfjarvis/claw/common/comments/Comments.kt +++ b/common/src/main/kotlin/dev/msfjarvis/claw/common/comments/Comments.kt @@ -1,5 +1,5 @@ /* - * Copyright © 2021-2023 Harsh Shandilya. + * Copyright © 2021-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. @@ -36,12 +36,12 @@ import dev.msfjarvis.claw.common.ui.NetworkError import dev.msfjarvis.claw.common.ui.ProgressBar import dev.msfjarvis.claw.database.local.PostComments import dev.msfjarvis.claw.model.Comment -import dev.msfjarvis.claw.model.LobstersPostDetails +import dev.msfjarvis.claw.model.UIPost @Suppress("LongParameterList") @Composable private fun CommentsPageInternal( - details: LobstersPostDetails, + details: UIPost, postActions: PostActions, htmlConverter: HTMLConverter, commentState: PostComments?, @@ -54,11 +54,7 @@ private fun CommentsPageInternal( Surface(color = MaterialTheme.colorScheme.surfaceVariant) { LazyColumn(modifier = modifier, contentPadding = PaddingValues(bottom = 24.dp)) { item { - CommentsHeader( - postDetails = details, - postActions = postActions, - htmlConverter = htmlConverter, - ) + CommentsHeader(post = details, postActions = postActions, htmlConverter = htmlConverter) } if (commentNodes.isNotEmpty()) { @@ -123,7 +119,7 @@ fun CommentsPage( when (postDetails) { is Success<*> -> { CommentsPageInternal( - details = (postDetails as Success).data, + details = (postDetails as Success).data, postActions = postActions, htmlConverter = htmlConverter, commentState = commentState, diff --git a/common/src/main/kotlin/dev/msfjarvis/claw/common/posts/LobstersCard.kt b/common/src/main/kotlin/dev/msfjarvis/claw/common/posts/LobstersCard.kt index aee19b15..2df2ff61 100644 --- a/common/src/main/kotlin/dev/msfjarvis/claw/common/posts/LobstersCard.kt +++ b/common/src/main/kotlin/dev/msfjarvis/claw/common/posts/LobstersCard.kt @@ -51,22 +51,15 @@ import androidx.compose.ui.unit.dp import dev.msfjarvis.claw.common.theme.LobstersTheme import dev.msfjarvis.claw.common.ui.NetworkImage import dev.msfjarvis.claw.common.ui.preview.ThemePreviews -import dev.msfjarvis.claw.database.local.SavedPost import dev.msfjarvis.claw.model.LinkMetadata -import dev.msfjarvis.claw.model.LobstersPostDetails +import dev.msfjarvis.claw.model.UIPost import dev.msfjarvis.claw.model.User import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList @Composable -fun LobstersCard( - post: SavedPost, - isSaved: Boolean, - isRead: Boolean, - postActions: PostActions, - modifier: Modifier = Modifier, -) { - var localSavedState by remember(post, isSaved) { mutableStateOf(isSaved) } +fun LobstersCard(post: UIPost, postActions: PostActions, modifier: Modifier = Modifier) { + var localSavedState by remember(post) { mutableStateOf(post.isSaved) } Box( modifier = modifier @@ -79,7 +72,7 @@ fun LobstersCard( horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically, ) { - PostDetails(modifier = Modifier.weight(1f), post = post, isRead = isRead) + PostDetails(modifier = Modifier.weight(1f), post = post) Column( modifier = Modifier.wrapContentHeight(), verticalArrangement = Arrangement.spacedBy(8.dp), @@ -108,15 +101,15 @@ fun LobstersCard( } @Composable -fun PostDetails(post: SavedPost, isRead: Boolean, modifier: Modifier = Modifier) { +fun PostDetails(post: UIPost, modifier: Modifier = Modifier) { Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(8.dp)) { - PostTitle(title = post.title, isRead = isRead) + PostTitle(title = post.title, isRead = post.isRead) TagRow(tags = post.tags.toImmutableList()) Spacer(Modifier.height(4.dp)) Submitter( - text = AnnotatedString("Submitted by ${post.submitterName}"), - avatarUrl = "https://lobste.rs/${post.submitterAvatarUrl}", - contentDescription = "User avatar for ${post.submitterName}", + text = AnnotatedString("Submitted by ${post.submitter.username}"), + avatarUrl = "https://lobste.rs/${post.submitter.avatarUrl}", + contentDescription = "User avatar for ${post.submitter.username}", ) } } @@ -234,20 +227,19 @@ private fun LobstersCardPreview() { LobstersTheme { LobstersCard( post = - SavedPost( + UIPost( shortId = "ooga", title = "Simple Anomaly Detection Using Plain SQL", url = "https://hakibenita.com/sql-anomaly-detection", createdAt = "2020-09-21T08:04:24.000-05:00", commentCount = 1, commentsUrl = "https://lobste.rs/s/q1hh1g/simple_anomaly_detection_using_plain_sql", - submitterName = "Haki", - submitterAvatarUrl = "/avatars/Haki-100.png", + submitter = User("Haki", "", "", "/avatars/Haki-100.png", ""), tags = listOf("databases", "apis"), description = "", + isSaved = true, + isRead = true, ), - isRead = true, - isSaved = true, postActions = object : PostActions { override fun viewPost(postId: String, postUrl: String, commentsUrl: String) {} @@ -256,10 +248,10 @@ private fun LobstersCardPreview() { override fun viewCommentsPage(commentsUrl: String) {} - override fun toggleSave(post: SavedPost) {} + override fun toggleSave(post: UIPost) {} - override suspend fun getComments(postId: String): LobstersPostDetails { - return LobstersPostDetails( + override suspend fun getComments(postId: String): UIPost { + return UIPost( shortId = "ooga", title = "Simple Anomaly Detection Using Plain SQL", url = "https://hakibenita.com/sql-anomaly-detection", diff --git a/common/src/main/kotlin/dev/msfjarvis/claw/common/posts/PostActions.kt b/common/src/main/kotlin/dev/msfjarvis/claw/common/posts/PostActions.kt index 25c2a776..8618640f 100644 --- a/common/src/main/kotlin/dev/msfjarvis/claw/common/posts/PostActions.kt +++ b/common/src/main/kotlin/dev/msfjarvis/claw/common/posts/PostActions.kt @@ -1,5 +1,5 @@ /* - * Copyright © 2021-2023 Harsh Shandilya. + * Copyright © 2021-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. @@ -7,9 +7,8 @@ package dev.msfjarvis.claw.common.posts import androidx.compose.runtime.Stable -import dev.msfjarvis.claw.database.local.SavedPost import dev.msfjarvis.claw.model.LinkMetadata -import dev.msfjarvis.claw.model.LobstersPostDetails +import dev.msfjarvis.claw.model.UIPost @Stable interface PostActions { @@ -19,9 +18,9 @@ interface PostActions { fun viewCommentsPage(commentsUrl: String) - fun toggleSave(post: SavedPost) + fun toggleSave(post: UIPost) - suspend fun getComments(postId: String): LobstersPostDetails + suspend fun getComments(postId: String): UIPost suspend fun getLinkMetadata(url: String): LinkMetadata } diff --git a/model/src/main/kotlin/dev/msfjarvis/claw/model/LobstersPost.kt b/model/src/main/kotlin/dev/msfjarvis/claw/model/LobstersPost.kt index 6e035d96..ee0a3b92 100644 --- a/model/src/main/kotlin/dev/msfjarvis/claw/model/LobstersPost.kt +++ b/model/src/main/kotlin/dev/msfjarvis/claw/model/LobstersPost.kt @@ -9,7 +9,6 @@ package dev.msfjarvis.claw.model import dev.drewhamilton.poko.Poko -import dev.msfjarvis.claw.database.local.SavedPost import io.mcarle.konvert.api.KonvertTo import io.mcarle.konvert.api.Mapping import kotlinx.serialization.SerialName @@ -18,7 +17,7 @@ import kotlinx.serialization.Serializable @Serializable @Poko @KonvertTo( - value = SavedPost::class, + value = UIPost::class, mappings = [ Mapping(target = "submitterName", expression = "it.submitter.username"), diff --git a/model/src/main/kotlin/dev/msfjarvis/claw/model/LobstersPostDetails.kt b/model/src/main/kotlin/dev/msfjarvis/claw/model/LobstersPostDetails.kt index a82d2e56..b5c369de 100644 --- a/model/src/main/kotlin/dev/msfjarvis/claw/model/LobstersPostDetails.kt +++ b/model/src/main/kotlin/dev/msfjarvis/claw/model/LobstersPostDetails.kt @@ -17,6 +17,14 @@ import kotlinx.serialization.Serializable @Serializable @Poko +@KonvertTo( + value = UIPost::class, + mappings = + [ + Mapping(target = "submitterName", expression = "it.submitter.username"), + Mapping(target = "submitterAvatarUrl", expression = "it.submitter.avatarUrl"), + ], +) @KonvertTo( value = SavedPost::class, mappings = diff --git a/model/src/main/kotlin/dev/msfjarvis/claw/model/UIPost.kt b/model/src/main/kotlin/dev/msfjarvis/claw/model/UIPost.kt new file mode 100644 index 00000000..63d74d3a --- /dev/null +++ b/model/src/main/kotlin/dev/msfjarvis/claw/model/UIPost.kt @@ -0,0 +1,50 @@ +/* + * Copyright © 2021-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.model + +import dev.msfjarvis.claw.database.local.SavedPost +import io.mcarle.konvert.api.KonvertFrom +import io.mcarle.konvert.api.KonvertTo +import io.mcarle.konvert.api.Mapping +import kotlinx.serialization.SerialName + +@KonvertTo( + value = SavedPost::class, + mappings = + [ + Mapping(target = "submitterName", expression = "it.submitter.username"), + Mapping(target = "submitterAvatarUrl", expression = "it.submitter.avatarUrl"), + ], +) +data class UIPost( + val shortId: String, + val createdAt: String, + val title: String, + val url: String, + val description: String, + val commentCount: Int, + val commentsUrl: String, + @SerialName("submitter_user") val submitter: User, + val tags: List, + val comments: List = emptyList(), + val isSaved: Boolean = false, + val isRead: Boolean = false, +) { + @KonvertFrom( + value = SavedPost::class, + mappings = + [ + Mapping( + target = "submitter", + expression = "User(it.submitterName, \"\", null, it.submitterAvatarUrl, \"\")", + ), + Mapping(target = "commentCount", expression = "it.commentCount ?: 0"), + Mapping(target = "isSaved", expression = "true"), + ], + ) + companion object +}