diff --git a/.github/ci-gradle.properties b/.github/ci-gradle.properties new file mode 100644 index 00000000..e32ec00b --- /dev/null +++ b/.github/ci-gradle.properties @@ -0,0 +1 @@ +org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index f2f7394e..e1068afe 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -10,12 +10,17 @@ jobs: test-pr: runs-on: macOS-latest steps: + - uses: actions/setup-java@d202f5dbf7256730fb690ec59f6381650114feb2 with: java-version: '11' + - name: Checkout repository uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f + - name: Copy CI gradle.properties + run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties + - uses: burrunan/gradle-cache-action@03c71a8ba93d670980695505f48f49daf43704a6 name: Run unit tests with: diff --git a/.idea/kotlinScripting.xml b/.idea/kotlinScripting.xml new file mode 100644 index 00000000..bc444dea --- /dev/null +++ b/.idea/kotlinScripting.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/api/build.gradle.kts b/api/build.gradle.kts index 831037f2..519bbbfa 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -1,12 +1,12 @@ plugins { kotlin("jvm") + id("com.google.devtools.ksp") version "1.4.30-1.0.0-alpha04" `lobsters-plugin` } dependencies { api(Dependencies.ThirdParty.Retrofit.lib) - implementation(project(":database")) - implementation(Dependencies.ThirdParty.Moshi.moshiMetadataReflect) + ksp(Dependencies.ThirdParty.Moshi.ksp) implementation(Dependencies.ThirdParty.Retrofit.moshi) testImplementation(Dependencies.Kotlin.Coroutines.core) testImplementation(Dependencies.Testing.junit) diff --git a/api/src/main/java/dev/msfjarvis/lobsters/data/api/LobstersApi.kt b/api/src/main/java/dev/msfjarvis/lobsters/data/api/LobstersApi.kt index 9e2f4cff..b924fb3f 100644 --- a/api/src/main/java/dev/msfjarvis/lobsters/data/api/LobstersApi.kt +++ b/api/src/main/java/dev/msfjarvis/lobsters/data/api/LobstersApi.kt @@ -1,6 +1,6 @@ package dev.msfjarvis.lobsters.data.api -import dev.msfjarvis.lobsters.data.local.LobstersPost +import dev.msfjarvis.lobsters.model.LobstersPost import retrofit2.http.GET import retrofit2.http.Query diff --git a/database/src/main/java/dev/msfjarvis/lobsters/model/KeybaseSignature.kt b/api/src/main/java/dev/msfjarvis/lobsters/model/KeybaseSignature.kt similarity index 65% rename from database/src/main/java/dev/msfjarvis/lobsters/model/KeybaseSignature.kt rename to api/src/main/java/dev/msfjarvis/lobsters/model/KeybaseSignature.kt index c1cc62ed..243bdff9 100644 --- a/database/src/main/java/dev/msfjarvis/lobsters/model/KeybaseSignature.kt +++ b/api/src/main/java/dev/msfjarvis/lobsters/model/KeybaseSignature.kt @@ -1,10 +1,12 @@ package dev.msfjarvis.lobsters.model import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +@JsonClass(generateAdapter = true) class KeybaseSignature( @Json(name = "kb_username") val kbUsername: String, @Json(name = "sig_hash") - val sigHash: String + val sigHash: String, ) diff --git a/api/src/main/java/dev/msfjarvis/lobsters/model/LobstersPost.kt b/api/src/main/java/dev/msfjarvis/lobsters/model/LobstersPost.kt new file mode 100644 index 00000000..f6d947bd --- /dev/null +++ b/api/src/main/java/dev/msfjarvis/lobsters/model/LobstersPost.kt @@ -0,0 +1,26 @@ +package dev.msfjarvis.lobsters.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +class LobstersPost( + @Json(name = "short_id") + val shortId: String, + @Json(name = "short_id_url") + val shortIdUrl: String, + @Json(name = "created_at") + val createdAt: String, + val title: String, + val url: String, + val score: Long, + val flags: Long, + @Json(name = "comment_count") + val commentCount: Long, + val description: String, + @Json(name = "comments_url") + val commentsUrl: String, + @Json(name = "submitter_user") + val submitterUser: Submitter, + val tags: List, +) diff --git a/database/src/main/java/dev/msfjarvis/lobsters/model/Submitter.kt b/api/src/main/java/dev/msfjarvis/lobsters/model/Submitter.kt similarity index 82% rename from database/src/main/java/dev/msfjarvis/lobsters/model/Submitter.kt rename to api/src/main/java/dev/msfjarvis/lobsters/model/Submitter.kt index e76e3de1..ea739b74 100644 --- a/database/src/main/java/dev/msfjarvis/lobsters/model/Submitter.kt +++ b/api/src/main/java/dev/msfjarvis/lobsters/model/Submitter.kt @@ -1,7 +1,9 @@ package dev.msfjarvis.lobsters.model import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +@JsonClass(generateAdapter = true) class Submitter( val username: String, @Json(name = "created_at") @@ -21,5 +23,5 @@ class Submitter( @Json(name = "twitter_username") val twitterUsername: String? = null, @Json(name = "keybase_signatures") - val keybaseSignatures: List = emptyList() + val keybaseSignatures: List = emptyList(), ) diff --git a/api/src/test/java/dev/msfjarvis/lobsters/data/api/LobstersApiTest.kt b/api/src/test/java/dev/msfjarvis/lobsters/data/api/LobstersApiTest.kt index 8ece55f3..81e23c5b 100644 --- a/api/src/test/java/dev/msfjarvis/lobsters/data/api/LobstersApiTest.kt +++ b/api/src/test/java/dev/msfjarvis/lobsters/data/api/LobstersApiTest.kt @@ -2,7 +2,6 @@ package dev.msfjarvis.lobsters.data.api import com.squareup.moshi.Moshi import dev.msfjarvis.lobsters.util.TestUtils -import dev.zacsweers.moshix.reflect.MetadataKotlinJsonAdapterFactory import kotlinx.coroutines.runBlocking import mockwebserver3.Dispatcher import mockwebserver3.MockResponse @@ -24,7 +23,6 @@ class LobstersApiTest { private val webServer = MockWebServer() private val apiData = TestUtils.getJson("hottest.json") private val moshi = Moshi.Builder() - .add(MetadataKotlinJsonAdapterFactory()) .build() private val okHttp = OkHttpClient.Builder() .build() @@ -63,7 +61,7 @@ class LobstersApiTest { fun `no moderator posts in test data`() = runBlocking { val posts = apiClient.getHottestPosts(1) val moderatorPosts = posts.asSequence() - .filter { it.submitter_user.isModerator } + .filter { it.submitterUser.isModerator } .toSet() assertTrue(moderatorPosts.isEmpty()) } diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 41aece73..21adc11f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -54,7 +54,6 @@ dependencies { implementation(Dependencies.Kotlin.Coroutines.android) implementation(Dependencies.ThirdParty.accompanist) implementation(Dependencies.ThirdParty.Moshi.lib) - implementation(Dependencies.ThirdParty.Moshi.moshiMetadataReflect) implementation(Dependencies.ThirdParty.Retrofit.moshi) implementation(Dependencies.ThirdParty.SQLDelight.androidDriver) testImplementation(Dependencies.Testing.junit) 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 f50b5c2e..766d4f88 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 @@ -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( 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 index 18e6f5c9..afba5a1e 100644 --- a/app/src/main/java/dev/msfjarvis/lobsters/data/repo/LobstersRepository.kt +++ b/app/src/main/java/dev/msfjarvis/lobsters/data/repo/LobstersRepository.kt @@ -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 = mutableMapOf() + private val savedPostsCache: MutableMap = mutableMapOf() private val _isCacheReady = MutableStateFlow(false) val isCacheReady = _isCacheReady.asStateFlow() @@ -21,7 +22,7 @@ class LobstersRepository constructor( return savedPostsCache.containsKey(postId) } - fun getAllPostsFromCache(): List { + fun getAllPostsFromCache(): List { 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 = withContext(Dispatchers.IO) { - return@withContext lobstersDatabase.postQueries.selectAllPosts().executeAsList() + private suspend fun getSavedPosts(): List = 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) } } } diff --git a/app/src/main/java/dev/msfjarvis/lobsters/injection/DatabaseModule.kt b/app/src/main/java/dev/msfjarvis/lobsters/injection/DatabaseModule.kt index b0f461be..68fd581c 100644 --- a/app/src/main/java/dev/msfjarvis/lobsters/injection/DatabaseModule.kt +++ b/app/src/main/java/dev/msfjarvis/lobsters/injection/DatabaseModule.kt @@ -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): 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), ) } } diff --git a/app/src/main/java/dev/msfjarvis/lobsters/injection/MoshiModule.kt b/app/src/main/java/dev/msfjarvis/lobsters/injection/MoshiModule.kt index 750ab673..3f0b22e7 100644 --- a/app/src/main/java/dev/msfjarvis/lobsters/injection/MoshiModule.kt +++ b/app/src/main/java/dev/msfjarvis/lobsters/injection/MoshiModule.kt @@ -1,15 +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 @InstallIn(SingletonComponent::class) @@ -18,14 +14,6 @@ object MoshiModule { @Reusable fun provideMoshi(): Moshi { return Moshi.Builder() - .add(MetadataKotlinJsonAdapterFactory()) .build() } - - @OptIn(ExperimentalStdlibApi::class) - @Provides - @Reusable - fun provideSubmitterJsonAdapter(moshi: Moshi): JsonAdapter { - return moshi.adapter() - } } diff --git a/app/src/main/java/dev/msfjarvis/lobsters/ui/posts/HottestPosts.kt b/app/src/main/java/dev/msfjarvis/lobsters/ui/posts/HottestPosts.kt index b5f16348..ae3371b4 100644 --- a/app/src/main/java/dev/msfjarvis/lobsters/ui/posts/HottestPosts.kt +++ b/app/src/main/java/dev/msfjarvis/lobsters/ui/posts/HottestPosts.kt @@ -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) 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 9f77715d..41f4d461 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 @@ -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), ) diff --git a/app/src/main/java/dev/msfjarvis/lobsters/ui/posts/SavedPosts.kt b/app/src/main/java/dev/msfjarvis/lobsters/ui/posts/SavedPosts.kt index d88df6a0..518658bc 100644 --- a/app/src/main/java/dev/msfjarvis/lobsters/ui/posts/SavedPosts.kt +++ b/app/src/main/java/dev/msfjarvis/lobsters/ui/posts/SavedPosts.kt @@ -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, + posts: List, 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) }, ) } 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 043006ce..c4e2fd4a 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 @@ -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>(emptyList()) + private val _savedPosts = MutableStateFlow>(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() diff --git a/app/src/main/java/dev/msfjarvis/lobsters/util/DatabaseExtensions.kt b/app/src/main/java/dev/msfjarvis/lobsters/util/DatabaseExtensions.kt new file mode 100644 index 00000000..13e85a29 --- /dev/null +++ b/app/src/main/java/dev/msfjarvis/lobsters/util/DatabaseExtensions.kt @@ -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, + ) +} diff --git a/build.gradle.kts b/build.gradle.kts index d3d9db38..ee3d8c2b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,12 @@ plugins { `lobsters-plugin` } + +subprojects { + configurations.configureEach { + resolutionStrategy { + // Retrofit depends on a very old version of Moshi that causes moshi-ksp to fail + force(Dependencies.ThirdParty.Moshi.lib) + } + } +} diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index d2127563..2057680f 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -71,7 +71,7 @@ object Dependencies { private const val version = "1.11.0" const val lib = "com.squareup.moshi:moshi:$version" - const val moshiMetadataReflect = "dev.zacsweers.moshix:moshi-metadata-reflect:0.9.1" + const val ksp = "dev.zacsweers.moshix:moshi-ksp:0.9.1" } object Retrofit { diff --git a/database/build.gradle.kts b/database/build.gradle.kts index 17e300c9..e4a4ed55 100644 --- a/database/build.gradle.kts +++ b/database/build.gradle.kts @@ -5,8 +5,6 @@ plugins { } dependencies { - implementation(Dependencies.ThirdParty.Moshi.lib) - implementation(Dependencies.ThirdParty.Moshi.moshiMetadataReflect) testImplementation(Dependencies.Kotlin.Coroutines.core) testImplementation(Dependencies.ThirdParty.SQLDelight.jvmDriver) testImplementation(Dependencies.Testing.junit) diff --git a/database/src/main/java/dev/msfjarvis/lobsters/model/SubmitterAdapter.kt b/database/src/main/java/dev/msfjarvis/lobsters/model/SubmitterAdapter.kt deleted file mode 100644 index d89d282a..00000000 --- a/database/src/main/java/dev/msfjarvis/lobsters/model/SubmitterAdapter.kt +++ /dev/null @@ -1,16 +0,0 @@ -package dev.msfjarvis.lobsters.model - -import com.squareup.moshi.JsonAdapter -import com.squareup.sqldelight.ColumnAdapter - -class SubmitterAdapter(private val submitterJsonAdapter: JsonAdapter) : - ColumnAdapter { - - override fun decode(databaseValue: String): Submitter { - return submitterJsonAdapter.fromJson(databaseValue)!! - } - - override fun encode(value: Submitter): String { - return submitterJsonAdapter.toJson(value) - } -} 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 deleted file mode 100644 index d2fe2f6f..00000000 --- a/database/src/main/sqldelight/dev/msfjarvis/lobsters/data/local/Post.sq +++ /dev/null @@ -1,45 +0,0 @@ -import dev.msfjarvis.lobsters.model.Submitter; -import java.lang.Boolean; -import kotlin.collections.List; - -CREATE TABLE IF NOT EXISTS LobstersPost( - short_id TEXT NOT NULL PRIMARY KEY, - short_id_url TEXT NOT NULL, - created_at TEXT NOT NULL, - title TEXT NOT NULL, - url TEXT NOT NULL, - score INTEGER NOT NULL, - flags INTEGER NOT NULL, - comment_count INTEGER NOT NULL, - description TEXT NOT NULL, - comments_url TEXT NOT NULL, - submitter_user TEXT as Submitter NOT NULL, - tags TEXT as List NOT NULL -); - -selectPost: -SELECT * -FROM LobstersPost -WHERE short_id = ?; - -selectAllPosts: -SELECT * -FROM LobstersPost; - -insertOrReplacePost: -INSERT OR REPLACE -INTO LobstersPost -VALUES ?; - -deletePost: -DELETE -FROM LobstersPost -WHERE short_id = ?; - -deleteAllPosts: -DELETE -FROM LobstersPost; - -selectCount: -SELECT COUNT(*) -FROM LobstersPost; diff --git a/database/src/main/sqldelight/dev/msfjarvis/lobsters/data/local/SavedPost.sq b/database/src/main/sqldelight/dev/msfjarvis/lobsters/data/local/SavedPost.sq new file mode 100644 index 00000000..1d88f39b --- /dev/null +++ b/database/src/main/sqldelight/dev/msfjarvis/lobsters/data/local/SavedPost.sq @@ -0,0 +1,39 @@ +import kotlin.collections.List; + +CREATE TABLE IF NOT EXISTS SavedPost( + shortId TEXT NOT NULL PRIMARY KEY, + title TEXT NOT NULL, + url TEXT NOT NULL, + createdAt TEXT NOT NULL, + commentsUrl TEXT NOT NULL, + submitterName TEXT NOT NULL, + submitterAvatarUrl TEXT NOT NULL, + tags TEXT as List NOT NULL +); + +insertOrReplacePost: +INSERT OR REPLACE +INTO SavedPost +VALUES ?; + +selectAllPosts: +SELECT * +FROM SavedPost; + +selectCount: +SELECT COUNT(*) +FROM SavedPost; + +deleteAllPosts: +DELETE +FROM SavedPost; + +deletePost: +DELETE +FROM SavedPost +WHERE shortId = ?; + +selectPost: +SELECT * +FROM SavedPost +WHERE shortId = ?; diff --git a/database/src/test/java/dev/msfjarvis/lobsters/data/local/SqlDelightQueriesTest.kt b/database/src/test/java/dev/msfjarvis/lobsters/data/local/SqlDelightQueriesTest.kt index 909e8f10..43daf226 100644 --- a/database/src/test/java/dev/msfjarvis/lobsters/data/local/SqlDelightQueriesTest.kt +++ b/database/src/test/java/dev/msfjarvis/lobsters/data/local/SqlDelightQueriesTest.kt @@ -1,13 +1,8 @@ package dev.msfjarvis.lobsters.data.local -import com.squareup.moshi.Moshi -import com.squareup.moshi.adapter import com.squareup.sqldelight.sqlite.driver.JdbcSqliteDriver 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 dev.zacsweers.moshix.reflect.MetadataKotlinJsonAdapterFactory import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals import org.junit.Before @@ -16,19 +11,17 @@ import org.junit.Test @OptIn(ExperimentalStdlibApi::class) class SqlDelightQueriesTest { - private lateinit var postQueries: PostQueries + private lateinit var postQueries: SavedPostQueries @Before fun setUp() { - val moshi = Moshi.Builder().add(MetadataKotlinJsonAdapterFactory()).build() - val submitterJsonAdapter = moshi.adapter() val driver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY) LobstersDatabase.Schema.create(driver) val database = LobstersDatabase( driver, - LobstersPost.Adapter(SubmitterAdapter(submitterJsonAdapter), TagsAdapter()) + SavedPost.Adapter(TagsAdapter()), ) - postQueries = database.postQueries + postQueries = database.savedPostQueries } @Test @@ -63,7 +56,7 @@ class SqlDelightQueriesTest { postQueries.insertOrReplacePost(post) // Create a new post and try replacing it - val newPost = post.copy(comment_count = 100) + val newPost = post.copy(submitterName = "Fake name") postQueries.insertOrReplacePost(newPost) // Check post count @@ -71,8 +64,8 @@ class SqlDelightQueriesTest { assertEquals(1, postsCount) // Check if post is updated - val postFromDb = postQueries.selectPost(post.short_id).executeAsOne() - assertEquals(100, postFromDb.comment_count) + val postFromDb = postQueries.selectPost(post.shortId).executeAsOne() + assertEquals("Fake name", postFromDb.submitterName) } @Test @@ -84,7 +77,7 @@ class SqlDelightQueriesTest { postQueries.insertOrReplacePost(post) val postFromDb = postQueries.selectAllPosts().executeAsOne() - assertEquals("test_id_1", postFromDb.short_id) + assertEquals("test_id_1", postFromDb.shortId) } @Test @@ -97,9 +90,9 @@ class SqlDelightQueriesTest { val postsFromDb = postQueries.selectAllPosts().executeAsList() - // Check if all posts have correct short_id + // Check if all posts have correct shortId for (i in 1..5) { - assertEquals("test_id_$i", postsFromDb[i - 1].short_id) + assertEquals("test_id_$i", postsFromDb[i - 1].shortId) } } @@ -116,8 +109,8 @@ class SqlDelightQueriesTest { // Check if size is 2, and only the correct post is deleted assertEquals(2, postsFromDB.size) - assertEquals("test_id_1", postsFromDB[0].short_id) - assertEquals("test_id_3", postsFromDB[1].short_id) + assertEquals("test_id_1", postsFromDB[0].shortId) + assertEquals("test_id_3", postsFromDB[1].shortId) } @Test @@ -136,32 +129,18 @@ class SqlDelightQueriesTest { } - private fun createTestData(count: Int): ArrayList { - val posts = arrayListOf() + private fun createTestData(count: Int): ArrayList { + val posts = arrayListOf() for (i in 1..count) { - val submitter = Submitter( - username = "test_user_$i", + val post = SavedPost( + shortId = "test_id_$i", createdAt = "0", - about = "test", - avatarUrl = "test_avatar_url", - invitedByUser = "test_user", - isAdmin = false, - isModerator = false - ) - - val post = LobstersPost( - short_id = "test_id_$i", - short_id_url = "test_id_url", - created_at = "0", title = "test", url = "test_url", - score = 0, - flags = 0, - comment_count = 0, - description = "test", - comments_url = "test_comments_url", - submitter_user = submitter, + commentsUrl = "test_comments_url", + submitterName = "test_user_$i", + submitterAvatarUrl = "test_avatar_url", tags = listOf(), ) diff --git a/settings.gradle.kts b/settings.gradle.kts index d35f9890..704d0a6e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,3 +1,9 @@ rootProject.name = "Claw" include(":app", ":api", ":database") enableFeaturePreview("GRADLE_METADATA") +pluginManagement { + repositories { + google() + gradlePluginPortal() + } +}