diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d165467b..2aff4fe8 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -44,6 +44,7 @@ dependencies { implementation(Dependencies.Kotlin.Coroutines.android) implementation(Dependencies.ThirdParty.accompanist) implementation(Dependencies.ThirdParty.Moshi.lib) + implementation(Dependencies.ThirdParty.SQLDelight.androidDriver) testImplementation(Dependencies.Testing.junit) androidTestImplementation(Dependencies.Testing.daggerHilt) androidTestImplementation(Dependencies.Testing.uiTest) diff --git a/app/src/main/java/dev/msfjarvis/lobsters/data/remote/LobstersPagingSource.kt b/app/src/main/java/dev/msfjarvis/lobsters/data/remote/LobstersPagingSource.kt index 77b48b65..851bb167 100644 --- a/app/src/main/java/dev/msfjarvis/lobsters/data/remote/LobstersPagingSource.kt +++ b/app/src/main/java/dev/msfjarvis/lobsters/data/remote/LobstersPagingSource.kt @@ -3,16 +3,21 @@ package dev.msfjarvis.lobsters.data.remote import androidx.paging.PagingSource import dev.msfjarvis.lobsters.data.api.LobstersApi import dev.msfjarvis.lobsters.data.local.LobstersPost +import dev.msfjarvis.lobsters.data.repo.LobstersRepository import javax.inject.Inject class LobstersPagingSource @Inject constructor( private val lobstersApi: LobstersApi, + private val lobstersRepository: LobstersRepository, ) : PagingSource() { override suspend fun load(params: LoadParams): LoadResult { return try { val page = params.key ?: 1 - val posts = lobstersApi.getHottestPosts(page) + val savedPosts = lobstersRepository.getCachedPosts() + val posts = lobstersApi.getHottestPosts(page).map { post -> + post.copy(is_saved = savedPosts.contains(post.short_id)) + } LoadResult.Page( data = posts, diff --git a/app/src/main/java/dev/msfjarvis/lobsters/data/repo/LobstersRepository.kt b/app/src/main/java/dev/msfjarvis/lobsters/data/repo/LobstersRepository.kt new file mode 100644 index 00000000..28c70634 --- /dev/null +++ b/app/src/main/java/dev/msfjarvis/lobsters/data/repo/LobstersRepository.kt @@ -0,0 +1,39 @@ +package dev.msfjarvis.lobsters.data.repo + +import dev.msfjarvis.lobsters.data.local.LobstersPost +import dev.msfjarvis.lobsters.database.LobstersDatabase +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import javax.inject.Inject + +class LobstersRepository @Inject constructor(private val lobstersDatabase: LobstersDatabase) { + + private var savedPostCache: MutableSet? = null + + fun isPostSaved(postId: String): Boolean { + savedPostCache ?: return false + return requireNotNull(savedPostCache).contains(postId) + } + + suspend fun savePost(post: LobstersPost) = withContext(Dispatchers.IO) { + val isElementAdded = getCachedPosts().add(post.short_id) + if (isElementAdded) { + lobstersDatabase.postQueries.savePost(post.short_id) + } + } + + suspend fun removeSavedPost(post: LobstersPost) = withContext(Dispatchers.IO) { + val isElementRemoved = getCachedPosts().remove(post.short_id) + if (isElementRemoved) { + lobstersDatabase.postQueries.removeSavedPost(post.short_id) + } + } + + suspend fun getCachedPosts(): MutableSet = withContext(Dispatchers.IO) { + if (savedPostCache != null) return@withContext requireNotNull(savedPostCache) + + val dbPosts = lobstersDatabase.postQueries.selectSavedPosts().executeAsList() + savedPostCache = dbPosts.filter { it.is_saved ?: false }.map { it.short_id }.toMutableSet() + return@withContext requireNotNull(savedPostCache) + } +} diff --git a/app/src/main/java/dev/msfjarvis/lobsters/injection/DatabaseModule.kt b/app/src/main/java/dev/msfjarvis/lobsters/injection/DatabaseModule.kt new file mode 100644 index 00000000..bd22e251 --- /dev/null +++ b/app/src/main/java/dev/msfjarvis/lobsters/injection/DatabaseModule.kt @@ -0,0 +1,29 @@ +package dev.msfjarvis.lobsters.injection + +import android.content.Context +import com.squareup.sqldelight.android.AndroidSqliteDriver +import com.squareup.sqldelight.db.SqlDriver +import dagger.Module +import dagger.Provides +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.database.LobstersDatabase +import dev.msfjarvis.lobsters.model.SubmitterAdapter +import dev.msfjarvis.lobsters.model.TagsAdapter + +@Module +@InstallIn(SingletonComponent::class) +object DatabaseModule { + + @Provides + fun providesSqlDriver(@ApplicationContext context: Context): SqlDriver { + return AndroidSqliteDriver(LobstersDatabase.Schema, context) + } + + @Provides + fun providesLobstersDatabase(sqlDriver: SqlDriver): LobstersDatabase { + return LobstersDatabase(sqlDriver, LobstersPost.Adapter(SubmitterAdapter(), TagsAdapter())) + } +} diff --git a/app/src/main/java/dev/msfjarvis/lobsters/ui/main/MainActivity.kt b/app/src/main/java/dev/msfjarvis/lobsters/ui/main/MainActivity.kt index 3f5ab58f..ec54d98f 100644 --- a/app/src/main/java/dev/msfjarvis/lobsters/ui/main/MainActivity.kt +++ b/app/src/main/java/dev/msfjarvis/lobsters/ui/main/MainActivity.kt @@ -78,14 +78,14 @@ fun LobstersApp() { HottestPosts( posts = hottestPosts, listState = hottestPostsListState, - saveAction = viewModel::savePost, + saveAction = viewModel::toggleSave, modifier = Modifier.padding(bottom = innerPadding.bottom), ) } composable(Destination.Saved.route) { SavedPosts( posts = savedPosts, - saveAction = viewModel::removeSavedPost, + saveAction = viewModel::toggleSave, modifier = Modifier.padding(bottom = innerPadding.bottom), ) } diff --git a/app/src/main/java/dev/msfjarvis/lobsters/ui/posts/LobstersItem.kt b/app/src/main/java/dev/msfjarvis/lobsters/ui/posts/LobstersItem.kt index 5734a430..85a84c8d 100644 --- a/app/src/main/java/dev/msfjarvis/lobsters/ui/posts/LobstersItem.kt +++ b/app/src/main/java/dev/msfjarvis/lobsters/ui/posts/LobstersItem.kt @@ -121,7 +121,7 @@ fun LobstersItem( }, ) IconResource( - resourceId = R.drawable.ic_favorite_border_24px, + resourceId = if (post.is_saved == true) R.drawable.ic_favorite_24px else R.drawable.ic_favorite_border_24px, modifier = Modifier .padding(8.dp) .clickable( diff --git a/app/src/main/java/dev/msfjarvis/lobsters/ui/viewmodel/LobstersViewModel.kt b/app/src/main/java/dev/msfjarvis/lobsters/ui/viewmodel/LobstersViewModel.kt index 89f3c4be..521f91a2 100644 --- a/app/src/main/java/dev/msfjarvis/lobsters/ui/viewmodel/LobstersViewModel.kt +++ b/app/src/main/java/dev/msfjarvis/lobsters/ui/viewmodel/LobstersViewModel.kt @@ -5,8 +5,9 @@ import androidx.lifecycle.viewModelScope import androidx.paging.Pager import androidx.paging.PagingConfig import dagger.hilt.android.lifecycle.HiltViewModel -import dev.msfjarvis.lobsters.data.remote.LobstersPagingSource import dev.msfjarvis.lobsters.data.local.LobstersPost +import dev.msfjarvis.lobsters.data.remote.LobstersPagingSource +import dev.msfjarvis.lobsters.data.repo.LobstersRepository import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch @@ -15,6 +16,7 @@ import javax.inject.Inject @HiltViewModel class LobstersViewModel @Inject constructor( private val pagingSource: LobstersPagingSource, + private val lobstersRepository: LobstersRepository, ) : ViewModel() { private val _savedPosts = MutableStateFlow>(emptyList()) val savedPosts = _savedPosts.asStateFlow() @@ -22,13 +24,24 @@ class LobstersViewModel @Inject constructor( pagingSource }.flow - fun savePost(post: LobstersPost) { + fun toggleSave(post: LobstersPost) { viewModelScope.launch { + val isSaved = lobstersRepository.isPostSaved(post.short_id) + if (isSaved) removeSavedPost(post) else savePost(post) } } - fun removeSavedPost(post: LobstersPost) { + private fun savePost(post: LobstersPost) { viewModelScope.launch { + lobstersRepository.savePost(post) + _savedPosts.value = _savedPosts.value + post + } + } + + private fun removeSavedPost(post: LobstersPost) { + viewModelScope.launch { + lobstersRepository.removeSavedPost(post) + _savedPosts.value = _savedPosts.value - post } } } diff --git a/database/src/main/sqldelight/dev/msfjarvis/lobsters/data/local/Post.sq b/database/src/main/sqldelight/dev/msfjarvis/lobsters/data/local/Post.sq index f64bb4cb..6e9577ca 100644 --- a/database/src/main/sqldelight/dev/msfjarvis/lobsters/data/local/Post.sq +++ b/database/src/main/sqldelight/dev/msfjarvis/lobsters/data/local/Post.sq @@ -22,6 +22,11 @@ selectAllPosts: SELECT * FROM LobstersPost; +selectSavedPosts: +SELECT * +FROM LobstersPost +WHERE is_saved = 1; + selectPost: SELECT * FROM LobstersPost