From dbd33df026fe861a447eb86c3f08342eadbe2edf Mon Sep 17 00:00:00 2001
From: Janne Mareike Koschinski <mail@justjanne.de>
Date: Wed, 30 Aug 2023 21:06:43 +0200
Subject: [PATCH] fix: serialization issues

---
 README.md                                     |  2 -
 app/build.gradle.kts                          |  4 +-
 app/proguard-rules.pro                        |  2 -
 app/sampledata/libraries.json                 |  9 ----
 .../quasseldroid/QuasseldroidGlideModule.kt   | 29 +++++++------
 .../kuschku/quasseldroid/dagger/AppModule.kt  | 17 +++-----
 .../quasseldroid/defaults/DefaultNetwork.kt   |  5 ++-
 .../defaults/DefaultNetworkServer.kt          |  5 ++-
 .../quasseldroid/defaults/DefaultNetworks.kt  | 11 +++--
 .../ui/clientsettings/about/AboutFragment.kt  |  5 ---
 .../ui/clientsettings/crash/CrashFragment.kt  | 15 +------
 .../quasseldroid/util/AndroidEmojiProvider.kt | 11 +++--
 .../util/avatars/MatrixAvatarInfo.kt          |  1 +
 .../util/avatars/MatrixAvatarResponse.kt      |  6 ++-
 .../util/avatars/MatrixModelLoader.kt         |  2 +-
 .../quasseldroid/util/embed/EmbedHelper.kt    | 35 ---------------
 .../quasseldroid/util/helper/GsonHelper.kt    | 43 -------------------
 .../quasseldroid/util/emoji/EmojiDataTest.kt  | 26 ++++-------
 build.gradle.kts                              |  1 +
 .../main/kotlin/KotlinAndroidConvention.kt    |  1 +
 .../src/main/kotlin/KotlinConvention.kt       |  1 +
 gradle/libs.versions.toml                     | 10 +++--
 malheur/build.gradle.kts                      |  2 +-
 malheur/proguard-rules.pro                    | 20 ---------
 .../java/de/kuschku/malheur/CrashHandler.kt   |  9 ++--
 ui_spinner/proguard-rules.pro                 | 20 ---------
 viewmodel/build.gradle.kts                    |  3 +-
 .../quasseldroid/util/emoji/EmojiHandler.kt   |  2 +
 28 files changed, 80 insertions(+), 217 deletions(-)
 delete mode 100644 app/src/main/java/de/kuschku/quasseldroid/util/embed/EmbedHelper.kt
 delete mode 100644 app/src/main/java/de/kuschku/quasseldroid/util/helper/GsonHelper.kt

diff --git a/README.md b/README.md
index 0b45d9fb4..50e67f570 100644
--- a/README.md
+++ b/README.md
@@ -69,8 +69,6 @@ Authors of legacy Quasseldroid:
   Apache-2.0
 * [**Glide**](https://bumptech.github.io/glide/)
   Apache-2.0
-* [**Gson**](https://github.com/google/gson)
-  Apache-2.0
 * [**JavaPoet**](https://github.com/square/javapoet)
   Apache-2.0
 * [**JetBrains Java Annotations**](https://github.com/JetBrains/java-annotations)
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index f05695102..d73edfeb8 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -89,11 +89,11 @@ dependencies {
     artifact { classifier = "no-tzdb" }
   }
   implementation(libs.annotations.jetbrains)
-  implementation(libs.gson)
   implementation(libs.commons.codec)
   implementation(libs.reactivenetwork)
   implementation(libs.retrofit.core)
-  implementation(libs.retrofit.converter.gson)
+  implementation(libs.retrofit.converter.kotlinx)
+  implementation(libs.kotlinx.serialization.json)
 
   // Quassel
   implementation(project(":viewmodel"))
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
index 88a5604d1..3f3b15881 100644
--- a/app/proguard-rules.pro
+++ b/app/proguard-rules.pro
@@ -41,8 +41,6 @@
 -dontnote org.apache.http.**
 # Kotlin stuff
 -dontnote kotlin.**
-# Gson
--dontnote com.google.gson.**
 # Dagger
 -dontwarn com.google.errorprone.annotations.*
 # Retrofit
diff --git a/app/sampledata/libraries.json b/app/sampledata/libraries.json
index 2a3b2c1fe..3c281ff69 100644
--- a/app/sampledata/libraries.json
+++ b/app/sampledata/libraries.json
@@ -60,15 +60,6 @@
       },
       "url": "https://bumptech.github.io/glide/"
     },
-    {
-      "name": "Gson",
-      "version": "2.8.2",
-      "license": {
-        "short_name": "Apache-2.0",
-        "full_name": "Apache License"
-      },
-      "url": "https://github.com/google/gson"
-    },
     {
       "name": "Gruvbox",
       "license": {
diff --git a/app/src/main/java/de/kuschku/quasseldroid/QuasseldroidGlideModule.kt b/app/src/main/java/de/kuschku/quasseldroid/QuasseldroidGlideModule.kt
index 3e5ef854b..da4eb4481 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/QuasseldroidGlideModule.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/QuasseldroidGlideModule.kt
@@ -29,12 +29,13 @@ import com.bumptech.glide.load.model.ModelLoader
 import com.bumptech.glide.load.model.ModelLoaderFactory
 import com.bumptech.glide.load.model.MultiModelLoaderFactory
 import com.bumptech.glide.module.AppGlideModule
-import com.google.gson.GsonBuilder
+import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
 import de.kuschku.quasseldroid.util.avatars.MatrixApi
 import de.kuschku.quasseldroid.util.avatars.MatrixModelLoader
 import de.kuschku.quasseldroid.viewmodel.data.Avatar
+import kotlinx.serialization.json.Json
+import okhttp3.MediaType
 import retrofit2.Retrofit
-import retrofit2.converter.gson.GsonConverterFactory
 import java.io.InputStream
 
 @GlideModule
@@ -43,22 +44,26 @@ class QuasseldroidGlideModule : AppGlideModule() {
     if (!BuildConfig.DEBUG) builder.setLogLevel(Log.ERROR)
   }
 
+  override fun isManifestParsingEnabled() = false
+
   override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
     val matrixApi = Retrofit.Builder()
       .baseUrl("https://matrix.org/")
-      .addConverterFactory(GsonConverterFactory.create(GsonBuilder().setLenient().create()))
+      .addConverterFactory(Json.asConverterFactory(MediaType.get("application/json")))
       .build()
       .create(MatrixApi::class.java)
 
-    registry.append(Avatar.MatrixAvatar::class.java,
-                    InputStream::class.java,
-                    object : ModelLoaderFactory<Avatar.MatrixAvatar, InputStream> {
-                      override fun build(
-                        multiFactory: MultiModelLoaderFactory): ModelLoader<Avatar.MatrixAvatar, InputStream> {
-                        return MatrixModelLoader(matrixApi)
-                      }
+    registry.append(
+      Avatar.MatrixAvatar::class.java,
+      InputStream::class.java,
+      object : ModelLoaderFactory<Avatar.MatrixAvatar, InputStream> {
+        override fun build(
+          multiFactory: MultiModelLoaderFactory
+        ): ModelLoader<Avatar.MatrixAvatar, InputStream> {
+          return MatrixModelLoader(matrixApi)
+        }
 
-                      override fun teardown() = Unit
-                    })
+        override fun teardown() = Unit
+      })
   }
 }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/dagger/AppModule.kt b/app/src/main/java/de/kuschku/quasseldroid/dagger/AppModule.kt
index 00d0edbbb..1741b19e0 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/dagger/AppModule.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/dagger/AppModule.kt
@@ -20,35 +20,30 @@
 package de.kuschku.quasseldroid.dagger
 
 import android.app.Application
-import android.content.Context
-import androidx.fragment.app.FragmentActivity
-import com.google.gson.Gson
-import com.google.gson.GsonBuilder
+import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
 import dagger.Module
 import dagger.Provides
 import de.kuschku.quasseldroid.Quasseldroid
 import de.kuschku.quasseldroid.util.AndroidEmojiProvider
 import de.kuschku.quasseldroid.util.avatars.MatrixApi
 import de.kuschku.quasseldroid.util.emoji.EmojiProvider
+import kotlinx.serialization.json.Json
+import okhttp3.MediaType
 import retrofit2.Retrofit
-import retrofit2.converter.gson.GsonConverterFactory
 
 @Module
 object AppModule {
   @Provides
   fun bindApplication(app: Quasseldroid): Application = app
 
-  @Provides
-  fun provideGson(): Gson = GsonBuilder().setPrettyPrinting().create()
-
   @Provides
   fun provideMatrixApi(): MatrixApi = Retrofit.Builder()
     .baseUrl("https://matrix.org/")
-    .addConverterFactory(GsonConverterFactory.create(GsonBuilder().setLenient().create()))
+    .addConverterFactory(Json.asConverterFactory(MediaType.get("application/json")))
     .build()
     .create(MatrixApi::class.java)
 
   @Provides
-  fun provideEmojiProvider(context: Application, gson: Gson): EmojiProvider =
-    AndroidEmojiProvider(context.applicationContext, gson)
+  fun provideEmojiProvider(context: Application): EmojiProvider =
+    AndroidEmojiProvider(context.applicationContext)
 }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/defaults/DefaultNetwork.kt b/app/src/main/java/de/kuschku/quasseldroid/defaults/DefaultNetwork.kt
index fcf364f79..e4f382c9a 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/defaults/DefaultNetwork.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/defaults/DefaultNetwork.kt
@@ -19,11 +19,12 @@
 
 package de.kuschku.quasseldroid.defaults
 
-import java.io.Serializable
+import kotlinx.serialization.Serializable
 
+@Serializable
 data class DefaultNetwork(
   val name: String,
   val default: Boolean = false,
   val defaultChannels: List<String> = emptyList(),
   val servers: List<DefaultNetworkServer>
-) : Serializable
+) : java.io.Serializable
diff --git a/app/src/main/java/de/kuschku/quasseldroid/defaults/DefaultNetworkServer.kt b/app/src/main/java/de/kuschku/quasseldroid/defaults/DefaultNetworkServer.kt
index cca659e8f..9cb3eb78d 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/defaults/DefaultNetworkServer.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/defaults/DefaultNetworkServer.kt
@@ -19,10 +19,11 @@
 
 package de.kuschku.quasseldroid.defaults
 
-import java.io.Serializable
+import kotlinx.serialization.Serializable
 
+@Serializable
 data class DefaultNetworkServer(
   val host: String,
   val port: UInt,
   val secure: Boolean
-) : Serializable
+) : java.io.Serializable
diff --git a/app/src/main/java/de/kuschku/quasseldroid/defaults/DefaultNetworks.kt b/app/src/main/java/de/kuschku/quasseldroid/defaults/DefaultNetworks.kt
index b0f559d82..97aafde0f 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/defaults/DefaultNetworks.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/defaults/DefaultNetworks.kt
@@ -20,16 +20,19 @@
 package de.kuschku.quasseldroid.defaults
 
 import android.content.Context
-import com.google.gson.Gson
-import de.kuschku.quasseldroid.util.helper.fromJsonList
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.json.DecodeSequenceMode
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.decodeToSequence
 import java.io.IOException
 import javax.inject.Inject
 
-class DefaultNetworks @Inject constructor(context: Context, gson: Gson) {
+@OptIn(ExperimentalSerializationApi::class)
+class DefaultNetworks @Inject constructor(context: Context) {
   val networks: List<DefaultNetwork> by lazy {
     try {
       context.assets.open("networks.json").use {
-        gson.fromJsonList(it.bufferedReader(Charsets.UTF_8))
+        Json.decodeToSequence<DefaultNetwork>(it, DecodeSequenceMode.ARRAY_WRAPPED).toList()
       }
     } catch (e: IOException) {
       throw IllegalStateException("networks.json missing from assets.", e)
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/about/AboutFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/about/AboutFragment.kt
index d8022265c..f92dd3098 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/about/AboutFragment.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/about/AboutFragment.kt
@@ -147,11 +147,6 @@ class AboutFragment : DaggerFragment() {
         license = apache2,
         url = "https://bumptech.github.io/glide/"
       ),
-      Library(
-        name = "Gson",
-        license = apache2,
-        url = "https://github.com/google/gson"
-      ),
       Library(
         name = "Gruvbox",
         license = License(
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/crash/CrashFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/crash/CrashFragment.kt
index 4e2d2af83..2f594bfa6 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/crash/CrashFragment.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/crash/CrashFragment.kt
@@ -30,17 +30,14 @@ import androidx.core.view.ViewCompat
 import androidx.recyclerview.widget.DividerItemDecoration
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
-import com.google.gson.Gson
-import com.google.gson.JsonSyntaxException
 import dagger.android.support.DaggerFragment
 import de.kuschku.malheur.CrashHandler
 import de.kuschku.malheur.data.Report
 import de.kuschku.quasseldroid.BuildConfig
 import de.kuschku.quasseldroid.R
-import de.kuschku.quasseldroid.util.helper.fromJson
 import de.kuschku.quasseldroid.util.helper.visibleIf
+import kotlinx.serialization.json.Json
 import java.io.File
-import javax.inject.Inject
 
 class CrashFragment : DaggerFragment() {
   lateinit var list: RecyclerView
@@ -49,9 +46,6 @@ class CrashFragment : DaggerFragment() {
   private lateinit var handlerThread: HandlerThread
   private lateinit var handler: Handler
 
-  @Inject
-  lateinit var gson: Gson
-
   private var crashDir: File? = null
   private var adapter: CrashAdapter? = null
 
@@ -69,7 +63,6 @@ class CrashFragment : DaggerFragment() {
 
   private fun reload() {
     val crashDir = this.crashDir
-    val gson = this.gson
     val context = this.context
 
     if (crashDir != null && context != null) {
@@ -78,11 +71,7 @@ class CrashFragment : DaggerFragment() {
         .orEmpty()
         .map {
           Pair<Report?, Uri>(
-            try {
-              gson.fromJson<Report>(it.readText())
-            } catch (e: JsonSyntaxException) {
-              null
-            },
+            Json.decodeFromString<Report>(it.readText()),
             FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.fileprovider", it)
           )
         }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/AndroidEmojiProvider.kt b/app/src/main/java/de/kuschku/quasseldroid/util/AndroidEmojiProvider.kt
index a7510c71a..818f301a1 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/util/AndroidEmojiProvider.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/AndroidEmojiProvider.kt
@@ -20,16 +20,19 @@
 package de.kuschku.quasseldroid.util
 
 import android.content.Context
-import com.google.gson.Gson
 import de.kuschku.quasseldroid.util.emoji.EmojiHandler
 import de.kuschku.quasseldroid.util.emoji.EmojiProvider
-import de.kuschku.quasseldroid.util.helper.fromJsonList
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.json.DecodeSequenceMode
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.decodeToSequence
 import java.io.IOException
 
-class AndroidEmojiProvider(context: Context, gson: Gson) : EmojiProvider {
+@OptIn(ExperimentalSerializationApi::class)
+class AndroidEmojiProvider(context: Context) : EmojiProvider {
   override val emoji: List<EmojiHandler.Emoji> = try {
     context.assets.open("emoji.json").use {
-      gson.fromJsonList(it.bufferedReader(Charsets.UTF_8))
+      Json.decodeToSequence<EmojiHandler.Emoji>(it, DecodeSequenceMode.ARRAY_WRAPPED).toList()
     }
   } catch (e: IOException) {
     throw IllegalStateException("emoji.json missing from assets.", e)
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/avatars/MatrixAvatarInfo.kt b/app/src/main/java/de/kuschku/quasseldroid/util/avatars/MatrixAvatarInfo.kt
index 7a3f173c3..50fc6d4a4 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/util/avatars/MatrixAvatarInfo.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/avatars/MatrixAvatarInfo.kt
@@ -19,6 +19,7 @@
 
 package de.kuschku.quasseldroid.util.avatars
 
+
 data class MatrixAvatarInfo(
   val avatarUrl: String,
   val size: Int?
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/avatars/MatrixAvatarResponse.kt b/app/src/main/java/de/kuschku/quasseldroid/util/avatars/MatrixAvatarResponse.kt
index 11cd0d2b3..badd4c342 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/util/avatars/MatrixAvatarResponse.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/avatars/MatrixAvatarResponse.kt
@@ -19,10 +19,12 @@
 
 package de.kuschku.quasseldroid.util.avatars
 
-import com.google.gson.annotations.SerializedName
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
 
+@Serializable
 data class MatrixAvatarResponse(
-  @SerializedName("avatar_url")
+  @SerialName("avatar_url")
   val avatarUrl: String?
 )
 
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/avatars/MatrixModelLoader.kt b/app/src/main/java/de/kuschku/quasseldroid/util/avatars/MatrixModelLoader.kt
index 3e8f45cb6..11a5ea873 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/util/avatars/MatrixModelLoader.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/avatars/MatrixModelLoader.kt
@@ -28,7 +28,7 @@ import java.io.InputStream
 class MatrixModelLoader(private val api: MatrixApi) :
   ModelLoader<Avatar.MatrixAvatar, InputStream> {
   override fun buildLoadData(model: Avatar.MatrixAvatar, width: Int, height: Int,
-                             options: Options): ModelLoader.LoadData<InputStream>? {
+                             options: Options): ModelLoader.LoadData<InputStream> {
     return ModelLoader.LoadData(ObjectKey(model), MatrixDataFetcher(model, api))
   }
 
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/embed/EmbedHelper.kt b/app/src/main/java/de/kuschku/quasseldroid/util/embed/EmbedHelper.kt
deleted file mode 100644
index f93a8e8d5..000000000
--- a/app/src/main/java/de/kuschku/quasseldroid/util/embed/EmbedHelper.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Quasseldroid - Quassel client for Android
- *
- * Copyright (c) 2020 Janne Mareike Koschinski
- * Copyright (c) 2020 The Quassel Project
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 3 as published
- * by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package de.kuschku.quasseldroid.util.embed
-
-import com.google.gson.GsonBuilder
-import io.reactivex.Observable
-import retrofit2.Retrofit
-import retrofit2.converter.gson.GsonConverterFactory
-
-class EmbedHelper(baseUrl: String) {
-  private val api = Retrofit.Builder()
-    .baseUrl(baseUrl)
-    .addConverterFactory(GsonConverterFactory.create(GsonBuilder().setLenient().create()))
-    .build()
-    .create(EmbedApi::class.java)
-
-  fun embedCode(url: String): Observable<EmbedResponse> = api.embedData(url)
-}
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/helper/GsonHelper.kt b/app/src/main/java/de/kuschku/quasseldroid/util/helper/GsonHelper.kt
deleted file mode 100644
index 6789a0c30..000000000
--- a/app/src/main/java/de/kuschku/quasseldroid/util/helper/GsonHelper.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Quasseldroid - Quassel client for Android
- *
- * Copyright (c) 2023 Janne Mareike Koschinski
- * Copyright (c) 2023 The Quassel Project
- *
- * This program is free software: you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 3 as published
- * by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package de.kuschku.quasseldroid.util.helper
-
-import com.google.gson.Gson
-import com.google.gson.JsonElement
-import com.google.gson.reflect.TypeToken
-import java.io.Reader
-
-inline fun <reified T> Gson.fromJsonList(jsonElement: JsonElement): T =
-  this.fromJson(jsonElement, object : TypeToken<T>() {}.type)
-
-inline fun <reified T> Gson.fromJsonList(reader: Reader): T =
-  this.fromJson(reader, object : TypeToken<T>() {}.type)
-
-inline fun <reified T> Gson.fromJsonList(text: String): T =
-  this.fromJson(text, object : TypeToken<T>() {}.type)
-
-inline fun <reified T> Gson.fromJson(jsonElement: JsonElement): T =
-  this.fromJson(jsonElement, T::class.java)
-
-inline fun <reified T> Gson.fromJson(reader: Reader): T =
-  this.fromJson(reader, T::class.java)
-
-inline fun <reified T> Gson.fromJson(text: String): T =
-  this.fromJson(text, T::class.java)
diff --git a/app/src/test/java/de/kuschku/quasseldroid/util/emoji/EmojiDataTest.kt b/app/src/test/java/de/kuschku/quasseldroid/util/emoji/EmojiDataTest.kt
index ee15c46fb..56a1b4bab 100644
--- a/app/src/test/java/de/kuschku/quasseldroid/util/emoji/EmojiDataTest.kt
+++ b/app/src/test/java/de/kuschku/quasseldroid/util/emoji/EmojiDataTest.kt
@@ -20,27 +20,18 @@
 package de.kuschku.quasseldroid.util.emoji
 
 import android.os.Build
-import android.text.Editable
 import android.text.SpannableStringBuilder
-import com.google.gson.Gson
-import com.google.gson.GsonBuilder
-import com.google.gson.reflect.TypeToken
 import de.kuschku.quasseldroid.QuasseldroidTest
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.json.DecodeSequenceMode
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.decodeToSequence
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertTrue
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.robolectric.RobolectricTestRunner
 import org.robolectric.annotation.Config
-import java.io.Reader
-
-inline fun <reified T> Gson.fromJson(reader: Reader): T =
-  if (T::class.java.typeParameters.isEmpty()) {
-    this.fromJson(reader, T::class.java)
-  } else {
-    val type = object : TypeToken<T>() {}.type
-    this.fromJson(reader, type)
-  }
 
 fun EmojiHandler.replaceShortcodes(source: String): String =
   this.replaceShortcodes(SpannableStringBuilder(source)).toString()
@@ -48,11 +39,12 @@ fun EmojiHandler.replaceShortcodes(source: String): String =
 @Config(application = QuasseldroidTest::class, sdk = [Build.VERSION_CODES.P])
 @RunWith(RobolectricTestRunner::class)
 class EmojiDataTest {
+  @OptIn(ExperimentalSerializationApi::class)
   object TestEmojiProvider : EmojiProvider {
-    private val gson: Gson = GsonBuilder().create()
-    override val emoji: List<EmojiHandler.Emoji> = gson.fromJson(
-      TestEmojiProvider::class.java.getResourceAsStream("/emoji.json")!!.reader(Charsets.UTF_8)
-    )
+    override val emoji: List<EmojiHandler.Emoji> = Json.decodeToSequence<EmojiHandler.Emoji>(
+      TestEmojiProvider::class.java.getResourceAsStream("/emoji.json"),
+      DecodeSequenceMode.ARRAY_WRAPPED
+    ).toList()
     override val shortcodes: Map<String, EmojiHandler.Emoji> = emoji.flatMap { entry ->
       entry.shortcodes.map { Pair(it, entry) }
     }.toMap()
diff --git a/build.gradle.kts b/build.gradle.kts
index 246daff18..b0c13fd03 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -30,5 +30,6 @@ plugins {
   alias(libs.plugins.android.application) apply false
   alias(libs.plugins.kotlin.jvm) apply false
   alias(libs.plugins.kotlin.kapt) apply false
+  alias(libs.plugins.kotlin.serialization) apply false
   alias(libs.plugins.ksp) apply false
 }
diff --git a/gradle/convention/src/main/kotlin/KotlinAndroidConvention.kt b/gradle/convention/src/main/kotlin/KotlinAndroidConvention.kt
index 0182e7b5d..6e035a976 100644
--- a/gradle/convention/src/main/kotlin/KotlinAndroidConvention.kt
+++ b/gradle/convention/src/main/kotlin/KotlinAndroidConvention.kt
@@ -19,6 +19,7 @@ class KotlinAndroidConvention : Plugin<Project> {
         apply("org.jetbrains.kotlin.android")
         apply("org.jetbrains.kotlin.kapt")
         apply("com.google.devtools.ksp")
+        apply("org.jetbrains.kotlin.plugin.serialization")
       }
 
       // Use withType to workaround https://youtrack.jetbrains.com/issue/KT-55947
diff --git a/gradle/convention/src/main/kotlin/KotlinConvention.kt b/gradle/convention/src/main/kotlin/KotlinConvention.kt
index 79b5fb12d..69aeb152f 100644
--- a/gradle/convention/src/main/kotlin/KotlinConvention.kt
+++ b/gradle/convention/src/main/kotlin/KotlinConvention.kt
@@ -24,6 +24,7 @@ class KotlinConvention : Plugin<Project> {
         apply("org.jetbrains.kotlin.jvm")
         apply("org.jetbrains.kotlin.kapt")
         apply("com.google.devtools.ksp")
+        apply("org.jetbrains.kotlin.plugin.serialization")
       }
 
       // Use withType to workaround https://youtrack.jetbrains.com/issue/KT-55947
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index ec50abdf6..52e2b01f4 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -8,9 +8,10 @@ androidx-test = "1.5.2"
 dagger = "2.47"
 glide = "4.16.0"
 kotlin = "1.9.10"
+kotlinx-serialization = "1.6.0"
 ksp = "1.9.10-1.0.13"
 materialdialogs = "0.9.6.0"
-retrofit = "2.6.1"
+retrofit = "2.9.0"
 
 [libraries]
 androidx-annotation = { module = "androidx.annotation:annotation", version = "1.6.0" }
@@ -57,11 +58,10 @@ flexbox = { module = "com.google.android.flexbox:flexbox", version = "3.0.0" }
 
 google-material = { module = "com.google.android.material:material", version = "1.9.0" }
 
-glide-compiler = { module = "com.github.bumptech.glide:compiler", version.ref = "glide" }
+glide-compiler = { module = "com.github.bumptech.glide:ksp", version.ref = "glide" }
 glide-core = { module = "com.github.bumptech.glide:glide", version.ref = "glide" }
 glide-recyclerview = { module = "com.github.bumptech.glide:recyclerview-integration", version.ref = "glide" }
 
-gson = { module = "com.google.code.gson:gson", version = "2.9.0" }
 hamcrest = { module = "org.hamcrest:hamcrest", version = "2.2" }
 junit-api = { module = "org.junit.jupiter:junit-jupiter-engine", version = "5.10.0" }
 junit-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version = "5.10.0" }
@@ -75,8 +75,10 @@ materialdialogs-core = { module = "com.afollestad.material-dialogs:core", versio
 materialprogressbar = { module = "me.zhanghai.android.materialprogressbar:library", version = "1.6.1" }
 reactivenetwork = { module = "com.github.pwittchen:reactivenetwork-rx2", version = "3.0.8" }
 
-retrofit-converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit" }
 retrofit-core = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
+retrofit-converter-kotlinx = { module = "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter", version = "1.0.0" }
+
+kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
 
 robolectric = { module = "org.robolectric:robolectric", version = "4.7.3" }
 
diff --git a/malheur/build.gradle.kts b/malheur/build.gradle.kts
index 4281aee5e..4dc7f0707 100644
--- a/malheur/build.gradle.kts
+++ b/malheur/build.gradle.kts
@@ -26,6 +26,6 @@ android {
 }
 
 dependencies {
-  implementation(libs.gson)
   implementation(libs.androidx.annotation)
+  implementation(libs.kotlinx.serialization.json)
 }
diff --git a/malheur/proguard-rules.pro b/malheur/proguard-rules.pro
index 8c9db7a6d..9fd367499 100644
--- a/malheur/proguard-rules.pro
+++ b/malheur/proguard-rules.pro
@@ -1,21 +1 @@
-# Gson uses generic type information stored in a class file when working with fields. Proguard
-# removes such information by default, so configure it to keep all of it.
--keepattributes Signature
-
-# For using GSON @Expose annotation
--keepattributes *Annotation*
-
-# Gson specific classes
--dontwarn sun.misc.**
-#-keep class com.google.gson.stream.** { *; }
-
-# Application classes that will be serialized/deserialized over Gson
--keep class com.google.gson.examples.android.model.** { *; }
-
-# Prevent proguard from stripping interface information from TypeAdapterFactory,
-# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
--keep class * implements com.google.gson.TypeAdapterFactory
--keep class * implements com.google.gson.JsonSerializer
--keep class * implements com.google.gson.JsonDeserializer
-
 -keep class **.BuildConfig { *; }
diff --git a/malheur/src/main/java/de/kuschku/malheur/CrashHandler.kt b/malheur/src/main/java/de/kuschku/malheur/CrashHandler.kt
index a6f5f21d3..b740237d8 100644
--- a/malheur/src/main/java/de/kuschku/malheur/CrashHandler.kt
+++ b/malheur/src/main/java/de/kuschku/malheur/CrashHandler.kt
@@ -24,15 +24,14 @@ import android.os.Handler
 import android.os.HandlerThread
 import android.util.Log
 import android.widget.Toast
-import com.google.gson.GsonBuilder
 import de.kuschku.malheur.collectors.ReportCollector
 import de.kuschku.malheur.config.ReportConfig
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
 import java.io.File
-import java.util.*
+import java.util.Date
 
 object CrashHandler {
-  private val gson = GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create()
-
   private val startTime = Date()
   private var originalHandler: Thread.UncaughtExceptionHandler? = null
   private var myHandler: ((Thread, Throwable) -> Unit)? = null
@@ -62,7 +61,7 @@ object CrashHandler {
         Toast.makeText(application, "Creating crash report", Toast.LENGTH_LONG).show()
       }
       try {
-        val json = gson.toJson(
+        val json = Json.encodeToString(
           reportCollector.collect(
             CrashContext(
               application = application,
diff --git a/ui_spinner/proguard-rules.pro b/ui_spinner/proguard-rules.pro
index 8c9db7a6d..9fd367499 100644
--- a/ui_spinner/proguard-rules.pro
+++ b/ui_spinner/proguard-rules.pro
@@ -1,21 +1 @@
-# Gson uses generic type information stored in a class file when working with fields. Proguard
-# removes such information by default, so configure it to keep all of it.
--keepattributes Signature
-
-# For using GSON @Expose annotation
--keepattributes *Annotation*
-
-# Gson specific classes
--dontwarn sun.misc.**
-#-keep class com.google.gson.stream.** { *; }
-
-# Application classes that will be serialized/deserialized over Gson
--keep class com.google.gson.examples.android.model.** { *; }
-
-# Prevent proguard from stripping interface information from TypeAdapterFactory,
-# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
--keep class * implements com.google.gson.TypeAdapterFactory
--keep class * implements com.google.gson.JsonSerializer
--keep class * implements com.google.gson.JsonDeserializer
-
 -keep class **.BuildConfig { *; }
diff --git a/viewmodel/build.gradle.kts b/viewmodel/build.gradle.kts
index 1fb805a14..2395e8684 100644
--- a/viewmodel/build.gradle.kts
+++ b/viewmodel/build.gradle.kts
@@ -37,9 +37,10 @@ dependencies {
   implementation(libs.threetenbp) {
     artifact { classifier = "no-tzdb" }
   }
-  implementation(libs.annotations.jetbrains)
 
+  implementation(libs.annotations.jetbrains)
   implementation(libs.annotations.inject)
+  implementation(libs.kotlinx.serialization.json)
 
   // Quassel
   implementation(project(":persistence"))
diff --git a/viewmodel/src/main/java/de/kuschku/quasseldroid/util/emoji/EmojiHandler.kt b/viewmodel/src/main/java/de/kuschku/quasseldroid/util/emoji/EmojiHandler.kt
index 8aeab3382..ff5f3d2f9 100644
--- a/viewmodel/src/main/java/de/kuschku/quasseldroid/util/emoji/EmojiHandler.kt
+++ b/viewmodel/src/main/java/de/kuschku/quasseldroid/util/emoji/EmojiHandler.kt
@@ -20,10 +20,12 @@
 package de.kuschku.quasseldroid.util.emoji
 
 import android.text.Editable
+import kotlinx.serialization.Serializable
 import java.util.*
 import javax.inject.Inject
 
 class EmojiHandler @Inject constructor(private val emojiProvider: EmojiProvider) {
+  @Serializable
   data class Emoji(
     val label: String,
     val tags: List<String>,
-- 
GitLab