diff --git a/android/build.gradle.kts b/android/build.gradle.kts index b9800336..2526dd68 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -23,7 +23,8 @@ dependencies { implementation(libs.androidx.paging.compose) implementation(libs.dagger.hilt.android) implementation(libs.sqldelight.extensions.coroutines) - implementation(libs.retrofit.moshiConverter) + implementation(libs.kotlinx.serialization.json) + implementation(libs.retrofit.kotlinxSerializationConverter) { isTransitive = false } } android { diff --git a/android/proguard-rules.pro b/android/proguard-rules.pro index e4e137ad..ca808ee4 100644 --- a/android/proguard-rules.pro +++ b/android/proguard-rules.pro @@ -12,3 +12,21 @@ -keep,allowobfuscation,allowshrinking class dev.msfjarvis.claw.android.** { *; } -dontobfuscate + +-keepattributes *Annotation*, InnerClasses +-dontnote kotlinx.serialization.AnnotationsKt + +-keepclassmembers class kotlinx.serialization.json.** { + *** Companion; +} +-keepclasseswithmembers class kotlinx.serialization.json.** { + kotlinx.serialization.KSerializer serializer(...); +} + +-keep,includedescriptorclasses class dev.msfjarvis.claw.model.**$$serializer { *; } +-keepclassmembers class dev.msfjarvis.claw.model.** { + *** Companion; +} +-keepclasseswithmembers class dev.msfjarvis.claw.model.** { + kotlinx.serialization.KSerializer serializer(...); +} diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/ext/database.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/ext/database.kt index 89cac5f5..9835f896 100644 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/ext/database.kt +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/ext/database.kt @@ -1,7 +1,7 @@ package dev.msfjarvis.claw.android.ext -import dev.msfjarvis.claw.api.model.LobstersPost import dev.msfjarvis.claw.database.local.SavedPost +import dev.msfjarvis.claw.model.LobstersPost /** Convert a [LobstersPost] object returned by the API into a [SavedPost] for persistence. */ fun LobstersPost.toDbModel(): SavedPost { diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/injection/ApiModule.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/injection/ApiModule.kt index 9bf9ab69..6e243853 100644 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/injection/ApiModule.kt +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/injection/ApiModule.kt @@ -1,18 +1,21 @@ package dev.msfjarvis.claw.android.injection import android.util.Log -import com.squareup.moshi.Moshi +import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory import dagger.Lazy import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import dev.msfjarvis.claw.api.LobstersApi +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.json.Json +import okhttp3.MediaType import okhttp3.OkHttpClient import retrofit2.Retrofit -import retrofit2.converter.moshi.MoshiConverterFactory import retrofit2.create +@OptIn(ExperimentalSerializationApi::class) @Module @InstallIn(SingletonComponent::class) object ApiModule { @@ -35,12 +38,12 @@ object ApiModule { @Provides fun provideRetrofit( client: Lazy, - moshi: Lazy, ): Retrofit { + val contentType = MediaType.get("application/json") return Retrofit.Builder() .client(client.get()) .baseUrl(LobstersApi.BASE_URL) - .addConverterFactory(MoshiConverterFactory.create(moshi.get())) + .addConverterFactory(Json.asConverterFactory(contentType)) .build() } diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/injection/MoshiModule.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/injection/MoshiModule.kt deleted file mode 100644 index 1de1f6d4..00000000 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/injection/MoshiModule.kt +++ /dev/null @@ -1,18 +0,0 @@ -package dev.msfjarvis.claw.android.injection - -import com.squareup.moshi.Moshi -import dagger.Module -import dagger.Provides -import dagger.Reusable -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent - -@Module -@InstallIn(SingletonComponent::class) -object MoshiModule { - @Provides - @Reusable - fun provideMoshi(): Moshi { - return Moshi.Builder().build() - } -} diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/paging/LobstersPagingSource.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/paging/LobstersPagingSource.kt index 0b29b16e..f6e3ac1d 100644 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/paging/LobstersPagingSource.kt +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/paging/LobstersPagingSource.kt @@ -2,7 +2,7 @@ package dev.msfjarvis.claw.android.paging import androidx.paging.PagingSource import androidx.paging.PagingState -import dev.msfjarvis.claw.api.model.LobstersPost +import dev.msfjarvis.claw.model.LobstersPost import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/NetworkPosts.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/NetworkPosts.kt index 75edc5a2..8c3e7143 100644 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/NetworkPosts.kt +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/NetworkPosts.kt @@ -14,9 +14,9 @@ import androidx.compose.ui.unit.dp import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.items import dev.msfjarvis.claw.android.ext.toDbModel -import dev.msfjarvis.claw.api.model.LobstersPost import dev.msfjarvis.claw.common.posts.LobstersCard import dev.msfjarvis.claw.database.local.SavedPost +import dev.msfjarvis.claw.model.LobstersPost import kotlinx.coroutines.launch @Composable diff --git a/api/build.gradle.kts b/api/build.gradle.kts index c9cd43cd..89dce9aa 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -1,14 +1,12 @@ -plugins { - kotlin("jvm") - id("com.google.devtools.ksp") version "1.5.31-1.0.0" -} +plugins { kotlin("jvm") } dependencies { + api(projects.model) api(libs.retrofit.lib) - ksp(libs.moshix.ksp) - implementation(libs.moshi.lib) - implementation(libs.retrofit.moshiConverter) { exclude(group = "com.squareup.moshi") } + implementation(libs.kotlinx.serialization.core) + testImplementation(libs.kotlinx.serialization.json) testImplementation(libs.kotlin.coroutines.core) testImplementation(kotlin("test-junit")) + testImplementation(libs.retrofit.kotlinxSerializationConverter) { isTransitive = false } testImplementation(libs.testing.mockWebServer) } diff --git a/api/src/main/kotlin/dev/msfjarvis/claw/api/LobstersApi.kt b/api/src/main/kotlin/dev/msfjarvis/claw/api/LobstersApi.kt index c0a79375..7dddda45 100644 --- a/api/src/main/kotlin/dev/msfjarvis/claw/api/LobstersApi.kt +++ b/api/src/main/kotlin/dev/msfjarvis/claw/api/LobstersApi.kt @@ -1,7 +1,7 @@ package dev.msfjarvis.claw.api -import dev.msfjarvis.claw.api.model.LobstersPost -import dev.msfjarvis.claw.api.model.LobstersPostDetails +import dev.msfjarvis.claw.model.LobstersPost +import dev.msfjarvis.claw.model.LobstersPostDetails import retrofit2.http.GET import retrofit2.http.Path import retrofit2.http.Query diff --git a/api/src/main/kotlin/dev/msfjarvis/claw/api/model/Comment.kt b/api/src/main/kotlin/dev/msfjarvis/claw/api/model/Comment.kt deleted file mode 100644 index 30634a68..00000000 --- a/api/src/main/kotlin/dev/msfjarvis/claw/api/model/Comment.kt +++ /dev/null @@ -1,20 +0,0 @@ -package dev.msfjarvis.claw.api.model - -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass - -@JsonClass(generateAdapter = true) -class Comment( - @Json(name = "short_id") val shortId: String, - @Json(name = "short_id_url") val shortIdUrl: String, - @Json(name = "created_at") val createdAt: String, - @Json(name = "updated_at") val updatedAt: String, - @Json(name = "is_deleted") val isDeleted: Boolean, - @Json(name = "is_moderated") val isModerated: Boolean, - val score: Long, - val flags: Long, - val comment: String, - val url: String, - @Json(name = "indent_level") val indentLevel: Long, - @Json(name = "commenting_user") val user: User, -) diff --git a/api/src/main/kotlin/dev/msfjarvis/claw/api/model/KeybaseSignature.kt b/api/src/main/kotlin/dev/msfjarvis/claw/api/model/KeybaseSignature.kt deleted file mode 100644 index 6792b3a1..00000000 --- a/api/src/main/kotlin/dev/msfjarvis/claw/api/model/KeybaseSignature.kt +++ /dev/null @@ -1,10 +0,0 @@ -package dev.msfjarvis.claw.api.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, -) diff --git a/api/src/main/kotlin/dev/msfjarvis/claw/api/model/LobstersPost.kt b/api/src/main/kotlin/dev/msfjarvis/claw/api/model/LobstersPost.kt deleted file mode 100644 index a6ce0ae9..00000000 --- a/api/src/main/kotlin/dev/msfjarvis/claw/api/model/LobstersPost.kt +++ /dev/null @@ -1,20 +0,0 @@ -package dev.msfjarvis.claw.api.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 submitter: User, - val tags: List, -) diff --git a/api/src/main/kotlin/dev/msfjarvis/claw/api/model/LobstersPostDetails.kt b/api/src/main/kotlin/dev/msfjarvis/claw/api/model/LobstersPostDetails.kt deleted file mode 100644 index 97b1ce11..00000000 --- a/api/src/main/kotlin/dev/msfjarvis/claw/api/model/LobstersPostDetails.kt +++ /dev/null @@ -1,21 +0,0 @@ -package dev.msfjarvis.claw.api.model - -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass - -@JsonClass(generateAdapter = true) -class LobstersPostDetails( - @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 submitter: User, - val tags: List, - val comments: List, -) diff --git a/api/src/main/kotlin/dev/msfjarvis/claw/api/model/User.kt b/api/src/main/kotlin/dev/msfjarvis/claw/api/model/User.kt deleted file mode 100644 index 1ecd6bc5..00000000 --- a/api/src/main/kotlin/dev/msfjarvis/claw/api/model/User.kt +++ /dev/null @@ -1,19 +0,0 @@ -package dev.msfjarvis.claw.api.model - -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass - -@JsonClass(generateAdapter = true) -class User( - val username: String, - @Json(name = "created_at") val createdAt: String, - @Json(name = "is_admin") val isAdmin: Boolean, - val about: String, - @Json(name = "is_moderator") val isModerator: Boolean, - val karma: Long = 0, - @Json(name = "avatar_url") val avatarUrl: String, - @Json(name = "invited_by_user") val invitedByUser: String, - @Json(name = "github_username") val githubUsername: String? = null, - @Json(name = "twitter_username") val twitterUsername: String? = null, - @Json(name = "keybase_signatures") val keybaseSignatures: List = emptyList(), -) diff --git a/api/src/test/kotlin/dev/msfjarvis/claw/api/LobstersApiTest.kt b/api/src/test/kotlin/dev/msfjarvis/claw/api/LobstersApiTest.kt index c3b35fa2..5fba4628 100644 --- a/api/src/test/kotlin/dev/msfjarvis/claw/api/LobstersApiTest.kt +++ b/api/src/test/kotlin/dev/msfjarvis/claw/api/LobstersApiTest.kt @@ -1,34 +1,37 @@ package dev.msfjarvis.claw.api -import com.squareup.moshi.Moshi +import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory import dev.msfjarvis.claw.util.TestUtils import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue import kotlin.test.fail import kotlinx.coroutines.runBlocking +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.json.Json import mockwebserver3.Dispatcher import mockwebserver3.MockResponse import mockwebserver3.MockWebServer import mockwebserver3.RecordedRequest +import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import org.junit.AfterClass import org.junit.BeforeClass import retrofit2.Retrofit -import retrofit2.converter.moshi.MoshiConverterFactory import retrofit2.create +@OptIn(ExperimentalSerializationApi::class) class LobstersApiTest { companion object { + private val contentType = "application/json".toMediaType() private val webServer = MockWebServer() - private val moshi = Moshi.Builder().build() private val okHttp = OkHttpClient.Builder().build() private val retrofit = Retrofit.Builder() .client(okHttp) .baseUrl("http://localhost:8080/") - .addConverterFactory(MoshiConverterFactory.create(moshi)) + .addConverterFactory(Json.asConverterFactory(contentType)) .build() private val apiClient = retrofit.create() diff --git a/build.gradle.kts b/build.gradle.kts index 6724a91b..8caa6771 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile buildscript { + val kotlinVersion = "1.5.31" repositories { google() mavenCentral() @@ -12,7 +13,8 @@ buildscript { } dependencies { classpath("com.android.tools:r8:3.1.17-dev") - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31") + classpath(kotlin("gradle-plugin", version = kotlinVersion)) + classpath(kotlin("serialization", version = kotlinVersion)) classpath("com.android.tools.build:gradle:7.1.0-alpha12") classpath("com.diffplug.spotless:spotless-plugin-gradle:5.15.0") classpath("com.google.dagger:hilt-android-gradle-plugin:2.39") diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 12099441..096a33e6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,8 +3,7 @@ accompanist = "0.18.0" aurora = "0.0.54-SNAPSHOT" coroutines = "1.5.2" hilt = "2.39" -moshix = "0.14.1" -retrofit = "2.9.0" +serialization = "1.3.0" sqldelight = "1.5.1" [libraries] @@ -12,6 +11,8 @@ sqldelight = "1.5.1" kotlin-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" } kotlin-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" } kotlin-coroutines-jvm = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm", version.ref = "coroutines" } +kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "serialization" } +kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization" } accompanist-insets = { module = "com.google.accompanist:accompanist-insets", version.ref = "accompanist" } accompanist-swiperefresh = { module = "com.google.accompanist:accompanist-swiperefresh", version.ref = "accompanist" } @@ -35,11 +36,8 @@ dagger-hilt-android = { module = "com.google.dagger:hilt-android", version.ref = dagger-hilt-compiler = { module = "com.google.dagger:hilt-compiler", version.ref = "hilt" } dagger-hilt-core = { module = "com.google.dagger:hilt-core", version.ref = "hilt" } -moshi-lib = "com.squareup.moshi:moshi:1.12.0" -moshix-ksp = { module = "dev.zacsweers.moshix:moshi-ksp", version.ref = "moshix" } - -retrofit-lib = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" } -retrofit-moshiConverter = { module = "com.squareup.retrofit2:converter-moshi", version.ref = "retrofit" } +retrofit-lib = "com.squareup.retrofit2:retrofit:2.9.0" +retrofit-kotlinxSerializationConverter = "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.8.0" sqldelight-jvmDriver = { module = "com.squareup.sqldelight:sqlite-driver", version.ref = "sqldelight" } sqldelight-androidDriver = { module = "com.squareup.sqldelight:android-driver", version.ref = "sqldelight" } diff --git a/model/build.gradle.kts b/model/build.gradle.kts new file mode 100644 index 00000000..69539118 --- /dev/null +++ b/model/build.gradle.kts @@ -0,0 +1,11 @@ +plugins { + kotlin("multiplatform") + kotlin("plugin.serialization") +} + +kotlin { + jvm { compilations.all { kotlinOptions.jvmTarget = "11" } } + sourceSets["commonMain"].apply { + dependencies { implementation(libs.kotlinx.serialization.core) } + } +} diff --git a/model/src/commonMain/kotlin/dev/msfjarvis/claw/model/Comment.kt b/model/src/commonMain/kotlin/dev/msfjarvis/claw/model/Comment.kt new file mode 100644 index 00000000..7ddb56bb --- /dev/null +++ b/model/src/commonMain/kotlin/dev/msfjarvis/claw/model/Comment.kt @@ -0,0 +1,20 @@ +package dev.msfjarvis.claw.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +class Comment( + @SerialName("short_id") val shortId: String, + @SerialName("short_id_url") val shortIdUrl: String, + @SerialName("created_at") val createdAt: String, + @SerialName("updated_at") val updatedAt: String, + @SerialName("is_deleted") val isDeleted: Boolean, + @SerialName("is_moderated") val isModerated: Boolean, + val score: Long, + val flags: Long, + val comment: String, + val url: String, + @SerialName("indent_level") val indentLevel: Long, + @SerialName("commenting_user") val user: User, +) diff --git a/model/src/commonMain/kotlin/dev/msfjarvis/claw/model/KeybaseSignature.kt b/model/src/commonMain/kotlin/dev/msfjarvis/claw/model/KeybaseSignature.kt new file mode 100644 index 00000000..c0425d02 --- /dev/null +++ b/model/src/commonMain/kotlin/dev/msfjarvis/claw/model/KeybaseSignature.kt @@ -0,0 +1,10 @@ +package dev.msfjarvis.claw.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +class KeybaseSignature( + @SerialName("kb_username") val kbUsername: String, + @SerialName("sig_hash") val sigHash: String, +) diff --git a/model/src/commonMain/kotlin/dev/msfjarvis/claw/model/LobstersPost.kt b/model/src/commonMain/kotlin/dev/msfjarvis/claw/model/LobstersPost.kt new file mode 100644 index 00000000..75b3d3aa --- /dev/null +++ b/model/src/commonMain/kotlin/dev/msfjarvis/claw/model/LobstersPost.kt @@ -0,0 +1,20 @@ +package dev.msfjarvis.claw.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +class LobstersPost( + @SerialName("short_id") val shortId: String, + @SerialName("short_id_url") val shortIdUrl: String, + @SerialName("created_at") val createdAt: String, + val title: String, + val url: String, + val score: Long, + val flags: Long, + @SerialName("comment_count") val commentCount: Long, + val description: String, + @SerialName("comments_url") val commentsUrl: String, + @SerialName("submitter_user") val submitter: User, + val tags: List, +) diff --git a/model/src/commonMain/kotlin/dev/msfjarvis/claw/model/LobstersPostDetails.kt b/model/src/commonMain/kotlin/dev/msfjarvis/claw/model/LobstersPostDetails.kt new file mode 100644 index 00000000..b727001b --- /dev/null +++ b/model/src/commonMain/kotlin/dev/msfjarvis/claw/model/LobstersPostDetails.kt @@ -0,0 +1,21 @@ +package dev.msfjarvis.claw.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +class LobstersPostDetails( + @SerialName("short_id") val shortId: String, + @SerialName("short_id_url") val shortIdUrl: String, + @SerialName("created_at") val createdAt: String, + val title: String, + val url: String, + val score: Long, + val flags: Long, + @SerialName("comment_count") val commentCount: Long, + val description: String, + @SerialName("comments_url") val commentsUrl: String, + @SerialName("submitter_user") val submitter: User, + val tags: List, + val comments: List, +) diff --git a/model/src/commonMain/kotlin/dev/msfjarvis/claw/model/User.kt b/model/src/commonMain/kotlin/dev/msfjarvis/claw/model/User.kt new file mode 100644 index 00000000..6ec4a3b4 --- /dev/null +++ b/model/src/commonMain/kotlin/dev/msfjarvis/claw/model/User.kt @@ -0,0 +1,19 @@ +package dev.msfjarvis.claw.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +class User( + val username: String, + @SerialName("created_at") val createdAt: String, + @SerialName("is_admin") val isAdmin: Boolean, + val about: String, + @SerialName("is_moderator") val isModerator: Boolean, + val karma: Long = 0, + @SerialName("avatar_url") val avatarUrl: String, + @SerialName("invited_by_user") val invitedByUser: String, + @SerialName("github_username") val githubUsername: String? = null, + @SerialName("twitter_username") val twitterUsername: String? = null, + @SerialName("keybase_signatures") val keybaseSignatures: List = emptyList(), +) diff --git a/settings.gradle.kts b/settings.gradle.kts index b4c99f9f..4d684028 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -34,3 +34,5 @@ include(":common") include(":database") include(":desktop") + +include(":model")