mirror of
https://github.com/msfjarvis/compose-lobsters
synced 2025-08-18 12:37:03 +05:30
Merge pull request #43 from Skrilltrax/develop
This commit is contained in:
commit
85a5c829e0
15 changed files with 279 additions and 41 deletions
|
@ -5,16 +5,24 @@ import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.compose.foundation.Text
|
import androidx.compose.foundation.Text
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumnForIndexed
|
import androidx.compose.foundation.lazy.LazyColumnForIndexed
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material.FloatingActionButton
|
import androidx.compose.material.FloatingActionButton
|
||||||
|
import androidx.compose.material.IconToggleButton
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.Scaffold
|
import androidx.compose.material.Scaffold
|
||||||
|
import androidx.compose.material.TopAppBar
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.Providers
|
import androidx.compose.runtime.Providers
|
||||||
import androidx.compose.runtime.ambientOf
|
import androidx.compose.runtime.ambientOf
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.setContent
|
import androidx.compose.ui.platform.setContent
|
||||||
|
@ -23,8 +31,10 @@ import androidx.compose.ui.unit.dp
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import dev.msfjarvis.lobsters.compose.utils.IconResource
|
import dev.msfjarvis.lobsters.compose.utils.IconResource
|
||||||
import dev.msfjarvis.lobsters.data.LobstersViewModel
|
import dev.msfjarvis.lobsters.data.LobstersViewModel
|
||||||
|
import dev.msfjarvis.lobsters.model.LobstersPost
|
||||||
import dev.msfjarvis.lobsters.ui.LobstersItem
|
import dev.msfjarvis.lobsters.ui.LobstersItem
|
||||||
import dev.msfjarvis.lobsters.ui.LobstersTheme
|
import dev.msfjarvis.lobsters.ui.LobstersTheme
|
||||||
|
import dev.msfjarvis.lobsters.ui.savedTitleColor
|
||||||
import dev.msfjarvis.lobsters.urllauncher.UrlLauncher
|
import dev.msfjarvis.lobsters.urllauncher.UrlLauncher
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -52,40 +62,128 @@ fun LobstersApp(
|
||||||
viewModel: LobstersViewModel
|
viewModel: LobstersViewModel
|
||||||
) {
|
) {
|
||||||
val urlLauncher = UrlLauncherAmbient.current
|
val urlLauncher = UrlLauncherAmbient.current
|
||||||
val state = viewModel.posts.collectAsState()
|
val posts = viewModel.posts.collectAsState()
|
||||||
val lastIndex = state.value.lastIndex
|
val savedPosts = viewModel.savedPosts.collectAsState()
|
||||||
|
val lastIndex = posts.value.lastIndex
|
||||||
|
val showSaved = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
LobstersTopAppBar(showSaved.value) {
|
||||||
|
showSaved.value = !showSaved.value
|
||||||
|
}
|
||||||
|
},
|
||||||
bodyContent = {
|
bodyContent = {
|
||||||
if (state.value.isEmpty()) {
|
val saved = showSaved.value
|
||||||
Column(
|
if (saved && savedPosts.value.isEmpty()) {
|
||||||
modifier = Modifier.fillMaxSize(),
|
EmptyList(saved)
|
||||||
verticalArrangement = Arrangement.Center,
|
} else if (!saved && posts.value.isEmpty()) {
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
EmptyList(saved)
|
||||||
) {
|
|
||||||
IconResource(R.drawable.ic_sync_problem_24px)
|
|
||||||
Text(stringResource(R.string.loading))
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
LazyColumnForIndexed(
|
LobsterList(
|
||||||
items = state.value,
|
showSaved.value,
|
||||||
modifier = Modifier.padding(horizontal = 8.dp)
|
savedPosts.value,
|
||||||
) { index, item ->
|
posts.value,
|
||||||
if (lastIndex == index) {
|
lastIndex,
|
||||||
viewModel.getMorePosts()
|
viewModel,
|
||||||
}
|
urlLauncher
|
||||||
LobstersItem(
|
)
|
||||||
item,
|
|
||||||
linkOpenAction = { post -> urlLauncher.launch(post.url.ifEmpty { post.commentsUrl }) },
|
|
||||||
commentOpenAction = { post -> urlLauncher.launch(post.commentsUrl) },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
floatingActionButton = {
|
|
||||||
FloatingActionButton(onClick = { viewModel.refreshPosts() }) {
|
|
||||||
IconResource(resourceId = R.drawable.ic_refresh_24px)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
floatingActionButton = { LobstersFAB(showSaved.value, viewModel) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun LobstersFAB(
|
||||||
|
showSaved: Boolean,
|
||||||
|
viewModel: LobstersViewModel
|
||||||
|
) {
|
||||||
|
if (!showSaved) {
|
||||||
|
FloatingActionButton(
|
||||||
|
onClick = { viewModel.refreshPosts() },
|
||||||
|
modifier = Modifier
|
||||||
|
) {
|
||||||
|
IconResource(resourceId = R.drawable.ic_refresh_24px)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun LobsterList(
|
||||||
|
showSaved: Boolean,
|
||||||
|
savedPosts: List<LobstersPost>,
|
||||||
|
hottestPosts: List<LobstersPost>,
|
||||||
|
lastIndex: Int,
|
||||||
|
viewModel: LobstersViewModel,
|
||||||
|
urlLauncher: UrlLauncher
|
||||||
|
) {
|
||||||
|
val hottestPostsListState = rememberLazyListState()
|
||||||
|
val savedPostsListState = rememberLazyListState()
|
||||||
|
|
||||||
|
LazyColumnForIndexed(
|
||||||
|
items = if (showSaved) savedPosts else hottestPosts,
|
||||||
|
state = if (showSaved) savedPostsListState else hottestPostsListState,
|
||||||
|
modifier = Modifier.padding(horizontal = 8.dp)
|
||||||
|
) { index, item ->
|
||||||
|
if (lastIndex == index && !showSaved) {
|
||||||
|
viewModel.getMorePosts()
|
||||||
|
}
|
||||||
|
LobstersItem(
|
||||||
|
item,
|
||||||
|
linkOpenAction = { post -> urlLauncher.launch(post.url.ifEmpty { post.commentsUrl }) },
|
||||||
|
commentOpenAction = { post -> urlLauncher.launch(post.commentsUrl) },
|
||||||
|
saveAction = { post ->
|
||||||
|
if (showSaved) {
|
||||||
|
viewModel.removeSavedPost(post)
|
||||||
|
} else {
|
||||||
|
viewModel.savePost(post)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun EmptyList(showSaved: Boolean) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
) {
|
||||||
|
if (showSaved) {
|
||||||
|
IconResource(
|
||||||
|
R.drawable.ic_favorite_border_24px,
|
||||||
|
tint = savedTitleColor,
|
||||||
|
modifier = Modifier.padding(16.dp)
|
||||||
|
)
|
||||||
|
Text(stringResource(R.string.no_saved_posts))
|
||||||
|
} else {
|
||||||
|
IconResource(R.drawable.ic_sync_problem_24px, modifier = Modifier.padding(16.dp))
|
||||||
|
Text(stringResource(R.string.loading))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun LobstersTopAppBar(showSaved: Boolean, toggleAction: () -> Unit) {
|
||||||
|
TopAppBar {
|
||||||
|
Box(modifier = Modifier.fillMaxWidth()) {
|
||||||
|
Text(
|
||||||
|
text = if (showSaved) "Saved" else "Home",
|
||||||
|
modifier = Modifier.padding(16.dp).align(Alignment.CenterStart),
|
||||||
|
style = MaterialTheme.typography.h6,
|
||||||
|
)
|
||||||
|
IconToggleButton(
|
||||||
|
checked = showSaved,
|
||||||
|
onCheckedChange = { toggleAction.invoke() },
|
||||||
|
modifier = Modifier.padding(8.dp).align(Alignment.CenterEnd),
|
||||||
|
) {
|
||||||
|
IconResource(
|
||||||
|
resourceId = if (showSaved) R.drawable.ic_favorite_24px else R.drawable.ic_favorite_border_24px,
|
||||||
|
tint = savedTitleColor,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -16,8 +16,8 @@
|
||||||
package dev.msfjarvis.lobsters.compose.utils
|
package dev.msfjarvis.lobsters.compose.utils
|
||||||
|
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.compose.foundation.Icon
|
|
||||||
import androidx.compose.foundation.AmbientContentColor
|
import androidx.compose.foundation.AmbientContentColor
|
||||||
|
import androidx.compose.foundation.Icon
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
|
@ -20,14 +20,16 @@ class LobstersViewModel @ViewModelInject constructor(
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
private var apiPage = 1
|
private var apiPage = 1
|
||||||
private val _posts = MutableStateFlow<List<LobstersPost>>(emptyList())
|
private val _posts = MutableStateFlow<List<LobstersPost>>(emptyList())
|
||||||
private val dao = database.postsDao()
|
private val _savedPosts = MutableStateFlow<List<LobstersPost>>(emptyList())
|
||||||
|
private val postsDao = database.postsDao()
|
||||||
|
private val savedPostsDao = database.savedPostsDao()
|
||||||
private val coroutineExceptionHandler = CoroutineExceptionHandler { _, throwable ->
|
private val coroutineExceptionHandler = CoroutineExceptionHandler { _, throwable ->
|
||||||
when (throwable) {
|
when (throwable) {
|
||||||
// Swallow known network errors that can be recovered from.
|
// Swallow known network errors that can be recovered from.
|
||||||
is UnknownHostException, is SocketTimeoutException -> {
|
is UnknownHostException, is SocketTimeoutException -> {
|
||||||
if (_posts.value.isEmpty()) {
|
if (_posts.value.isEmpty()) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
dao.loadPosts().collectLatest { _posts.value = it }
|
postsDao.loadPosts().collectLatest { _posts.value = it }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,9 +37,17 @@ class LobstersViewModel @ViewModelInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val posts: StateFlow<List<LobstersPost>> get() = _posts
|
val posts: StateFlow<List<LobstersPost>> get() = _posts
|
||||||
|
val savedPosts: StateFlow<List<LobstersPost>> get() = _savedPosts
|
||||||
|
|
||||||
init {
|
init {
|
||||||
getMorePostsInternal(true)
|
getMorePostsInternal(true)
|
||||||
|
getSavedPosts()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getSavedPosts() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
savedPostsDao.loadPosts().collectLatest { _savedPosts.value = it }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getMorePosts() {
|
fun getMorePosts() {
|
||||||
|
@ -52,14 +62,36 @@ class LobstersViewModel @ViewModelInject constructor(
|
||||||
private fun getMorePostsInternal(firstLoad: Boolean) {
|
private fun getMorePostsInternal(firstLoad: Boolean) {
|
||||||
viewModelScope.launch(coroutineExceptionHandler) {
|
viewModelScope.launch(coroutineExceptionHandler) {
|
||||||
val newPosts = lobstersApi.getHottestPosts(apiPage)
|
val newPosts = lobstersApi.getHottestPosts(apiPage)
|
||||||
|
.transformLikedFlag()
|
||||||
|
.toList()
|
||||||
if (firstLoad) {
|
if (firstLoad) {
|
||||||
_posts.value = newPosts
|
_posts.value = newPosts
|
||||||
dao.deleteAllPosts()
|
postsDao.deleteAllPosts()
|
||||||
} else {
|
} else {
|
||||||
_posts.value += newPosts
|
_posts.value += newPosts
|
||||||
}
|
}
|
||||||
apiPage += 1
|
apiPage += 1
|
||||||
dao.insertPosts(*_posts.value.toTypedArray())
|
postsDao.insertPosts(*_posts.value.toTypedArray())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun savePost(post: LobstersPost) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
savedPostsDao.insertPosts(post)
|
||||||
|
getSavedPosts()
|
||||||
|
_posts.value = _posts.value.transformLikedFlag().toList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeSavedPost(post: LobstersPost) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
savedPostsDao.deletePostById(post.shortId)
|
||||||
|
getSavedPosts()
|
||||||
|
_posts.value = _posts.value.transformLikedFlag().toList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun List<LobstersPost>.transformLikedFlag() = map {
|
||||||
|
it.apply { isLiked = savedPostsDao.isLiked(shortId) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import dev.msfjarvis.lobsters.api.LobstersApi
|
||||||
@Module
|
@Module
|
||||||
object ApiModule {
|
object ApiModule {
|
||||||
const val LOBSTERS_URL = "https://lobste.rs"
|
const val LOBSTERS_URL = "https://lobste.rs"
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun provideLobstersApi(): LobstersApi {
|
fun provideLobstersApi(): LobstersApi {
|
||||||
return ApiClient.getClient(LOBSTERS_URL)
|
return ApiClient.getClient(LOBSTERS_URL)
|
||||||
|
|
|
@ -13,6 +13,8 @@ import androidx.compose.foundation.lazy.LazyColumnFor
|
||||||
import androidx.compose.foundation.lazy.LazyItemScope
|
import androidx.compose.foundation.lazy.LazyItemScope
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
@ -31,18 +33,27 @@ fun LazyItemScope.LobstersItem(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
linkOpenAction: (LobstersPost) -> Unit,
|
linkOpenAction: (LobstersPost) -> Unit,
|
||||||
commentOpenAction: (LobstersPost) -> Unit,
|
commentOpenAction: (LobstersPost) -> Unit,
|
||||||
|
saveAction: (LobstersPost) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
val liked = remember { mutableStateOf(false) }
|
||||||
|
val titleColor = if (post.isLiked || liked.value) savedTitleColor else titleColor
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillParentMaxWidth()
|
.fillParentMaxWidth()
|
||||||
.clickable(
|
.clickable(
|
||||||
onClick = { linkOpenAction.invoke(post) },
|
onClick = { linkOpenAction.invoke(post) },
|
||||||
onLongClick = { commentOpenAction.invoke(post) }
|
onLongClick = { commentOpenAction.invoke(post) },
|
||||||
|
onDoubleClick = {
|
||||||
|
post.isLiked = true
|
||||||
|
liked.value = true
|
||||||
|
saveAction.invoke(post)
|
||||||
|
},
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = post.title,
|
text = post.title,
|
||||||
color = Color(0xFF7395D9),
|
color = titleColor,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
modifier = Modifier.padding(top = 4.dp)
|
modifier = Modifier.padding(top = 4.dp)
|
||||||
)
|
)
|
||||||
|
@ -106,11 +117,15 @@ fun PreviewLobstersItem() {
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
),
|
),
|
||||||
listOf("openbsd")
|
listOf("openbsd"),
|
||||||
)
|
)
|
||||||
LobstersTheme {
|
LobstersTheme {
|
||||||
LazyColumnFor(items = listOf(post)) { item ->
|
LazyColumnFor(items = listOf(post)) { item ->
|
||||||
LobstersItem(post = item, linkOpenAction = {}, commentOpenAction = {})
|
LobstersItem(
|
||||||
|
post = item,
|
||||||
|
linkOpenAction = {},
|
||||||
|
commentOpenAction = {},
|
||||||
|
saveAction = {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,9 @@ import androidx.compose.material.darkColors
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
|
val titleColor = Color(0xFF7395D9)
|
||||||
|
val savedTitleColor = Color(0xFFD97373)
|
||||||
|
|
||||||
val darkColors = darkColors(
|
val darkColors = darkColors(
|
||||||
primary = Color.White,
|
primary = Color.White,
|
||||||
secondary = Color(0xFF6C0000),
|
secondary = Color(0xFF6C0000),
|
||||||
|
|
9
app/src/main/res/drawable/ic_favorite_24px.xml
Normal file
9
app/src/main/res/drawable/ic_favorite_24px.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M12,21.35l-1.45,-1.32C5.4,15.36 2,12.28 2,8.5 2,5.42 4.42,3 7.5,3c1.74,0 3.41,0.81 4.5,2.09C13.09,3.81 14.76,3 16.5,3 19.58,3 22,5.42 22,8.5c0,3.78 -3.4,6.86 -8.55,11.54L12,21.35z"/>
|
||||||
|
</vector>
|
9
app/src/main/res/drawable/ic_favorite_border_24px.xml
Normal file
9
app/src/main/res/drawable/ic_favorite_border_24px.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M16.5,3c-1.74,0 -3.41,0.81 -4.5,2.09C10.91,3.81 9.24,3 7.5,3 4.42,3 2,5.42 2,8.5c0,3.78 3.4,6.86 8.55,11.54L12,21.35l1.45,-1.32C18.6,15.36 22,12.28 22,8.5 22,5.42 19.58,3 16.5,3zM12.1,18.55l-0.1,0.1 -0.1,-0.1C7.14,14.24 4,11.39 4,8.5 4,6.5 5.5,5 7.5,5c1.54,0 3.04,0.99 3.57,2.36h1.87C13.46,5.99 14.96,5 16.5,5c2,0 3.5,1.5 3.5,3.5 0,2.89 -3.14,5.74 -7.9,10.05z"/>
|
||||||
|
</vector>
|
|
@ -1,4 +1,5 @@
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">lobste.rs</string>
|
<string name="app_name">lobste.rs</string>
|
||||||
<string name="loading">Loading posts…</string>
|
<string name="loading">Loading posts…</string>
|
||||||
|
<string name="no_saved_posts">You don\'t have any saved posts</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
package dev.msfjarvis.lobsters.data.model
|
||||||
|
|
||||||
|
import androidx.room.Embedded
|
||||||
|
import androidx.room.Entity
|
||||||
|
import dev.msfjarvis.lobsters.model.LobstersPost
|
||||||
|
|
||||||
|
@Entity(
|
||||||
|
tableName = "lobsters_saved_posts",
|
||||||
|
primaryKeys = ["shortId"],
|
||||||
|
)
|
||||||
|
data class SavedLobstersEntity(
|
||||||
|
@Embedded
|
||||||
|
val post: LobstersPost
|
||||||
|
)
|
|
@ -6,6 +6,7 @@ import androidx.room.Insert
|
||||||
import androidx.room.OnConflictStrategy
|
import androidx.room.OnConflictStrategy
|
||||||
import androidx.room.Query
|
import androidx.room.Query
|
||||||
import androidx.room.Transaction
|
import androidx.room.Transaction
|
||||||
|
import androidx.room.Update
|
||||||
import dev.msfjarvis.lobsters.data.model.LobstersEntity
|
import dev.msfjarvis.lobsters.data.model.LobstersEntity
|
||||||
import dev.msfjarvis.lobsters.model.LobstersPost
|
import dev.msfjarvis.lobsters.model.LobstersPost
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
@ -15,6 +16,15 @@ abstract class PostsDao {
|
||||||
@Query("SELECT * FROM lobsters_posts")
|
@Query("SELECT * FROM lobsters_posts")
|
||||||
abstract fun loadPosts(): Flow<List<LobstersPost>>
|
abstract fun loadPosts(): Flow<List<LobstersPost>>
|
||||||
|
|
||||||
|
@Update
|
||||||
|
suspend fun updatePost(vararg posts: LobstersPost) {
|
||||||
|
updatePosts(posts.map { LobstersEntity(it) })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Update(onConflict = OnConflictStrategy.IGNORE)
|
||||||
|
protected abstract suspend fun updatePosts(posts: List<LobstersEntity>)
|
||||||
|
|
||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
open suspend fun insertPosts(vararg posts: LobstersPost) {
|
open suspend fun insertPosts(vararg posts: LobstersPost) {
|
||||||
insertPosts(posts.map { LobstersEntity(it) })
|
insertPosts(posts.map { LobstersEntity(it) })
|
||||||
|
|
|
@ -4,12 +4,14 @@ import androidx.room.Database
|
||||||
import androidx.room.RoomDatabase
|
import androidx.room.RoomDatabase
|
||||||
import androidx.room.TypeConverters
|
import androidx.room.TypeConverters
|
||||||
import dev.msfjarvis.lobsters.data.model.LobstersEntity
|
import dev.msfjarvis.lobsters.data.model.LobstersEntity
|
||||||
|
import dev.msfjarvis.lobsters.data.model.SavedLobstersEntity
|
||||||
|
|
||||||
@Database(
|
@Database(
|
||||||
entities = [
|
entities = [
|
||||||
LobstersEntity::class,
|
LobstersEntity::class,
|
||||||
|
SavedLobstersEntity::class
|
||||||
],
|
],
|
||||||
version = 1,
|
version = 2,
|
||||||
exportSchema = false,
|
exportSchema = false,
|
||||||
)
|
)
|
||||||
@TypeConverters(
|
@TypeConverters(
|
||||||
|
@ -17,4 +19,5 @@ import dev.msfjarvis.lobsters.data.model.LobstersEntity
|
||||||
)
|
)
|
||||||
abstract class PostsDatabase : RoomDatabase() {
|
abstract class PostsDatabase : RoomDatabase() {
|
||||||
abstract fun postsDao(): PostsDao
|
abstract fun postsDao(): PostsDao
|
||||||
|
abstract fun savedPostsDao(): SavedPostsDao
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
package dev.msfjarvis.lobsters.data.source
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Delete
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Transaction
|
||||||
|
import dev.msfjarvis.lobsters.data.model.SavedLobstersEntity
|
||||||
|
import dev.msfjarvis.lobsters.model.LobstersPost
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
abstract class SavedPostsDao {
|
||||||
|
@Query("SELECT * FROM lobsters_saved_posts")
|
||||||
|
abstract fun loadPosts(): Flow<List<LobstersPost>>
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
open suspend fun insertPosts(vararg posts: LobstersPost) {
|
||||||
|
insertPosts(posts.map { SavedLobstersEntity(it) })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||||
|
protected abstract suspend fun insertPosts(posts: List<SavedLobstersEntity>)
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
open suspend fun deletePosts(vararg posts: LobstersPost) {
|
||||||
|
deletePosts(posts.map { SavedLobstersEntity(it) })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
protected abstract suspend fun deletePosts(posts: List<SavedLobstersEntity>)
|
||||||
|
|
||||||
|
@Query("DELETE FROM lobsters_saved_posts")
|
||||||
|
abstract suspend fun deleteAllPosts()
|
||||||
|
|
||||||
|
@Query("DELETE FROM lobsters_saved_posts WHERE shortId LIKE :shortId")
|
||||||
|
abstract suspend fun deletePostById(shortId: String)
|
||||||
|
|
||||||
|
@Query("SELECT EXISTS(SELECT 1 FROM lobsters_saved_posts WHERE shortId LIKE :shortId)")
|
||||||
|
abstract suspend fun isLiked(shortId: String): Boolean
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ package dev.msfjarvis.lobsters.api
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
object TestUtils {
|
object TestUtils {
|
||||||
fun getJson(path : String) : String {
|
fun getJson(path: String): String {
|
||||||
// Load the JSON response
|
// Load the JSON response
|
||||||
val uri = javaClass.classLoader.getResource(path)
|
val uri = javaClass.classLoader.getResource(path)
|
||||||
val file = File(uri.path)
|
val file = File(uri.path)
|
||||||
|
|
|
@ -22,5 +22,6 @@ class LobstersPost(
|
||||||
val commentsUrl: String,
|
val commentsUrl: String,
|
||||||
@Json(name = "submitter_user")
|
@Json(name = "submitter_user")
|
||||||
val submitterUser: Submitter,
|
val submitterUser: Submitter,
|
||||||
val tags: List<String>
|
val tags: List<String>,
|
||||||
|
var isLiked: Boolean = false,
|
||||||
)
|
)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue