mirror of
https://github.com/msfjarvis/compose-lobsters
synced 2025-08-18 00:57:02 +05:30
app: switch to new models
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
This commit is contained in:
parent
9d684536a1
commit
7b87792d8a
9 changed files with 74 additions and 86 deletions
|
@ -2,8 +2,8 @@ package dev.msfjarvis.lobsters.data.remote
|
|||
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.paging.PagingState
|
||||
import dev.msfjarvis.lobsters.data.local.LobstersPost
|
||||
import dev.msfjarvis.lobsters.data.repo.LobstersRepository
|
||||
import dev.msfjarvis.lobsters.model.LobstersPost
|
||||
import javax.inject.Inject
|
||||
|
||||
class LobstersPagingSource @Inject constructor(
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
package dev.msfjarvis.lobsters.data.repo
|
||||
|
||||
import dev.msfjarvis.lobsters.data.api.LobstersApi
|
||||
import dev.msfjarvis.lobsters.data.local.LobstersPost
|
||||
import dev.msfjarvis.lobsters.data.local.SavedPost
|
||||
import dev.msfjarvis.lobsters.model.LobstersPost
|
||||
import dev.msfjarvis.lobsters.database.LobstersDatabase
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
@ -13,7 +14,7 @@ class LobstersRepository constructor(
|
|||
private val lobstersDatabase: LobstersDatabase,
|
||||
) {
|
||||
|
||||
private val savedPostsCache: MutableMap<String, LobstersPost> = mutableMapOf()
|
||||
private val savedPostsCache: MutableMap<String, SavedPost> = mutableMapOf()
|
||||
private val _isCacheReady = MutableStateFlow(false)
|
||||
val isCacheReady = _isCacheReady.asStateFlow()
|
||||
|
||||
|
@ -21,7 +22,7 @@ class LobstersRepository constructor(
|
|||
return savedPostsCache.containsKey(postId)
|
||||
}
|
||||
|
||||
fun getAllPostsFromCache(): List<LobstersPost> {
|
||||
fun getAllPostsFromCache(): List<SavedPost> {
|
||||
return savedPostsCache.values.toList()
|
||||
}
|
||||
|
||||
|
@ -31,29 +32,29 @@ class LobstersRepository constructor(
|
|||
|
||||
suspend fun updateCache() {
|
||||
if (_isCacheReady.value) return
|
||||
val posts = getAllPosts()
|
||||
val posts = getSavedPosts()
|
||||
|
||||
posts.forEach {
|
||||
savedPostsCache[it.short_id] = it
|
||||
savedPostsCache[it.shortId] = it
|
||||
}
|
||||
_isCacheReady.value = true
|
||||
}
|
||||
|
||||
private suspend fun getAllPosts(): List<LobstersPost> = withContext(Dispatchers.IO) {
|
||||
return@withContext lobstersDatabase.postQueries.selectAllPosts().executeAsList()
|
||||
private suspend fun getSavedPosts(): List<SavedPost> = withContext(Dispatchers.IO) {
|
||||
return@withContext lobstersDatabase.savedPostQueries.selectAllPosts().executeAsList()
|
||||
}
|
||||
|
||||
suspend fun addPost(post: LobstersPost) = withContext(Dispatchers.IO) {
|
||||
if (!savedPostsCache.containsKey(post.short_id)) {
|
||||
savedPostsCache.putIfAbsent(post.short_id, post)
|
||||
lobstersDatabase.postQueries.insertOrReplacePost(post)
|
||||
suspend fun addPost(post: SavedPost) = withContext(Dispatchers.IO) {
|
||||
if (!savedPostsCache.containsKey(post.shortId)) {
|
||||
savedPostsCache.putIfAbsent(post.shortId, post)
|
||||
lobstersDatabase.savedPostQueries.insertOrReplacePost(post)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun removePost(post: LobstersPost) = withContext(Dispatchers.IO) {
|
||||
if (savedPostsCache.containsKey(post.short_id)) {
|
||||
savedPostsCache.remove(post.short_id)
|
||||
lobstersDatabase.postQueries.deletePost(post.short_id)
|
||||
suspend fun removePost(post: SavedPost) = withContext(Dispatchers.IO) {
|
||||
if (savedPostsCache.containsKey(post.shortId)) {
|
||||
savedPostsCache.remove(post.shortId)
|
||||
lobstersDatabase.savedPostQueries.deletePost(post.shortId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package dev.msfjarvis.lobsters.injection
|
||||
|
||||
import android.content.Context
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.sqldelight.android.AndroidSqliteDriver
|
||||
import com.squareup.sqldelight.db.SqlDriver
|
||||
import dagger.Module
|
||||
|
@ -10,10 +9,8 @@ import dagger.Reusable
|
|||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dev.msfjarvis.lobsters.data.local.LobstersPost
|
||||
import dev.msfjarvis.lobsters.data.local.SavedPost
|
||||
import dev.msfjarvis.lobsters.database.LobstersDatabase
|
||||
import dev.msfjarvis.lobsters.model.Submitter
|
||||
import dev.msfjarvis.lobsters.model.SubmitterAdapter
|
||||
import dev.msfjarvis.lobsters.model.TagsAdapter
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
@ -21,12 +18,6 @@ import javax.inject.Singleton
|
|||
@InstallIn(SingletonComponent::class)
|
||||
object DatabaseModule {
|
||||
|
||||
@Provides
|
||||
@Reusable
|
||||
fun providesSubmitterAdapter(jsonAdapter: JsonAdapter<Submitter>): SubmitterAdapter {
|
||||
return SubmitterAdapter(jsonAdapter)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Reusable
|
||||
fun providesTagsAdapter(): TagsAdapter {
|
||||
|
@ -43,12 +34,11 @@ object DatabaseModule {
|
|||
@Singleton
|
||||
fun providesLobstersDatabase(
|
||||
sqlDriver: SqlDriver,
|
||||
submitterAdapter: SubmitterAdapter,
|
||||
tagsAdapter: TagsAdapter
|
||||
): LobstersDatabase {
|
||||
return LobstersDatabase(
|
||||
sqlDriver,
|
||||
LobstersPost.Adapter(submitterAdapter, tagsAdapter)
|
||||
SavedPost.Adapter(tagsAdapter),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
package dev.msfjarvis.lobsters.injection
|
||||
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.adapter
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.Reusable
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dev.msfjarvis.lobsters.model.Submitter
|
||||
import dev.zacsweers.moshix.reflect.MetadataKotlinJsonAdapterFactory
|
||||
|
||||
@Module
|
||||
|
@ -21,11 +18,4 @@ object MoshiModule {
|
|||
.add(MetadataKotlinJsonAdapterFactory())
|
||||
.build()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
@Provides
|
||||
@Reusable
|
||||
fun provideSubmitterJsonAdapter(moshi: Moshi): JsonAdapter<Submitter> {
|
||||
return moshi.adapter()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,8 +11,10 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.paging.LoadState
|
||||
import androidx.paging.compose.LazyPagingItems
|
||||
import androidx.paging.compose.items
|
||||
import dev.msfjarvis.lobsters.data.local.LobstersPost
|
||||
import dev.msfjarvis.lobsters.data.local.SavedPost
|
||||
import dev.msfjarvis.lobsters.model.LobstersPost
|
||||
import dev.msfjarvis.lobsters.ui.urllauncher.LocalUrlLauncher
|
||||
import dev.msfjarvis.lobsters.util.toDbModel
|
||||
|
||||
@Composable
|
||||
fun HottestPosts(
|
||||
|
@ -20,7 +22,7 @@ fun HottestPosts(
|
|||
listState: LazyListState,
|
||||
isPostSaved: (String) -> Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
saveAction: (LobstersPost) -> Unit,
|
||||
saveAction: (SavedPost) -> Unit,
|
||||
) {
|
||||
val urlLauncher = LocalUrlLauncher.current
|
||||
|
||||
|
@ -33,13 +35,15 @@ fun HottestPosts(
|
|||
) {
|
||||
items(posts) { item ->
|
||||
if (item != null) {
|
||||
var isSaved by remember(item.short_id) { mutableStateOf(isPostSaved(item.short_id)) }
|
||||
@Suppress("NAME_SHADOWING")
|
||||
val item = item.toDbModel()
|
||||
var isSaved by remember(item.shortId) { mutableStateOf(isPostSaved(item.shortId)) }
|
||||
|
||||
LobstersItem(
|
||||
post = item,
|
||||
isSaved = isSaved,
|
||||
onClick = { urlLauncher.launch(item.url.ifEmpty { item.comments_url }) },
|
||||
onLongClick = { urlLauncher.launch(item.comments_url) },
|
||||
onClick = { urlLauncher.launch(item.url.ifEmpty { item.commentsUrl }) },
|
||||
onLongClick = { urlLauncher.launch(item.commentsUrl) },
|
||||
onSaveButtonClick = {
|
||||
isSaved = isSaved.not()
|
||||
saveAction.invoke(item)
|
||||
|
|
|
@ -29,43 +29,26 @@ import coil.transform.CircleCropTransformation
|
|||
import dev.chrisbanes.accompanist.coil.CoilImage
|
||||
import dev.msfjarvis.lobsters.R
|
||||
import dev.msfjarvis.lobsters.data.api.LobstersApi
|
||||
import dev.msfjarvis.lobsters.data.local.LobstersPost
|
||||
import dev.msfjarvis.lobsters.model.Submitter
|
||||
import dev.msfjarvis.lobsters.data.local.SavedPost
|
||||
import dev.msfjarvis.lobsters.ui.theme.LobstersTheme
|
||||
import dev.msfjarvis.lobsters.ui.theme.titleColor
|
||||
import dev.msfjarvis.lobsters.util.IconResource
|
||||
|
||||
val TEST_POST = LobstersPost(
|
||||
"zqyydb",
|
||||
"https://lobste.rs/s/zqyydb",
|
||||
"2020-09-21T07:11:14.000-05:00",
|
||||
"k2k20 hackathon report: Bob Beck on LibreSSL progress",
|
||||
"https://undeadly.org/cgi?action=article;sid=20200921105847",
|
||||
4,
|
||||
0,
|
||||
0,
|
||||
"",
|
||||
"https://lobste.rs/s/zqyydb/k2k20_hackathon_report_bob_beck_on",
|
||||
Submitter(
|
||||
"Vigdis",
|
||||
"2017-02-27T21:08:14.000-06:00",
|
||||
false,
|
||||
"Alleycat for the fun, sys/net admin for a living and OpenBSD contributions for the pleasure. (Not so) French dude in Montreal\r\n\r\nhttps://chown.me",
|
||||
false,
|
||||
76,
|
||||
"/avatars/Vigdis-100.png",
|
||||
"sevan",
|
||||
null,
|
||||
null,
|
||||
emptyList(),
|
||||
),
|
||||
listOf("openbsd", "linux", "containers", "hack the planet", "no thanks"),
|
||||
val TEST_POST = SavedPost(
|
||||
shortId = "zqyydb",
|
||||
title = "k2k20 hackathon report: Bob Beck on LibreSSL progress",
|
||||
url = "https://undeadly.org/cgi?action=article;sid=20200921105847",
|
||||
createdAt = "2020-09-21T07:11:14.000-05:00",
|
||||
commentsUrl = "https://lobste.rs/s/zqyydb/k2k20_hackathon_report_bob_beck_on",
|
||||
submitterName = "Vigdis",
|
||||
submitterAvatarUrl = "/avatars/Vigdis-100.png",
|
||||
tags = listOf("openbsd", "linux", "containers", "hack the planet", "no thanks"),
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun LobstersItem(
|
||||
post: LobstersPost,
|
||||
post: SavedPost,
|
||||
isSaved: Boolean,
|
||||
onClick: () -> Unit,
|
||||
onLongClick: () -> Unit,
|
||||
|
@ -99,10 +82,10 @@ fun LobstersItem(
|
|||
)
|
||||
Row {
|
||||
CoilImage(
|
||||
data = "${LobstersApi.BASE_URL}/${post.submitter_user.avatarUrl}",
|
||||
data = "${LobstersApi.BASE_URL}/${post.submitterAvatarUrl}",
|
||||
contentDescription = stringResource(
|
||||
R.string.avatar_content_description,
|
||||
post.submitter_user.username
|
||||
post.submitterName
|
||||
),
|
||||
fadeIn = true,
|
||||
requestBuilder = {
|
||||
|
@ -113,7 +96,7 @@ fun LobstersItem(
|
|||
.padding(4.dp),
|
||||
)
|
||||
Text(
|
||||
text = stringResource(id = R.string.submitted_by, post.submitter_user.username),
|
||||
text = stringResource(id = R.string.submitted_by, post.submitterName),
|
||||
modifier = Modifier
|
||||
.padding(4.dp),
|
||||
)
|
||||
|
|
|
@ -6,16 +6,16 @@ import androidx.compose.foundation.lazy.items
|
|||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import dev.msfjarvis.lobsters.data.local.LobstersPost
|
||||
import dev.msfjarvis.lobsters.data.local.SavedPost
|
||||
import dev.msfjarvis.lobsters.ui.urllauncher.LocalUrlLauncher
|
||||
import dev.msfjarvis.lobsters.util.asZonedDateTime
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun SavedPosts(
|
||||
posts: List<LobstersPost>,
|
||||
posts: List<SavedPost>,
|
||||
modifier: Modifier = Modifier,
|
||||
saveAction: (LobstersPost) -> Unit,
|
||||
saveAction: (SavedPost) -> Unit,
|
||||
) {
|
||||
val listState = rememberLazyListState()
|
||||
val urlLauncher = LocalUrlLauncher.current
|
||||
|
@ -27,7 +27,7 @@ fun SavedPosts(
|
|||
state = listState,
|
||||
modifier = Modifier.then(modifier),
|
||||
) {
|
||||
val grouped = posts.groupBy { it.created_at.asZonedDateTime().month }
|
||||
val grouped = posts.groupBy { it.createdAt.asZonedDateTime().month }
|
||||
grouped.forEach { (month, posts) ->
|
||||
stickyHeader {
|
||||
MonthHeader(month = month)
|
||||
|
@ -36,8 +36,8 @@ fun SavedPosts(
|
|||
LobstersItem(
|
||||
post = item,
|
||||
isSaved = true,
|
||||
onClick = { urlLauncher.launch(item.url.ifEmpty { item.comments_url }) },
|
||||
onLongClick = { urlLauncher.launch(item.comments_url) },
|
||||
onClick = { urlLauncher.launch(item.url.ifEmpty { item.commentsUrl }) },
|
||||
onLongClick = { urlLauncher.launch(item.commentsUrl) },
|
||||
onSaveButtonClick = { saveAction.invoke(item) },
|
||||
)
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import androidx.paging.Pager
|
|||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.cachedIn
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dev.msfjarvis.lobsters.data.local.LobstersPost
|
||||
import dev.msfjarvis.lobsters.data.local.SavedPost
|
||||
import dev.msfjarvis.lobsters.data.remote.LobstersPagingSource
|
||||
import dev.msfjarvis.lobsters.data.repo.LobstersRepository
|
||||
import javax.inject.Inject
|
||||
|
@ -21,7 +21,7 @@ class LobstersViewModel @Inject constructor(
|
|||
private val lobstersRepository: LobstersRepository,
|
||||
private val pagingSource: LobstersPagingSource,
|
||||
) : ViewModel() {
|
||||
private val _savedPosts = MutableStateFlow<List<LobstersPost>>(emptyList())
|
||||
private val _savedPosts = MutableStateFlow<List<SavedPost>>(emptyList())
|
||||
val savedPosts = _savedPosts.asStateFlow()
|
||||
val posts = Pager(PagingConfig(25)) {
|
||||
pagingSource
|
||||
|
@ -35,9 +35,9 @@ class LobstersViewModel @Inject constructor(
|
|||
}.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
fun toggleSave(post: LobstersPost) {
|
||||
fun toggleSave(post: SavedPost) {
|
||||
viewModelScope.launch {
|
||||
val isSaved = lobstersRepository.isPostSaved(post.short_id)
|
||||
val isSaved = lobstersRepository.isPostSaved(post.shortId)
|
||||
if (isSaved) removeSavedPost(post) else savePost(post)
|
||||
}
|
||||
}
|
||||
|
@ -46,14 +46,14 @@ class LobstersViewModel @Inject constructor(
|
|||
return lobstersRepository.isPostSaved(postId)
|
||||
}
|
||||
|
||||
private fun savePost(post: LobstersPost) {
|
||||
private fun savePost(post: SavedPost) {
|
||||
viewModelScope.launch {
|
||||
lobstersRepository.addPost(post)
|
||||
_savedPosts.value = lobstersRepository.getAllPostsFromCache()
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeSavedPost(post: LobstersPost) {
|
||||
private fun removeSavedPost(post: SavedPost) {
|
||||
viewModelScope.launch {
|
||||
lobstersRepository.removePost(post)
|
||||
_savedPosts.value = lobstersRepository.getAllPostsFromCache()
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
package dev.msfjarvis.lobsters.util
|
||||
|
||||
import dev.msfjarvis.lobsters.data.local.SavedPost
|
||||
import dev.msfjarvis.lobsters.model.LobstersPost
|
||||
|
||||
/**
|
||||
* Convert a [LobstersPost] object returned by the API into a [SavedPost] for persistence.
|
||||
*/
|
||||
fun LobstersPost.toDbModel(): SavedPost {
|
||||
return SavedPost(
|
||||
shortId = shortId,
|
||||
title = title,
|
||||
url = url,
|
||||
createdAt = createdAt,
|
||||
commentsUrl = commentsUrl,
|
||||
submitterName = submitterUser.username,
|
||||
submitterAvatarUrl = submitterUser.avatarUrl,
|
||||
tags = tags,
|
||||
)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue