mirror of
https://github.com/msfjarvis/compose-lobsters
synced 2025-08-14 22:17:03 +05:30
refactor(android): switch over to PagingData transformations
This commit is contained in:
parent
051b7ab2bb
commit
74a7835a53
19 changed files with 187 additions and 151 deletions
|
@ -28,11 +28,11 @@ import androidx.glance.text.Text
|
||||||
import androidx.glance.text.TextStyle
|
import androidx.glance.text.TextStyle
|
||||||
import dev.msfjarvis.claw.common.theme.DarkThemeColors
|
import dev.msfjarvis.claw.common.theme.DarkThemeColors
|
||||||
import dev.msfjarvis.claw.common.theme.LightThemeColors
|
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.ImmutableList
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
class SavedPostsWidget(private val posts: List<SavedPost>) : GlanceAppWidget() {
|
class SavedPostsWidget(private val posts: List<UIPost>) : GlanceAppWidget() {
|
||||||
override suspend fun provideGlance(context: Context, id: GlanceId) {
|
override suspend fun provideGlance(context: Context, id: GlanceId) {
|
||||||
provideContent {
|
provideContent {
|
||||||
GlanceTheme(
|
GlanceTheme(
|
||||||
|
@ -46,7 +46,7 @@ class SavedPostsWidget(private val posts: List<SavedPost>) : GlanceAppWidget() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun WidgetHost(posts: ImmutableList<SavedPost>, modifier: GlanceModifier = GlanceModifier) {
|
fun WidgetHost(posts: ImmutableList<UIPost>, modifier: GlanceModifier = GlanceModifier) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier =
|
modifier =
|
||||||
modifier.fillMaxSize().background(GlanceTheme.colors.background).appWidgetBackground(),
|
modifier.fillMaxSize().background(GlanceTheme.colors.background).appWidgetBackground(),
|
||||||
|
|
|
@ -33,13 +33,13 @@ import androidx.glance.text.TextStyle
|
||||||
import dev.msfjarvis.claw.android.MainActivity
|
import dev.msfjarvis.claw.android.MainActivity
|
||||||
import dev.msfjarvis.claw.android.MainActivity.Companion.NAVIGATION_KEY
|
import dev.msfjarvis.claw.android.MainActivity.Companion.NAVIGATION_KEY
|
||||||
import dev.msfjarvis.claw.android.R
|
import dev.msfjarvis.claw.android.R
|
||||||
import dev.msfjarvis.claw.database.local.SavedPost
|
import dev.msfjarvis.claw.model.UIPost
|
||||||
|
|
||||||
private val destinationKey = Key<String>(NAVIGATION_KEY)
|
private val destinationKey = Key<String>(NAVIGATION_KEY)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@GlanceComposable
|
@GlanceComposable
|
||||||
fun WidgetListEntry(post: SavedPost, modifier: GlanceModifier = GlanceModifier) {
|
fun WidgetListEntry(post: UIPost, modifier: GlanceModifier = GlanceModifier) {
|
||||||
val titleStyle = MaterialTheme.typography.titleMedium
|
val titleStyle = MaterialTheme.typography.titleMedium
|
||||||
val commentsAction =
|
val commentsAction =
|
||||||
actionStartActivity<MainActivity>(actionParametersOf(destinationKey to post.shortId))
|
actionStartActivity<MainActivity>(actionParametersOf(destinationKey to post.shortId))
|
||||||
|
@ -68,7 +68,7 @@ fun WidgetListEntry(post: SavedPost, modifier: GlanceModifier = GlanceModifier)
|
||||||
)
|
)
|
||||||
Image(
|
Image(
|
||||||
provider = ImageProvider(R.drawable.ic_comment),
|
provider = ImageProvider(R.drawable.ic_comment),
|
||||||
contentDescription = "${post.commentCount ?: 0} comments",
|
contentDescription = "${post.commentCount} comments",
|
||||||
modifier = GlanceModifier.padding(end = 4.dp).clickable(commentsAction),
|
modifier = GlanceModifier.padding(end = 4.dp).clickable(commentsAction),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
* Use of this source code is governed by an MIT-style
|
||||||
* license that can be found in the LICENSE file or at
|
* license that can be found in the LICENSE file or at
|
||||||
* https://opensource.org/licenses/MIT.
|
* 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.android.viewmodel.ClawViewModel
|
||||||
import dev.msfjarvis.claw.common.posts.PostActions
|
import dev.msfjarvis.claw.common.posts.PostActions
|
||||||
import dev.msfjarvis.claw.common.urllauncher.UrlLauncher
|
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.LinkMetadata
|
||||||
import dev.msfjarvis.claw.model.LobstersPostDetails
|
import dev.msfjarvis.claw.model.UIPost
|
||||||
|
|
||||||
fun Context.getActivity(): ComponentActivity? {
|
fun Context.getActivity(): ComponentActivity? {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
|
@ -59,11 +58,11 @@ fun rememberPostActions(
|
||||||
urlLauncher.openUri(commentsUrl.replaceAfterLast('/', "r"))
|
urlLauncher.openUri(commentsUrl.replaceAfterLast('/', "r"))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toggleSave(post: SavedPost) {
|
override fun toggleSave(post: UIPost) {
|
||||||
viewModel.toggleSave(post)
|
viewModel.toggleSave(post)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getComments(postId: String): LobstersPostDetails {
|
override suspend fun getComments(postId: String): UIPost {
|
||||||
return viewModel.getPostComments(postId)
|
return viewModel.getPostComments(postId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,13 +27,13 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import dev.msfjarvis.claw.common.posts.PostActions
|
import dev.msfjarvis.claw.common.posts.PostActions
|
||||||
import dev.msfjarvis.claw.common.ui.decorations.MonthHeader
|
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
|
import kotlinx.collections.immutable.ImmutableMap
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun DatabasePosts(
|
fun DatabasePosts(
|
||||||
items: ImmutableMap<String, List<SavedPost>>,
|
items: ImmutableMap<String, List<UIPost>>,
|
||||||
listState: LazyListState,
|
listState: LazyListState,
|
||||||
postActions: PostActions,
|
postActions: PostActions,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
@ -54,12 +54,7 @@ fun DatabasePosts(
|
||||||
items.forEach { (month, posts) ->
|
items.forEach { (month, posts) ->
|
||||||
stickyHeader(contentType = "month-header") { MonthHeader(label = month) }
|
stickyHeader(contentType = "month-header") { MonthHeader(label = month) }
|
||||||
items(items = posts, key = { it.shortId }, contentType = { "LobstersItem" }) { item ->
|
items(items = posts, key = { it.shortId }, contentType = { "LobstersItem" }) { item ->
|
||||||
LobstersListItem(
|
LobstersListItem(item = item, postActions = postActions)
|
||||||
item = item,
|
|
||||||
isSaved = { true },
|
|
||||||
isRead = { false },
|
|
||||||
postActions = postActions,
|
|
||||||
)
|
|
||||||
HorizontalDivider()
|
HorizontalDivider()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,24 +11,16 @@ import androidx.compose.material.icons.automirrored.filled.Reply
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.produceState
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||||
import dev.msfjarvis.claw.common.posts.LobstersCard
|
import dev.msfjarvis.claw.common.posts.LobstersCard
|
||||||
import dev.msfjarvis.claw.common.posts.PostActions
|
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.SwipeAction
|
||||||
import me.saket.swipe.SwipeableActionsBox
|
import me.saket.swipe.SwipeableActionsBox
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LobstersListItem(
|
fun LobstersListItem(item: UIPost, postActions: PostActions, modifier: Modifier = Modifier) {
|
||||||
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) }
|
|
||||||
val commentsAction =
|
val commentsAction =
|
||||||
SwipeAction(
|
SwipeAction(
|
||||||
icon = rememberVectorPainter(Icons.AutoMirrored.Filled.Reply),
|
icon = rememberVectorPainter(Icons.AutoMirrored.Filled.Reply),
|
||||||
|
@ -36,12 +28,6 @@ fun LobstersListItem(
|
||||||
onSwipe = { postActions.viewCommentsPage(item.commentsUrl) },
|
onSwipe = { postActions.viewCommentsPage(item.commentsUrl) },
|
||||||
)
|
)
|
||||||
SwipeableActionsBox(endActions = listOf(commentsAction)) {
|
SwipeableActionsBox(endActions = listOf(commentsAction)) {
|
||||||
LobstersCard(
|
LobstersCard(post = item, postActions = postActions, modifier = modifier)
|
||||||
post = item,
|
|
||||||
isSaved = isSaved(item),
|
|
||||||
isRead = read,
|
|
||||||
postActions = postActions,
|
|
||||||
modifier = modifier,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,9 +27,7 @@ import androidx.paging.compose.itemKey
|
||||||
import dev.msfjarvis.claw.common.posts.PostActions
|
import dev.msfjarvis.claw.common.posts.PostActions
|
||||||
import dev.msfjarvis.claw.common.ui.NetworkError
|
import dev.msfjarvis.claw.common.ui.NetworkError
|
||||||
import dev.msfjarvis.claw.common.ui.ProgressBar
|
import dev.msfjarvis.claw.common.ui.ProgressBar
|
||||||
import dev.msfjarvis.claw.database.local.SavedPost
|
import dev.msfjarvis.claw.model.UIPost
|
||||||
import dev.msfjarvis.claw.model.LobstersPost
|
|
||||||
import dev.msfjarvis.claw.model.toSavedPost
|
|
||||||
import eu.bambooapps.material3.pullrefresh.PullRefreshIndicator
|
import eu.bambooapps.material3.pullrefresh.PullRefreshIndicator
|
||||||
import eu.bambooapps.material3.pullrefresh.pullRefresh
|
import eu.bambooapps.material3.pullrefresh.pullRefresh
|
||||||
import eu.bambooapps.material3.pullrefresh.rememberPullRefreshState
|
import eu.bambooapps.material3.pullrefresh.rememberPullRefreshState
|
||||||
|
@ -37,10 +35,8 @@ import eu.bambooapps.material3.pullrefresh.rememberPullRefreshState
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun NetworkPosts(
|
fun NetworkPosts(
|
||||||
lazyPagingItems: LazyPagingItems<LobstersPost>,
|
lazyPagingItems: LazyPagingItems<UIPost>,
|
||||||
listState: LazyListState,
|
listState: LazyListState,
|
||||||
isPostSaved: (SavedPost) -> Boolean,
|
|
||||||
isPostRead: suspend (String) -> Boolean,
|
|
||||||
postActions: PostActions,
|
postActions: PostActions,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
|
@ -64,13 +60,7 @@ fun NetworkPosts(
|
||||||
) { index ->
|
) { index ->
|
||||||
val item = lazyPagingItems[index]
|
val item = lazyPagingItems[index]
|
||||||
if (item != null) {
|
if (item != null) {
|
||||||
val dbModel = item.toSavedPost()
|
LobstersListItem(item = item, postActions = postActions)
|
||||||
LobstersListItem(
|
|
||||||
item = dbModel,
|
|
||||||
isSaved = isPostSaved,
|
|
||||||
isRead = isPostRead,
|
|
||||||
postActions = postActions,
|
|
||||||
)
|
|
||||||
|
|
||||||
HorizontalDivider()
|
HorizontalDivider()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright © 2023 Harsh Shandilya.
|
* Copyright © 2023-2024 Harsh Shandilya.
|
||||||
* Use of this source code is governed by an MIT-style
|
* Use of this source code is governed by an MIT-style
|
||||||
* license that can be found in the LICENSE file or at
|
* license that can be found in the LICENSE file or at
|
||||||
* https://opensource.org/licenses/MIT.
|
* https://opensource.org/licenses/MIT.
|
||||||
|
@ -21,15 +21,13 @@ import androidx.paging.PagingData
|
||||||
import androidx.paging.compose.collectAsLazyPagingItems
|
import androidx.paging.compose.collectAsLazyPagingItems
|
||||||
import dev.msfjarvis.claw.common.posts.PostActions
|
import dev.msfjarvis.claw.common.posts.PostActions
|
||||||
import dev.msfjarvis.claw.common.ui.SearchBar
|
import dev.msfjarvis.claw.common.ui.SearchBar
|
||||||
import dev.msfjarvis.claw.database.local.SavedPost
|
import dev.msfjarvis.claw.model.UIPost
|
||||||
import dev.msfjarvis.claw.model.LobstersPost
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SearchList(
|
fun SearchList(
|
||||||
items: Flow<PagingData<LobstersPost>>,
|
items: Flow<PagingData<UIPost>>,
|
||||||
listState: LazyListState,
|
listState: LazyListState,
|
||||||
isPostSaved: (SavedPost) -> Boolean,
|
|
||||||
postActions: PostActions,
|
postActions: PostActions,
|
||||||
searchQuery: String,
|
searchQuery: String,
|
||||||
setSearchQuery: (String) -> Unit,
|
setSearchQuery: (String) -> Unit,
|
||||||
|
@ -50,8 +48,6 @@ fun SearchList(
|
||||||
NetworkPosts(
|
NetworkPosts(
|
||||||
lazyPagingItems = lazyPagingItems,
|
lazyPagingItems = lazyPagingItems,
|
||||||
listState = listState,
|
listState = listState,
|
||||||
isPostSaved = isPostSaved,
|
|
||||||
isPostRead = { false },
|
|
||||||
postActions = postActions,
|
postActions = postActions,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -228,8 +228,6 @@ fun LobstersPostsScreen(
|
||||||
NetworkPosts(
|
NetworkPosts(
|
||||||
lazyPagingItems = hottestPosts,
|
lazyPagingItems = hottestPosts,
|
||||||
listState = hottestListState,
|
listState = hottestListState,
|
||||||
isPostSaved = viewModel::isPostSaved,
|
|
||||||
isPostRead = viewModel::isPostRead,
|
|
||||||
postActions = postActions,
|
postActions = postActions,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -238,8 +236,6 @@ fun LobstersPostsScreen(
|
||||||
NetworkPosts(
|
NetworkPosts(
|
||||||
lazyPagingItems = newestPosts,
|
lazyPagingItems = newestPosts,
|
||||||
listState = newestListState,
|
listState = newestListState,
|
||||||
isPostSaved = viewModel::isPostSaved,
|
|
||||||
isPostRead = viewModel::isPostRead,
|
|
||||||
postActions = postActions,
|
postActions = postActions,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright © 2023 Harsh Shandilya.
|
* Copyright © 2023-2024 Harsh Shandilya.
|
||||||
* Use of this source code is governed by an MIT-style
|
* Use of this source code is governed by an MIT-style
|
||||||
* license that can be found in the LICENSE file or at
|
* license that can be found in the LICENSE file or at
|
||||||
* https://opensource.org/licenses/MIT.
|
* https://opensource.org/licenses/MIT.
|
||||||
|
@ -47,7 +47,6 @@ fun SearchScreen(
|
||||||
SearchList(
|
SearchList(
|
||||||
items = viewModel.searchResults,
|
items = viewModel.searchResults,
|
||||||
listState = listState,
|
listState = listState,
|
||||||
isPostSaved = viewModel::isPostSaved,
|
|
||||||
postActions = postActions,
|
postActions = postActions,
|
||||||
searchQuery = viewModel.searchQuery,
|
searchQuery = viewModel.searchQuery,
|
||||||
setSearchQuery = { query -> viewModel.searchQuery = query },
|
setSearchQuery = { query -> viewModel.searchQuery = query },
|
||||||
|
|
|
@ -18,6 +18,9 @@ import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.paging.Pager
|
import androidx.paging.Pager
|
||||||
import androidx.paging.PagingConfig
|
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.app.ApplicationScope
|
||||||
import com.deliveryhero.whetstone.viewmodel.ContributesViewModel
|
import com.deliveryhero.whetstone.viewmodel.ContributesViewModel
|
||||||
import com.slack.eithernet.ApiResult.Failure
|
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.api.LobstersApi
|
||||||
import dev.msfjarvis.claw.core.injection.IODispatcher
|
import dev.msfjarvis.claw.core.injection.IODispatcher
|
||||||
import dev.msfjarvis.claw.core.injection.MainDispatcher
|
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.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.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
|
@ -44,9 +51,11 @@ import javax.inject.Inject
|
||||||
import kotlinx.collections.immutable.ImmutableMap
|
import kotlinx.collections.immutable.ImmutableMap
|
||||||
import kotlinx.collections.immutable.toImmutableMap
|
import kotlinx.collections.immutable.toImmutableMap
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.shareIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
@ -68,34 +77,43 @@ constructor(
|
||||||
@MainDispatcher private val mainDispatcher: CoroutineDispatcher,
|
@MainDispatcher private val mainDispatcher: CoroutineDispatcher,
|
||||||
@ForScope(ApplicationScope::class) context: Context,
|
@ForScope(ApplicationScope::class) context: Context,
|
||||||
) : AndroidViewModel(context as Application) {
|
) : AndroidViewModel(context as Application) {
|
||||||
private val hottestPostsPager =
|
val hottestPosts =
|
||||||
Pager(PagingConfig(pageSize = PAGE_SIZE), initialKey = STARTING_PAGE_INDEX) {
|
Pager(
|
||||||
pagingSourceFactory.create(api::getHottestPosts)
|
config = PagingConfig(pageSize = PAGE_SIZE),
|
||||||
}
|
initialKey = STARTING_PAGE_INDEX,
|
||||||
private val newestPostsPager =
|
pagingSourceFactory = { pagingSourceFactory.create(api::getHottestPosts) },
|
||||||
Pager(PagingConfig(pageSize = PAGE_SIZE), initialKey = STARTING_PAGE_INDEX) {
|
)
|
||||||
pagingSourceFactory.create(api::getNewestPosts)
|
.flow
|
||||||
}
|
.map(::mapUIPost)
|
||||||
private val searchResultsPager =
|
.cachedIn(viewModelScope)
|
||||||
Pager(PagingConfig(pageSize = PAGE_SIZE), initialKey = STARTING_PAGE_INDEX) {
|
|
||||||
searchPagingSourceFactory.create { searchQuery }
|
|
||||||
}
|
|
||||||
|
|
||||||
val hottestPosts
|
val newestPosts =
|
||||||
get() = hottestPostsPager.flow
|
Pager(
|
||||||
|
config = PagingConfig(pageSize = PAGE_SIZE),
|
||||||
val newestPosts
|
initialKey = STARTING_PAGE_INDEX,
|
||||||
get() = newestPostsPager.flow
|
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
|
val savedPosts
|
||||||
get() = savedPostsRepository.savedPosts
|
get() =
|
||||||
|
savedPostsRepository.savedPosts
|
||||||
|
.map { it.map(UIPost.Companion::fromSavedPost) }
|
||||||
|
.shareIn(viewModelScope, started = SharingStarted.Lazily, Int.MAX_VALUE)
|
||||||
|
|
||||||
val savedPostsByMonth
|
val savedPostsByMonth
|
||||||
get() = savedPosts.map(::mapSavedPosts)
|
get() = savedPosts.map(::mapSavedPosts)
|
||||||
|
|
||||||
val searchResults
|
|
||||||
get() = searchResultsPager.flow
|
|
||||||
|
|
||||||
var searchQuery by mutableStateOf("")
|
var searchQuery by mutableStateOf("")
|
||||||
|
|
||||||
private val _savedPostsMutex = Mutex()
|
private val _savedPostsMutex = Mutex()
|
||||||
|
@ -104,12 +122,19 @@ constructor(
|
||||||
init {
|
init {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
savedPosts.collectLatest {
|
savedPosts.collectLatest {
|
||||||
_savedPostsMutex.withLock { _savedPosts = it.map(SavedPost::shortId) }
|
_savedPostsMutex.withLock { _savedPosts = it.map(UIPost::shortId) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun mapSavedPosts(items: List<SavedPost>): ImmutableMap<String, List<SavedPost>> {
|
private fun mapUIPost(pagingData: PagingData<LobstersPost>): PagingData<UIPost> {
|
||||||
|
return pagingData.map { post ->
|
||||||
|
val uiPost = post.toUIPost()
|
||||||
|
uiPost.copy(isSaved = isPostSaved(uiPost), isRead = isPostRead(uiPost))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mapSavedPosts(items: List<UIPost>): ImmutableMap<String, List<UIPost>> {
|
||||||
val sorted =
|
val sorted =
|
||||||
items.sortedWith { post1, post2 ->
|
items.sortedWith { post1, post2 ->
|
||||||
val post1Date = post1.createdAt.toLocalDateTime()
|
val post1Date = post1.createdAt.toLocalDateTime()
|
||||||
|
@ -130,17 +155,19 @@ constructor(
|
||||||
.toImmutableMap()
|
.toImmutableMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isPostSaved(post: SavedPost): Boolean {
|
private fun isPostSaved(post: UIPost): Boolean {
|
||||||
return _savedPosts.contains(post.shortId)
|
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) {
|
viewModelScope.launch(ioDispatcher) {
|
||||||
val saved = isPostSaved(post)
|
val saved = isPostSaved(post)
|
||||||
if (saved) {
|
if (saved) {
|
||||||
savedPostsRepository.removePost(post)
|
savedPostsRepository.removePost(post.toSavedPost())
|
||||||
} else {
|
} else {
|
||||||
savedPostsRepository.savePost(post)
|
savedPostsRepository.savePost(post.toSavedPost())
|
||||||
}
|
}
|
||||||
val newPosts = savedPosts.first()
|
val newPosts = savedPosts.first()
|
||||||
withContext(mainDispatcher) {
|
withContext(mainDispatcher) {
|
||||||
|
@ -152,7 +179,7 @@ constructor(
|
||||||
suspend fun getPostComments(postId: String) =
|
suspend fun getPostComments(postId: String) =
|
||||||
withContext(ioDispatcher) {
|
withContext(ioDispatcher) {
|
||||||
when (val result = api.getPostDetails(postId)) {
|
when (val result = api.getPostDetails(postId)) {
|
||||||
is Success -> result.value
|
is Success -> result.value.toUIPost()
|
||||||
is Failure.NetworkFailure -> throw result.error
|
is Failure.NetworkFailure -> throw result.error
|
||||||
is Failure.UnknownFailure -> throw result.error
|
is Failure.UnknownFailure -> throw result.error
|
||||||
is Failure.HttpFailure -> {
|
is Failure.HttpFailure -> {
|
||||||
|
@ -198,8 +225,6 @@ constructor(
|
||||||
viewModelScope.launch { readPostsRepository.markRead(postId) }
|
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
|
||||||
|
|
|
@ -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
|
* Use of this source code is governed by an MIT-style
|
||||||
* license that can be found in the LICENSE file or at
|
* license that can be found in the LICENSE file or at
|
||||||
* https://opensource.org/licenses/MIT.
|
* https://opensource.org/licenses/MIT.
|
||||||
|
@ -23,6 +23,7 @@ constructor(
|
||||||
withContext(dbDispatcher) { readPostsQueries.markRead(postId) }
|
withContext(dbDispatcher) { readPostsQueries.markRead(postId) }
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun isRead(postId: String): Boolean =
|
fun isRead(postId: String): Boolean {
|
||||||
withContext(dbDispatcher) { readPostsQueries.isRead(postId).executeAsOneOrNull() != null }
|
return readPostsQueries.isRead(postId).executeAsOneOrNull() != null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
* Use of this source code is governed by an MIT-style
|
||||||
* license that can be found in the LICENSE file or at
|
* license that can be found in the LICENSE file or at
|
||||||
* https://opensource.org/licenses/MIT.
|
* 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.android.viewmodel.SavedPostsRepository
|
||||||
import dev.msfjarvis.claw.api.LobstersApi
|
import dev.msfjarvis.claw.api.LobstersApi
|
||||||
import dev.msfjarvis.claw.model.LobstersPostDetails
|
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 dev.msfjarvis.claw.model.toSavedPost
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
|
@ -44,7 +46,10 @@ constructor(
|
||||||
.filterIsInstance<Success<LobstersPostDetails>>()
|
.filterIsInstance<Success<LobstersPostDetails>>()
|
||||||
.map { result -> result.value.toSavedPost() }
|
.map { result -> result.value.toSavedPost() }
|
||||||
.let { savedPostsRepository.savePosts(it) }
|
.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()
|
return Result.success()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,22 +46,22 @@ import dev.msfjarvis.claw.common.posts.TagRow
|
||||||
import dev.msfjarvis.claw.common.ui.NetworkImage
|
import dev.msfjarvis.claw.common.ui.NetworkImage
|
||||||
import dev.msfjarvis.claw.common.ui.ThemedRichText
|
import dev.msfjarvis.claw.common.ui.ThemedRichText
|
||||||
import dev.msfjarvis.claw.model.LinkMetadata
|
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.Instant
|
||||||
import java.time.temporal.TemporalAccessor
|
import java.time.temporal.TemporalAccessor
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun CommentsHeader(
|
internal fun CommentsHeader(
|
||||||
postDetails: LobstersPostDetails,
|
post: UIPost,
|
||||||
postActions: PostActions,
|
postActions: PostActions,
|
||||||
htmlConverter: HTMLConverter,
|
htmlConverter: HTMLConverter,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val uriHandler = LocalUriHandler.current
|
val uriHandler = LocalUriHandler.current
|
||||||
val linkMetadata by
|
val linkMetadata by
|
||||||
produceState(initialValue = LinkMetadata(postDetails.url, null)) {
|
produceState(initialValue = LinkMetadata(post.url, null)) {
|
||||||
runSuspendCatching { postActions.getLinkMetadata(postDetails.url) }
|
runSuspendCatching { postActions.getLinkMetadata(post.url) }
|
||||||
.onSuccess { metadata -> value = metadata }
|
.onSuccess { metadata -> value = metadata }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,8 +70,8 @@ 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, isRead = false)
|
PostTitle(title = post.title, isRead = false)
|
||||||
TagRow(tags = postDetails.tags.toImmutableList())
|
TagRow(tags = post.tags.toImmutableList())
|
||||||
Spacer(Modifier.height(4.dp))
|
Spacer(Modifier.height(4.dp))
|
||||||
|
|
||||||
if (linkMetadata.url.isNotBlank()) {
|
if (linkMetadata.url.isNotBlank()) {
|
||||||
|
@ -79,23 +79,23 @@ internal fun CommentsHeader(
|
||||||
linkMetadata = linkMetadata,
|
linkMetadata = linkMetadata,
|
||||||
modifier =
|
modifier =
|
||||||
Modifier.clickable {
|
Modifier.clickable {
|
||||||
postActions.viewPost(postDetails.shortId, linkMetadata.url, postDetails.commentsUrl)
|
postActions.viewPost(post.shortId, linkMetadata.url, post.commentsUrl)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
Spacer(Modifier.height(4.dp))
|
Spacer(Modifier.height(4.dp))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (postDetails.description.isNotBlank()) {
|
if (post.description.isNotBlank()) {
|
||||||
ThemedRichText(htmlConverter.convertHTMLToMarkdown(postDetails.description))
|
ThemedRichText(htmlConverter.convertHTMLToMarkdown(post.description))
|
||||||
Spacer(Modifier.height(4.dp))
|
Spacer(Modifier.height(4.dp))
|
||||||
}
|
}
|
||||||
Submitter(
|
Submitter(
|
||||||
text = AnnotatedString("Submitted by ${postDetails.submitter.username}"),
|
text = AnnotatedString("Submitted by ${post.submitter.username}"),
|
||||||
avatarUrl = "https://lobste.rs/${postDetails.submitter.avatarUrl}",
|
avatarUrl = "https://lobste.rs/${post.submitter.avatarUrl}",
|
||||||
contentDescription = "User avatar for ${postDetails.submitter.username}",
|
contentDescription = "User avatar for ${post.submitter.username}",
|
||||||
modifier =
|
modifier =
|
||||||
Modifier.clickable {
|
Modifier.clickable {
|
||||||
uriHandler.openUri("https://lobste.rs/u/${postDetails.submitter.username}")
|
uriHandler.openUri("https://lobste.rs/u/${post.submitter.username}")
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
* Use of this source code is governed by an MIT-style
|
||||||
* license that can be found in the LICENSE file or at
|
* license that can be found in the LICENSE file or at
|
||||||
* https://opensource.org/licenses/MIT.
|
* 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.common.ui.ProgressBar
|
||||||
import dev.msfjarvis.claw.database.local.PostComments
|
import dev.msfjarvis.claw.database.local.PostComments
|
||||||
import dev.msfjarvis.claw.model.Comment
|
import dev.msfjarvis.claw.model.Comment
|
||||||
import dev.msfjarvis.claw.model.LobstersPostDetails
|
import dev.msfjarvis.claw.model.UIPost
|
||||||
|
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList")
|
||||||
@Composable
|
@Composable
|
||||||
private fun CommentsPageInternal(
|
private fun CommentsPageInternal(
|
||||||
details: LobstersPostDetails,
|
details: UIPost,
|
||||||
postActions: PostActions,
|
postActions: PostActions,
|
||||||
htmlConverter: HTMLConverter,
|
htmlConverter: HTMLConverter,
|
||||||
commentState: PostComments?,
|
commentState: PostComments?,
|
||||||
|
@ -54,11 +54,7 @@ private fun CommentsPageInternal(
|
||||||
Surface(color = MaterialTheme.colorScheme.surfaceVariant) {
|
Surface(color = MaterialTheme.colorScheme.surfaceVariant) {
|
||||||
LazyColumn(modifier = modifier, contentPadding = PaddingValues(bottom = 24.dp)) {
|
LazyColumn(modifier = modifier, contentPadding = PaddingValues(bottom = 24.dp)) {
|
||||||
item {
|
item {
|
||||||
CommentsHeader(
|
CommentsHeader(post = details, postActions = postActions, htmlConverter = htmlConverter)
|
||||||
postDetails = details,
|
|
||||||
postActions = postActions,
|
|
||||||
htmlConverter = htmlConverter,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (commentNodes.isNotEmpty()) {
|
if (commentNodes.isNotEmpty()) {
|
||||||
|
@ -123,7 +119,7 @@ fun CommentsPage(
|
||||||
when (postDetails) {
|
when (postDetails) {
|
||||||
is Success<*> -> {
|
is Success<*> -> {
|
||||||
CommentsPageInternal(
|
CommentsPageInternal(
|
||||||
details = (postDetails as Success<LobstersPostDetails>).data,
|
details = (postDetails as Success<UIPost>).data,
|
||||||
postActions = postActions,
|
postActions = postActions,
|
||||||
htmlConverter = htmlConverter,
|
htmlConverter = htmlConverter,
|
||||||
commentState = commentState,
|
commentState = commentState,
|
||||||
|
|
|
@ -51,22 +51,15 @@ import androidx.compose.ui.unit.dp
|
||||||
import dev.msfjarvis.claw.common.theme.LobstersTheme
|
import dev.msfjarvis.claw.common.theme.LobstersTheme
|
||||||
import dev.msfjarvis.claw.common.ui.NetworkImage
|
import dev.msfjarvis.claw.common.ui.NetworkImage
|
||||||
import dev.msfjarvis.claw.common.ui.preview.ThemePreviews
|
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.LinkMetadata
|
||||||
import dev.msfjarvis.claw.model.LobstersPostDetails
|
import dev.msfjarvis.claw.model.UIPost
|
||||||
import dev.msfjarvis.claw.model.User
|
import dev.msfjarvis.claw.model.User
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LobstersCard(
|
fun LobstersCard(post: UIPost, postActions: PostActions, modifier: Modifier = Modifier) {
|
||||||
post: SavedPost,
|
var localSavedState by remember(post) { mutableStateOf(post.isSaved) }
|
||||||
isSaved: Boolean,
|
|
||||||
isRead: Boolean,
|
|
||||||
postActions: PostActions,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
var localSavedState by remember(post, isSaved) { mutableStateOf(isSaved) }
|
|
||||||
Box(
|
Box(
|
||||||
modifier =
|
modifier =
|
||||||
modifier
|
modifier
|
||||||
|
@ -79,7 +72,7 @@ fun LobstersCard(
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
PostDetails(modifier = Modifier.weight(1f), post = post, isRead = isRead)
|
PostDetails(modifier = Modifier.weight(1f), post = post)
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.wrapContentHeight(),
|
modifier = Modifier.wrapContentHeight(),
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
@ -108,15 +101,15 @@ fun LobstersCard(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@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)) {
|
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())
|
TagRow(tags = post.tags.toImmutableList())
|
||||||
Spacer(Modifier.height(4.dp))
|
Spacer(Modifier.height(4.dp))
|
||||||
Submitter(
|
Submitter(
|
||||||
text = AnnotatedString("Submitted by ${post.submitterName}"),
|
text = AnnotatedString("Submitted by ${post.submitter.username}"),
|
||||||
avatarUrl = "https://lobste.rs/${post.submitterAvatarUrl}",
|
avatarUrl = "https://lobste.rs/${post.submitter.avatarUrl}",
|
||||||
contentDescription = "User avatar for ${post.submitterName}",
|
contentDescription = "User avatar for ${post.submitter.username}",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -234,20 +227,19 @@ private fun LobstersCardPreview() {
|
||||||
LobstersTheme {
|
LobstersTheme {
|
||||||
LobstersCard(
|
LobstersCard(
|
||||||
post =
|
post =
|
||||||
SavedPost(
|
UIPost(
|
||||||
shortId = "ooga",
|
shortId = "ooga",
|
||||||
title = "Simple Anomaly Detection Using Plain SQL",
|
title = "Simple Anomaly Detection Using Plain SQL",
|
||||||
url = "https://hakibenita.com/sql-anomaly-detection",
|
url = "https://hakibenita.com/sql-anomaly-detection",
|
||||||
createdAt = "2020-09-21T08:04:24.000-05:00",
|
createdAt = "2020-09-21T08:04:24.000-05:00",
|
||||||
commentCount = 1,
|
commentCount = 1,
|
||||||
commentsUrl = "https://lobste.rs/s/q1hh1g/simple_anomaly_detection_using_plain_sql",
|
commentsUrl = "https://lobste.rs/s/q1hh1g/simple_anomaly_detection_using_plain_sql",
|
||||||
submitterName = "Haki",
|
submitter = User("Haki", "", "", "/avatars/Haki-100.png", ""),
|
||||||
submitterAvatarUrl = "/avatars/Haki-100.png",
|
|
||||||
tags = listOf("databases", "apis"),
|
tags = listOf("databases", "apis"),
|
||||||
description = "",
|
description = "",
|
||||||
|
isSaved = true,
|
||||||
|
isRead = true,
|
||||||
),
|
),
|
||||||
isRead = true,
|
|
||||||
isSaved = true,
|
|
||||||
postActions =
|
postActions =
|
||||||
object : PostActions {
|
object : PostActions {
|
||||||
override fun viewPost(postId: String, postUrl: String, commentsUrl: String) {}
|
override fun viewPost(postId: String, postUrl: String, commentsUrl: String) {}
|
||||||
|
@ -256,10 +248,10 @@ private fun LobstersCardPreview() {
|
||||||
|
|
||||||
override fun viewCommentsPage(commentsUrl: String) {}
|
override fun viewCommentsPage(commentsUrl: String) {}
|
||||||
|
|
||||||
override fun toggleSave(post: SavedPost) {}
|
override fun toggleSave(post: UIPost) {}
|
||||||
|
|
||||||
override suspend fun getComments(postId: String): LobstersPostDetails {
|
override suspend fun getComments(postId: String): UIPost {
|
||||||
return LobstersPostDetails(
|
return UIPost(
|
||||||
shortId = "ooga",
|
shortId = "ooga",
|
||||||
title = "Simple Anomaly Detection Using Plain SQL",
|
title = "Simple Anomaly Detection Using Plain SQL",
|
||||||
url = "https://hakibenita.com/sql-anomaly-detection",
|
url = "https://hakibenita.com/sql-anomaly-detection",
|
||||||
|
|
|
@ -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
|
* Use of this source code is governed by an MIT-style
|
||||||
* license that can be found in the LICENSE file or at
|
* license that can be found in the LICENSE file or at
|
||||||
* https://opensource.org/licenses/MIT.
|
* https://opensource.org/licenses/MIT.
|
||||||
|
@ -7,9 +7,8 @@
|
||||||
package dev.msfjarvis.claw.common.posts
|
package dev.msfjarvis.claw.common.posts
|
||||||
|
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import dev.msfjarvis.claw.database.local.SavedPost
|
|
||||||
import dev.msfjarvis.claw.model.LinkMetadata
|
import dev.msfjarvis.claw.model.LinkMetadata
|
||||||
import dev.msfjarvis.claw.model.LobstersPostDetails
|
import dev.msfjarvis.claw.model.UIPost
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
interface PostActions {
|
interface PostActions {
|
||||||
|
@ -19,9 +18,9 @@ interface PostActions {
|
||||||
|
|
||||||
fun viewCommentsPage(commentsUrl: String)
|
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
|
suspend fun getLinkMetadata(url: String): LinkMetadata
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
package dev.msfjarvis.claw.model
|
package dev.msfjarvis.claw.model
|
||||||
|
|
||||||
import dev.drewhamilton.poko.Poko
|
import dev.drewhamilton.poko.Poko
|
||||||
import dev.msfjarvis.claw.database.local.SavedPost
|
|
||||||
import io.mcarle.konvert.api.KonvertTo
|
import io.mcarle.konvert.api.KonvertTo
|
||||||
import io.mcarle.konvert.api.Mapping
|
import io.mcarle.konvert.api.Mapping
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
|
@ -18,7 +17,7 @@ import kotlinx.serialization.Serializable
|
||||||
@Serializable
|
@Serializable
|
||||||
@Poko
|
@Poko
|
||||||
@KonvertTo(
|
@KonvertTo(
|
||||||
value = SavedPost::class,
|
value = UIPost::class,
|
||||||
mappings =
|
mappings =
|
||||||
[
|
[
|
||||||
Mapping(target = "submitterName", expression = "it.submitter.username"),
|
Mapping(target = "submitterName", expression = "it.submitter.username"),
|
||||||
|
|
|
@ -17,6 +17,14 @@ import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@Poko
|
@Poko
|
||||||
|
@KonvertTo(
|
||||||
|
value = UIPost::class,
|
||||||
|
mappings =
|
||||||
|
[
|
||||||
|
Mapping(target = "submitterName", expression = "it.submitter.username"),
|
||||||
|
Mapping(target = "submitterAvatarUrl", expression = "it.submitter.avatarUrl"),
|
||||||
|
],
|
||||||
|
)
|
||||||
@KonvertTo(
|
@KonvertTo(
|
||||||
value = SavedPost::class,
|
value = SavedPost::class,
|
||||||
mappings =
|
mappings =
|
||||||
|
|
50
model/src/main/kotlin/dev/msfjarvis/claw/model/UIPost.kt
Normal file
50
model/src/main/kotlin/dev/msfjarvis/claw/model/UIPost.kt
Normal file
|
@ -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<String>,
|
||||||
|
val comments: List<Comment> = 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
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue