Kaynağa Gözat

Remove dependency injection from core module and data module from presentation-widget module

Includes side effects:
- No longer need to restart app for user agent string change to take effect
- parseAs extension function requires a Json instance in the calling context, which doesn't necessarily need to be the default one provided by Injekt
arkon 2 yıl önce
ebeveyn
işleme
93523ef50b
34 değiştirilmiş dosya ile 577 ekleme ve 434 silme
  1. 1 0
      app/build.gradle.kts
  2. 1 2
      app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt
  3. 1 1
      app/src/main/java/eu/kanade/tachiyomi/AppModule.kt
  4. 71 59
      app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt
  5. 11 8
      app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiApi.kt
  6. 35 23
      app/src/main/java/eu/kanade/tachiyomi/data/track/kavita/KavitaApi.kt
  7. 112 89
      app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuApi.kt
  8. 18 14
      app/src/main/java/eu/kanade/tachiyomi/data/track/komga/KomgaApi.kt
  9. 43 36
      app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdatesApi.kt
  10. 77 55
      app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt
  11. 5 1
      app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListInterceptor.kt
  12. 50 42
      app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt
  13. 16 3
      app/src/main/java/eu/kanade/tachiyomi/data/track/suwayomi/TachideskApi.kt
  14. 20 15
      app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt
  15. 9 4
      app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt
  16. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/ExtensionDetailsScreenModel.kt
  17. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/webview/WebViewActivity.kt
  18. 1 1
      app/src/main/java/eu/kanade/tachiyomi/ui/webview/WebViewScreenModel.kt
  19. 7 4
      core/build.gradle.kts
  20. 13 11
      core/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt
  21. 7 6
      core/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt
  22. 9 8
      core/src/main/java/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt
  23. 4 6
      core/src/main/java/eu/kanade/tachiyomi/network/interceptor/UserAgentInterceptor.kt
  24. 5 6
      core/src/main/java/eu/kanade/tachiyomi/network/interceptor/WebViewInterceptor.kt
  25. 1 0
      data/build.gradle.kts
  26. 21 1
      data/src/main/java/tachiyomi/data/updates/UpdatesRepositoryImpl.kt
  27. 3 3
      domain/build.gradle.kts
  28. 8 0
      domain/src/main/java/tachiyomi/domain/updates/interactor/GetUpdates.kt
  29. 4 0
      domain/src/main/java/tachiyomi/domain/updates/repository/UpdatesRepository.kt
  30. 1 1
      presentation-widget/build.gradle.kts
  31. 6 10
      presentation-widget/src/main/java/tachiyomi/presentation/widget/TachiyomiWidgetManager.kt
  32. 13 15
      presentation-widget/src/main/java/tachiyomi/presentation/widget/UpdatesGridGlanceWidget.kt
  33. 1 7
      source-api/build.gradle.kts
  34. 1 1
      source-api/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt

+ 1 - 0
app/build.gradle.kts

@@ -283,6 +283,7 @@ tasks {
     // See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api(-markers)
     withType<KotlinCompile> {
         kotlinOptions.freeCompilerArgs += listOf(
+            "-Xcontext-receivers",
             "-opt-in=coil.annotation.ExperimentalCoilApi",
             "-opt-in=com.google.accompanist.permissions.ExperimentalPermissionsApi",
             "-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",

+ 1 - 2
app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt

@@ -222,7 +222,7 @@ object SettingsAdvancedScreen : SearchableSettings {
                 Preference.PreferenceItem.TextPreference(
                     title = stringResource(R.string.pref_clear_cookies),
                     onClick = {
-                        networkHelper.cookieManager.removeAll()
+                        networkHelper.cookieJar.removeAll()
                         context.toast(R.string.cookies_cleared)
                     },
                 ),
@@ -280,7 +280,6 @@ object SettingsAdvancedScreen : SearchableSettings {
                             context.toast(R.string.error_user_agent_string_invalid)
                             return@EditTextPreference false
                         }
-                        context.toast(R.string.requires_app_restart)
                         true
                     },
                 ),

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/AppModule.kt

@@ -118,7 +118,7 @@ class AppModule(val app: Application) : InjektModule {
         addSingletonFactory { ChapterCache(app) }
         addSingletonFactory { CoverCache(app) }
 
-        addSingletonFactory { NetworkHelper(app) }
+        addSingletonFactory { NetworkHelper(app, get()) }
         addSingletonFactory { JavaScriptEngine(app) }
 
         addSingletonFactory { SourceManager(app, get(), get()) }

+ 71 - 59
app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt

@@ -9,6 +9,7 @@ import eu.kanade.tachiyomi.network.awaitSuccess
 import eu.kanade.tachiyomi.network.interceptor.rateLimit
 import eu.kanade.tachiyomi.network.jsonMime
 import eu.kanade.tachiyomi.network.parseAs
+import kotlinx.serialization.json.Json
 import kotlinx.serialization.json.JsonNull
 import kotlinx.serialization.json.JsonObject
 import kotlinx.serialization.json.buildJsonObject
@@ -24,11 +25,14 @@ import kotlinx.serialization.json.putJsonObject
 import okhttp3.OkHttpClient
 import okhttp3.RequestBody.Companion.toRequestBody
 import tachiyomi.core.util.lang.withIOContext
+import uy.kohesive.injekt.injectLazy
 import java.util.Calendar
 import java.util.concurrent.TimeUnit
 
 class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
 
+    private val json: Json by injectLazy()
+
     private val authClient = client.newBuilder()
         .addInterceptor(interceptor)
         .rateLimit(permits = 85, period = 1, unit = TimeUnit.MINUTES)
@@ -53,19 +57,21 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
                     put("status", track.toAnilistStatus())
                 }
             }
-            authClient.newCall(
-                POST(
-                    apiUrl,
-                    body = payload.toString().toRequestBody(jsonMime),
-                ),
-            )
-                .awaitSuccess()
-                .parseAs<JsonObject>()
-                .let {
-                    track.library_id =
-                        it["data"]!!.jsonObject["SaveMediaListEntry"]!!.jsonObject["id"]!!.jsonPrimitive.long
-                    track
-                }
+            with(json) {
+                authClient.newCall(
+                    POST(
+                        apiUrl,
+                        body = payload.toString().toRequestBody(jsonMime),
+                    ),
+                )
+                    .awaitSuccess()
+                    .parseAs<JsonObject>()
+                    .let {
+                        track.library_id =
+                            it["data"]!!.jsonObject["SaveMediaListEntry"]!!.jsonObject["id"]!!.jsonPrimitive.long
+                        track
+                    }
+            }
         }
     }
 
@@ -137,21 +143,23 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
                     put("query", search)
                 }
             }
-            authClient.newCall(
-                POST(
-                    apiUrl,
-                    body = payload.toString().toRequestBody(jsonMime),
-                ),
-            )
-                .awaitSuccess()
-                .parseAs<JsonObject>()
-                .let { response ->
-                    val data = response["data"]!!.jsonObject
-                    val page = data["Page"]!!.jsonObject
-                    val media = page["media"]!!.jsonArray
-                    val entries = media.map { jsonToALManga(it.jsonObject) }
-                    entries.map { it.toTrack() }
-                }
+            with(json) {
+                authClient.newCall(
+                    POST(
+                        apiUrl,
+                        body = payload.toString().toRequestBody(jsonMime),
+                    ),
+                )
+                    .awaitSuccess()
+                    .parseAs<JsonObject>()
+                    .let { response ->
+                        val data = response["data"]!!.jsonObject
+                        val page = data["Page"]!!.jsonObject
+                        val media = page["media"]!!.jsonArray
+                        val entries = media.map { jsonToALManga(it.jsonObject) }
+                        entries.map { it.toTrack() }
+                    }
+            }
         }
     }
 
@@ -205,21 +213,23 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
                     put("manga_id", track.media_id)
                 }
             }
-            authClient.newCall(
-                POST(
-                    apiUrl,
-                    body = payload.toString().toRequestBody(jsonMime),
-                ),
-            )
-                .awaitSuccess()
-                .parseAs<JsonObject>()
-                .let { response ->
-                    val data = response["data"]!!.jsonObject
-                    val page = data["Page"]!!.jsonObject
-                    val media = page["mediaList"]!!.jsonArray
-                    val entries = media.map { jsonToALUserManga(it.jsonObject) }
-                    entries.firstOrNull()?.toTrack()
-                }
+            with(json) {
+                authClient.newCall(
+                    POST(
+                        apiUrl,
+                        body = payload.toString().toRequestBody(jsonMime),
+                    ),
+                )
+                    .awaitSuccess()
+                    .parseAs<JsonObject>()
+                    .let { response ->
+                        val data = response["data"]!!.jsonObject
+                        val page = data["Page"]!!.jsonObject
+                        val media = page["mediaList"]!!.jsonArray
+                        val entries = media.map { jsonToALUserManga(it.jsonObject) }
+                        entries.firstOrNull()?.toTrack()
+                    }
+            }
         }
     }
 
@@ -247,22 +257,24 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
             val payload = buildJsonObject {
                 put("query", query)
             }
-            authClient.newCall(
-                POST(
-                    apiUrl,
-                    body = payload.toString().toRequestBody(jsonMime),
-                ),
-            )
-                .awaitSuccess()
-                .parseAs<JsonObject>()
-                .let {
-                    val data = it["data"]!!.jsonObject
-                    val viewer = data["Viewer"]!!.jsonObject
-                    Pair(
-                        viewer["id"]!!.jsonPrimitive.int,
-                        viewer["mediaListOptions"]!!.jsonObject["scoreFormat"]!!.jsonPrimitive.content,
-                    )
-                }
+            with(json) {
+                authClient.newCall(
+                    POST(
+                        apiUrl,
+                        body = payload.toString().toRequestBody(jsonMime),
+                    ),
+                )
+                    .awaitSuccess()
+                    .parseAs<JsonObject>()
+                    .let {
+                        val data = it["data"]!!.jsonObject
+                        val viewer = data["Viewer"]!!.jsonObject
+                        Pair(
+                            viewer["id"]!!.jsonPrimitive.int,
+                            viewer["mediaListOptions"]!!.jsonObject["scoreFormat"]!!.jsonPrimitive.content,
+                        )
+                    }
+            }
         }
     }
 

+ 11 - 8
app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiApi.kt

@@ -118,10 +118,12 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
 
     suspend fun findLibManga(track: Track): Track? {
         return withIOContext {
-            authClient.newCall(GET("$apiUrl/subject/${track.media_id}"))
-                .awaitSuccess()
-                .parseAs<JsonObject>()
-                .let { jsonToSearch(it) }
+            with(json) {
+                authClient.newCall(GET("$apiUrl/subject/${track.media_id}"))
+                    .awaitSuccess()
+                    .parseAs<JsonObject>()
+                    .let { jsonToSearch(it) }
+            }
         }
     }
 
@@ -155,9 +157,11 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
 
     suspend fun accessToken(code: String): OAuth {
         return withIOContext {
-            client.newCall(accessTokenRequest(code))
-                .awaitSuccess()
-                .parseAs()
+            with(json) {
+                client.newCall(accessTokenRequest(code))
+                    .awaitSuccess()
+                    .parseAs()
+            }
         }
     }
 
@@ -181,7 +185,6 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
         private const val loginUrl = "https://bgm.tv/oauth/authorize"
 
         private const val redirectUrl = "tachiyomi://bangumi-auth"
-        private const val baseMangaUrl = "$apiUrl/mangas"
 
         fun authUrl(): Uri =
             loginUrl.toUri().buildUpon()

+ 35 - 23
app/src/main/java/eu/kanade/tachiyomi/data/track/kavita/KavitaApi.kt

@@ -6,6 +6,7 @@ import eu.kanade.tachiyomi.network.GET
 import eu.kanade.tachiyomi.network.POST
 import eu.kanade.tachiyomi.network.awaitSuccess
 import eu.kanade.tachiyomi.network.parseAs
+import kotlinx.serialization.json.Json
 import logcat.LogPriority
 import okhttp3.Dns
 import okhttp3.MediaType.Companion.toMediaTypeOrNull
@@ -13,11 +14,14 @@ import okhttp3.OkHttpClient
 import okhttp3.RequestBody.Companion.toRequestBody
 import tachiyomi.core.util.lang.withIOContext
 import tachiyomi.core.util.system.logcat
+import uy.kohesive.injekt.injectLazy
 import java.io.IOException
 import java.net.SocketTimeoutException
 
 class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor) {
 
+    private val json: Json by injectLazy()
+
     private val authClient = client.newBuilder()
         .dns(Dns.SYSTEM)
         .addInterceptor(interceptor)
@@ -39,18 +43,20 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
             body = "{}".toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull()),
         )
         try {
-            client.newCall(request).execute().use {
-                when (it.code) {
-                    200 -> return it.parseAs<AuthenticationDto>().token
-                    401 -> {
-                        logcat(LogPriority.WARN) { "Unauthorized / api key not valid: Cleaned api URL: $apiUrl, Api key is empty: ${apiKey.isEmpty()}" }
-                        throw IOException("Unauthorized / api key not valid")
-                    }
-                    500 -> {
-                        logcat(LogPriority.WARN) { "Error fetching JWT token. Cleaned api URL: $apiUrl, Api key is empty: ${apiKey.isEmpty()}" }
-                        throw IOException("Error fetching JWT token")
+            with(json) {
+                client.newCall(request).execute().use {
+                    when (it.code) {
+                        200 -> return it.parseAs<AuthenticationDto>().token
+                        401 -> {
+                            logcat(LogPriority.WARN) { "Unauthorized / api key not valid: Cleaned api URL: $apiUrl, Api key is empty: ${apiKey.isEmpty()}" }
+                            throw IOException("Unauthorized / api key not valid")
+                        }
+                        500 -> {
+                            logcat(LogPriority.WARN) { "Error fetching JWT token. Cleaned api URL: $apiUrl, Api key is empty: ${apiKey.isEmpty()}" }
+                            throw IOException("Error fetching JWT token")
+                        }
+                        else -> {}
                     }
-                    else -> {}
                 }
             }
             // Not sure which one to catch
@@ -86,9 +92,11 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
     private fun getTotalChapters(url: String): Int {
         val requestUrl = getApiVolumesUrl(url)
         try {
-            val listVolumeDto = authClient.newCall(GET(requestUrl))
-                .execute()
-                .parseAs<List<VolumeDto>>()
+            val listVolumeDto = with(json) {
+                authClient.newCall(GET(requestUrl))
+                    .execute()
+                    .parseAs<List<VolumeDto>>()
+            }
             var volumeNumber = 0
             var maxChapterNumber = 0
             for (volume in listVolumeDto) {
@@ -110,12 +118,14 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
         val serieId = getIdFromUrl(url)
         val requestUrl = "${getApiFromUrl(url)}/Tachiyomi/latest-chapter?seriesId=$serieId"
         try {
-            authClient.newCall(GET(requestUrl)).execute().use {
-                if (it.code == 200) {
-                    return it.parseAs<ChapterDto>().number!!.replace(",", ".").toFloat()
-                }
-                if (it.code == 204) {
-                    return 0F
+            with(json) {
+                authClient.newCall(GET(requestUrl)).execute().use {
+                    if (it.code == 200) {
+                        return it.parseAs<ChapterDto>().number!!.replace(",", ".").toFloat()
+                    }
+                    if (it.code == 204) {
+                        return 0F
+                    }
                 }
             }
         } catch (e: Exception) {
@@ -127,9 +137,11 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
 
     suspend fun getTrackSearch(url: String): TrackSearch = withIOContext {
         try {
-            val serieDto: SeriesDto = authClient.newCall(GET(url))
-                .awaitSuccess()
-                .parseAs()
+            val serieDto: SeriesDto = with(json) {
+                authClient.newCall(GET(url))
+                    .awaitSuccess()
+                    .parseAs()
+            }
 
             val track = serieDto.toTrack()
             track.apply {

+ 112 - 89
app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuApi.kt

@@ -8,6 +8,7 @@ import eu.kanade.tachiyomi.network.POST
 import eu.kanade.tachiyomi.network.awaitSuccess
 import eu.kanade.tachiyomi.network.jsonMime
 import eu.kanade.tachiyomi.network.parseAs
+import kotlinx.serialization.json.Json
 import kotlinx.serialization.json.JsonObject
 import kotlinx.serialization.json.buildJsonObject
 import kotlinx.serialization.json.jsonArray
@@ -24,11 +25,14 @@ import okhttp3.Request
 import okhttp3.RequestBody
 import okhttp3.RequestBody.Companion.toRequestBody
 import tachiyomi.core.util.lang.withIOContext
+import uy.kohesive.injekt.injectLazy
 import java.net.URLEncoder
 import java.nio.charset.StandardCharsets
 
 class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) {
 
+    private val json: Json by injectLazy()
+
     private val authClient = client.newBuilder().addInterceptor(interceptor).build()
 
     suspend fun addLibManga(track: Track, userId: String): Track {
@@ -57,22 +61,25 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
                 }
             }
 
-            authClient.newCall(
-                POST(
-                    "${baseUrl}library-entries",
-                    headers = headersOf(
-                        "Content-Type",
-                        "application/vnd.api+json",
+            with(json) {
+                authClient.newCall(
+                    POST(
+                        "${baseUrl}library-entries",
+                        headers = headersOf(
+                            "Content-Type",
+                            "application/vnd.api+json",
+                        ),
+                        body = data.toString()
+                            .toRequestBody("application/vnd.api+json".toMediaType()),
                     ),
-                    body = data.toString().toRequestBody("application/vnd.api+json".toMediaType()),
-                ),
-            )
-                .awaitSuccess()
-                .parseAs<JsonObject>()
-                .let {
-                    track.media_id = it["data"]!!.jsonObject["id"]!!.jsonPrimitive.long
-                    track
-                }
+                )
+                    .awaitSuccess()
+                    .parseAs<JsonObject>()
+                    .let {
+                        track.media_id = it["data"]!!.jsonObject["id"]!!.jsonPrimitive.long
+                        track
+                    }
+            }
         }
     }
 
@@ -92,35 +99,41 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
                 }
             }
 
-            authClient.newCall(
-                Request.Builder()
-                    .url("${baseUrl}library-entries/${track.media_id}")
-                    .headers(
-                        headersOf(
-                            "Content-Type",
-                            "application/vnd.api+json",
-                        ),
-                    )
-                    .patch(data.toString().toRequestBody("application/vnd.api+json".toMediaType()))
-                    .build(),
-            )
-                .awaitSuccess()
-                .parseAs<JsonObject>()
-                .let {
-                    track
-                }
+            with(json) {
+                authClient.newCall(
+                    Request.Builder()
+                        .url("${baseUrl}library-entries/${track.media_id}")
+                        .headers(
+                            headersOf(
+                                "Content-Type",
+                                "application/vnd.api+json",
+                            ),
+                        )
+                        .patch(
+                            data.toString().toRequestBody("application/vnd.api+json".toMediaType()),
+                        )
+                        .build(),
+                )
+                    .awaitSuccess()
+                    .parseAs<JsonObject>()
+                    .let {
+                        track
+                    }
+            }
         }
     }
 
     suspend fun search(query: String): List<TrackSearch> {
         return withIOContext {
-            authClient.newCall(GET(algoliaKeyUrl))
-                .awaitSuccess()
-                .parseAs<JsonObject>()
-                .let {
-                    val key = it["media"]!!.jsonObject["key"]!!.jsonPrimitive.content
-                    algoliaSearch(key, query)
-                }
+            with(json) {
+                authClient.newCall(GET(algoliaKeyUrl))
+                    .awaitSuccess()
+                    .parseAs<JsonObject>()
+                    .let {
+                        val key = it["media"]!!.jsonObject["key"]!!.jsonPrimitive.content
+                        algoliaSearch(key, query)
+                    }
+            }
         }
     }
 
@@ -130,26 +143,28 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
                 put("params", "query=${URLEncoder.encode(query, StandardCharsets.UTF_8.name())}$algoliaFilter")
             }
 
-            client.newCall(
-                POST(
-                    algoliaUrl,
-                    headers = headersOf(
-                        "X-Algolia-Application-Id",
-                        algoliaAppId,
-                        "X-Algolia-API-Key",
-                        key,
+            with(json) {
+                client.newCall(
+                    POST(
+                        algoliaUrl,
+                        headers = headersOf(
+                            "X-Algolia-Application-Id",
+                            algoliaAppId,
+                            "X-Algolia-API-Key",
+                            key,
+                        ),
+                        body = jsonObject.toString().toRequestBody(jsonMime),
                     ),
-                    body = jsonObject.toString().toRequestBody(jsonMime),
-                ),
-            )
-                .awaitSuccess()
-                .parseAs<JsonObject>()
-                .let {
-                    it["hits"]!!.jsonArray
-                        .map { KitsuSearchManga(it.jsonObject) }
-                        .filter { it.subType != "novel" }
-                        .map { it.toTrack() }
-                }
+                )
+                    .awaitSuccess()
+                    .parseAs<JsonObject>()
+                    .let {
+                        it["hits"]!!.jsonArray
+                            .map { KitsuSearchManga(it.jsonObject) }
+                            .filter { it.subType != "novel" }
+                            .map { it.toTrack() }
+                    }
+            }
         }
     }
 
@@ -159,18 +174,20 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
                 .encodedQuery("filter[manga_id]=${track.media_id}&filter[user_id]=$userId")
                 .appendQueryParameter("include", "manga")
                 .build()
-            authClient.newCall(GET(url.toString()))
-                .awaitSuccess()
-                .parseAs<JsonObject>()
-                .let {
-                    val data = it["data"]!!.jsonArray
-                    if (data.size > 0) {
-                        val manga = it["included"]!!.jsonArray[0].jsonObject
-                        KitsuLibManga(data[0].jsonObject, manga).toTrack()
-                    } else {
-                        null
+            with(json) {
+                authClient.newCall(GET(url.toString()))
+                    .awaitSuccess()
+                    .parseAs<JsonObject>()
+                    .let {
+                        val data = it["data"]!!.jsonArray
+                        if (data.size > 0) {
+                            val manga = it["included"]!!.jsonArray[0].jsonObject
+                            KitsuLibManga(data[0].jsonObject, manga).toTrack()
+                        } else {
+                            null
+                        }
                     }
-                }
+            }
         }
     }
 
@@ -180,18 +197,20 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
                 .encodedQuery("filter[id]=${track.media_id}")
                 .appendQueryParameter("include", "manga")
                 .build()
-            authClient.newCall(GET(url.toString()))
-                .awaitSuccess()
-                .parseAs<JsonObject>()
-                .let {
-                    val data = it["data"]!!.jsonArray
-                    if (data.size > 0) {
-                        val manga = it["included"]!!.jsonArray[0].jsonObject
-                        KitsuLibManga(data[0].jsonObject, manga).toTrack()
-                    } else {
-                        throw Exception("Could not find manga")
+            with(json) {
+                authClient.newCall(GET(url.toString()))
+                    .awaitSuccess()
+                    .parseAs<JsonObject>()
+                    .let {
+                        val data = it["data"]!!.jsonArray
+                        if (data.size > 0) {
+                            val manga = it["included"]!!.jsonArray[0].jsonObject
+                            KitsuLibManga(data[0].jsonObject, manga).toTrack()
+                        } else {
+                            throw Exception("Could not find manga")
+                        }
                     }
-                }
+            }
         }
     }
 
@@ -204,9 +223,11 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
                 .add("client_id", clientId)
                 .add("client_secret", clientSecret)
                 .build()
-            client.newCall(POST(loginUrl, body = formBody))
-                .awaitSuccess()
-                .parseAs()
+            with(json) {
+                client.newCall(POST(loginUrl, body = formBody))
+                    .awaitSuccess()
+                    .parseAs()
+            }
         }
     }
 
@@ -215,12 +236,14 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
             val url = "${baseUrl}users".toUri().buildUpon()
                 .encodedQuery("filter[self]=true")
                 .build()
-            authClient.newCall(GET(url.toString()))
-                .awaitSuccess()
-                .parseAs<JsonObject>()
-                .let {
-                    it["data"]!!.jsonArray[0].jsonObject["id"]!!.jsonPrimitive.content
-                }
+            with(json) {
+                authClient.newCall(GET(url.toString()))
+                    .awaitSuccess()
+                    .parseAs<JsonObject>()
+                    .let {
+                        it["data"]!!.jsonArray[0].jsonObject["id"]!!.jsonPrimitive.content
+                    }
+            }
         }
     }
 

+ 18 - 14
app/src/main/java/eu/kanade/tachiyomi/data/track/komga/KomgaApi.kt

@@ -26,25 +26,29 @@ class KomgaApi(private val client: OkHttpClient) {
     suspend fun getTrackSearch(url: String): TrackSearch =
         withIOContext {
             try {
-                val track = if (url.contains(READLIST_API)) {
-                    client.newCall(GET(url))
-                        .awaitSuccess()
-                        .parseAs<ReadListDto>()
-                        .toTrack()
-                } else {
-                    client.newCall(GET(url))
-                        .awaitSuccess()
-                        .parseAs<SeriesDto>()
-                        .toTrack()
+                val track = with(json) {
+                    if (url.contains(READLIST_API)) {
+                        client.newCall(GET(url))
+                            .awaitSuccess()
+                            .parseAs<ReadListDto>()
+                            .toTrack()
+                    } else {
+                        client.newCall(GET(url))
+                            .awaitSuccess()
+                            .parseAs<SeriesDto>()
+                            .toTrack()
+                    }
                 }
 
                 val progress = client
                     .newCall(GET("${url.replace("/api/v1/series/", "/api/v2/series/")}/read-progress/tachiyomi"))
                     .awaitSuccess().let {
-                        if (url.contains("/api/v1/series/")) {
-                            it.parseAs<ReadProgressV2Dto>()
-                        } else {
-                            it.parseAs<ReadProgressDto>().toV2()
+                        with(json) {
+                            if (url.contains("/api/v1/series/")) {
+                                it.parseAs<ReadProgressV2Dto>()
+                            } else {
+                                it.parseAs<ReadProgressDto>().toV2()
+                            }
                         }
                     }
 

+ 43 - 36
app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdatesApi.kt

@@ -47,7 +47,7 @@ class MangaUpdatesApi(
     }
 
     suspend fun getSeriesListItem(track: Track): Pair<ListItem, Rating?> {
-        val listItem =
+        val listItem = with(json) {
             authClient.newCall(
                 GET(
                     url = "$baseUrl/v1/lists/series/${track.media_id}",
@@ -55,6 +55,7 @@ class MangaUpdatesApi(
             )
                 .awaitSuccess()
                 .parseAs<ListItem>()
+        }
 
         val rating = getSeriesRating(track)
 
@@ -111,13 +112,15 @@ class MangaUpdatesApi(
 
     suspend fun getSeriesRating(track: Track): Rating? {
         return try {
-            authClient.newCall(
-                GET(
-                    url = "$baseUrl/v1/series/${track.media_id}/rating",
-                ),
-            )
-                .awaitSuccess()
-                .parseAs<Rating>()
+            with(json) {
+                authClient.newCall(
+                    GET(
+                        url = "$baseUrl/v1/series/${track.media_id}/rating",
+                    ),
+                )
+                    .awaitSuccess()
+                    .parseAs<Rating>()
+            }
         } catch (e: Exception) {
             null
         }
@@ -156,20 +159,22 @@ class MangaUpdatesApi(
                 },
             )
         }
-        return client.newCall(
-            POST(
-                url = "$baseUrl/v1/series/search",
-                body = body.toString().toRequestBody(contentType),
-            ),
-        )
-            .awaitSuccess()
-            .parseAs<JsonObject>()
-            .let { obj ->
-                obj["results"]?.jsonArray?.map { element ->
-                    json.decodeFromJsonElement<Record>(element.jsonObject["record"]!!)
+        return with(json) {
+            client.newCall(
+                POST(
+                    url = "$baseUrl/v1/series/search",
+                    body = body.toString().toRequestBody(contentType),
+                ),
+            )
+                .awaitSuccess()
+                .parseAs<JsonObject>()
+                .let { obj ->
+                    obj["results"]?.jsonArray?.map { element ->
+                        json.decodeFromJsonElement<Record>(element.jsonObject["record"]!!)
+                    }
                 }
-            }
-            .orEmpty()
+                .orEmpty()
+        }
     }
 
     suspend fun authenticate(username: String, password: String): Context? {
@@ -177,21 +182,23 @@ class MangaUpdatesApi(
             put("username", username)
             put("password", password)
         }
-        return client.newCall(
-            PUT(
-                url = "$baseUrl/v1/account/login",
-                body = body.toString().toRequestBody(contentType),
-            ),
-        )
-            .awaitSuccess()
-            .parseAs<JsonObject>()
-            .let { obj ->
-                try {
-                    json.decodeFromJsonElement<Context>(obj["context"]!!)
-                } catch (e: Exception) {
-                    logcat(LogPriority.ERROR, e)
-                    null
+        return with(json) {
+            client.newCall(
+                PUT(
+                    url = "$baseUrl/v1/account/login",
+                    body = body.toString().toRequestBody(contentType),
+                ),
+            )
+                .awaitSuccess()
+                .parseAs<JsonObject>()
+                .let { obj ->
+                    try {
+                        json.decodeFromJsonElement<Context>(obj["context"]!!)
+                    } catch (e: Exception) {
+                        logcat(LogPriority.ERROR, e)
+                        null
+                    }
                 }
-            }
+        }
     }
 }

+ 77 - 55
app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt

@@ -12,6 +12,7 @@ import eu.kanade.tachiyomi.network.parseAs
 import eu.kanade.tachiyomi.util.PkceUtil
 import kotlinx.coroutines.async
 import kotlinx.coroutines.awaitAll
+import kotlinx.serialization.json.Json
 import kotlinx.serialization.json.JsonObject
 import kotlinx.serialization.json.boolean
 import kotlinx.serialization.json.contentOrNull
@@ -27,11 +28,14 @@ import okhttp3.OkHttpClient
 import okhttp3.Request
 import okhttp3.RequestBody
 import tachiyomi.core.util.lang.withIOContext
+import uy.kohesive.injekt.injectLazy
 import java.text.SimpleDateFormat
 import java.util.Locale
 
 class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListInterceptor) {
 
+    private val json: Json by injectLazy()
+
     private val authClient = client.newBuilder().addInterceptor(interceptor).build()
 
     suspend fun getAccessToken(authCode: String): OAuth {
@@ -42,9 +46,11 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
                 .add("code_verifier", codeVerifier)
                 .add("grant_type", "authorization_code")
                 .build()
-            client.newCall(POST("$baseOAuthUrl/token", body = formBody))
-                .awaitSuccess()
-                .parseAs()
+            with(json) {
+                client.newCall(POST("$baseOAuthUrl/token", body = formBody))
+                    .awaitSuccess()
+                    .parseAs()
+            }
         }
     }
 
@@ -54,10 +60,12 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
                 .url("$baseApiUrl/users/@me")
                 .get()
                 .build()
-            authClient.newCall(request)
-                .awaitSuccess()
-                .parseAs<JsonObject>()
-                .let { it["name"]!!.jsonPrimitive.content }
+            with(json) {
+                authClient.newCall(request)
+                    .awaitSuccess()
+                    .parseAs<JsonObject>()
+                    .let { it["name"]!!.jsonPrimitive.content }
+            }
         }
     }
 
@@ -68,19 +76,21 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
                 .appendQueryParameter("q", query.take(64))
                 .appendQueryParameter("nsfw", "true")
                 .build()
-            authClient.newCall(GET(url.toString()))
-                .awaitSuccess()
-                .parseAs<JsonObject>()
-                .let {
-                    it["data"]!!.jsonArray
-                        .map { data -> data.jsonObject["node"]!!.jsonObject }
-                        .map { node ->
-                            val id = node["id"]!!.jsonPrimitive.int
-                            async { getMangaDetails(id) }
-                        }
-                        .awaitAll()
-                        .filter { trackSearch -> !trackSearch.publishing_type.contains("novel") }
-                }
+            with(json) {
+                authClient.newCall(GET(url.toString()))
+                    .awaitSuccess()
+                    .parseAs<JsonObject>()
+                    .let {
+                        it["data"]!!.jsonArray
+                            .map { data -> data.jsonObject["node"]!!.jsonObject }
+                            .map { node ->
+                                val id = node["id"]!!.jsonPrimitive.int
+                                async { getMangaDetails(id) }
+                            }
+                            .awaitAll()
+                            .filter { trackSearch -> !trackSearch.publishing_type.contains("novel") }
+                    }
+            }
         }
     }
 
@@ -90,28 +100,34 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
                 .appendPath(id.toString())
                 .appendQueryParameter("fields", "id,title,synopsis,num_chapters,main_picture,status,media_type,start_date")
                 .build()
-            authClient.newCall(GET(url.toString()))
-                .awaitSuccess()
-                .parseAs<JsonObject>()
-                .let {
-                    val obj = it.jsonObject
-                    TrackSearch.create(TrackManager.MYANIMELIST).apply {
-                        media_id = obj["id"]!!.jsonPrimitive.long
-                        title = obj["title"]!!.jsonPrimitive.content
-                        summary = obj["synopsis"]?.jsonPrimitive?.content ?: ""
-                        total_chapters = obj["num_chapters"]!!.jsonPrimitive.int
-                        cover_url = obj["main_picture"]?.jsonObject?.get("large")?.jsonPrimitive?.content ?: ""
-                        tracking_url = "https://myanimelist.net/manga/$media_id"
-                        publishing_status = obj["status"]!!.jsonPrimitive.content.replace("_", " ")
-                        publishing_type = obj["media_type"]!!.jsonPrimitive.content.replace("_", " ")
-                        start_date = try {
-                            val outputDf = SimpleDateFormat("yyyy-MM-dd", Locale.US)
-                            outputDf.format(obj["start_date"]!!)
-                        } catch (e: Exception) {
-                            ""
+            with(json) {
+                authClient.newCall(GET(url.toString()))
+                    .awaitSuccess()
+                    .parseAs<JsonObject>()
+                    .let {
+                        val obj = it.jsonObject
+                        TrackSearch.create(TrackManager.MYANIMELIST).apply {
+                            media_id = obj["id"]!!.jsonPrimitive.long
+                            title = obj["title"]!!.jsonPrimitive.content
+                            summary = obj["synopsis"]?.jsonPrimitive?.content ?: ""
+                            total_chapters = obj["num_chapters"]!!.jsonPrimitive.int
+                            cover_url =
+                                obj["main_picture"]?.jsonObject?.get("large")?.jsonPrimitive?.content
+                                    ?: ""
+                            tracking_url = "https://myanimelist.net/manga/$media_id"
+                            publishing_status =
+                                obj["status"]!!.jsonPrimitive.content.replace("_", " ")
+                            publishing_type =
+                                obj["media_type"]!!.jsonPrimitive.content.replace("_", " ")
+                            start_date = try {
+                                val outputDf = SimpleDateFormat("yyyy-MM-dd", Locale.US)
+                                outputDf.format(obj["start_date"]!!)
+                            } catch (e: Exception) {
+                                ""
+                            }
                         }
                     }
-                }
+            }
         }
     }
 
@@ -133,10 +149,12 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
                 .url(mangaUrl(track.media_id).toString())
                 .put(formBodyBuilder.build())
                 .build()
-            authClient.newCall(request)
-                .awaitSuccess()
-                .parseAs<JsonObject>()
-                .let { parseMangaItem(it, track) }
+            with(json) {
+                authClient.newCall(request)
+                    .awaitSuccess()
+                    .parseAs<JsonObject>()
+                    .let { parseMangaItem(it, track) }
+            }
         }
     }
 
@@ -146,15 +164,17 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
                 .appendPath(track.media_id.toString())
                 .appendQueryParameter("fields", "num_chapters,my_list_status{start_date,finish_date}")
                 .build()
-            authClient.newCall(GET(uri.toString()))
-                .awaitSuccess()
-                .parseAs<JsonObject>()
-                .let { obj ->
-                    track.total_chapters = obj["num_chapters"]!!.jsonPrimitive.int
-                    obj.jsonObject["my_list_status"]?.jsonObject?.let {
-                        parseMangaItem(it, track)
+            with(json) {
+                authClient.newCall(GET(uri.toString()))
+                    .awaitSuccess()
+                    .parseAs<JsonObject>()
+                    .let { obj ->
+                        track.total_chapters = obj["num_chapters"]!!.jsonPrimitive.int
+                        obj.jsonObject["my_list_status"]?.jsonObject?.let {
+                            parseMangaItem(it, track)
+                        }
                     }
-                }
+            }
         }
     }
 
@@ -198,9 +218,11 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
                 .url(urlBuilder.build().toString())
                 .get()
                 .build()
-            authClient.newCall(request)
-                .awaitSuccess()
-                .parseAs()
+            with(json) {
+                authClient.newCall(request)
+                    .awaitSuccess()
+                    .parseAs()
+            }
         }
     }
 

+ 5 - 1
app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListInterceptor.kt

@@ -1,12 +1,16 @@
 package eu.kanade.tachiyomi.data.track.myanimelist
 
 import eu.kanade.tachiyomi.network.parseAs
+import kotlinx.serialization.json.Json
 import okhttp3.Interceptor
 import okhttp3.Response
+import uy.kohesive.injekt.injectLazy
 import java.io.IOException
 
 class MyAnimeListInterceptor(private val myanimelist: MyAnimeList, private var token: String?) : Interceptor {
 
+    private val json: Json by injectLazy()
+
     private var oauth: OAuth? = null
 
     override fun intercept(chain: Interceptor.Chain): Response {
@@ -69,7 +73,7 @@ class MyAnimeListInterceptor(private val myanimelist: MyAnimeList, private var t
             val oauthResponse = chain.proceed(MyAnimeListApi.refreshTokenRequest(oauth!!))
 
             if (oauthResponse.isSuccessful) {
-                oauthResponse.parseAs<OAuth>()
+                with(json) { oauthResponse.parseAs<OAuth>() }
             } else {
                 oauthResponse.close()
                 null

+ 50 - 42
app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt

@@ -9,6 +9,7 @@ import eu.kanade.tachiyomi.network.POST
 import eu.kanade.tachiyomi.network.awaitSuccess
 import eu.kanade.tachiyomi.network.jsonMime
 import eu.kanade.tachiyomi.network.parseAs
+import kotlinx.serialization.json.Json
 import kotlinx.serialization.json.JsonArray
 import kotlinx.serialization.json.JsonObject
 import kotlinx.serialization.json.buildJsonObject
@@ -24,9 +25,12 @@ import okhttp3.FormBody
 import okhttp3.OkHttpClient
 import okhttp3.RequestBody.Companion.toRequestBody
 import tachiyomi.core.util.lang.withIOContext
+import uy.kohesive.injekt.injectLazy
 
 class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInterceptor) {
 
+    private val json: Json by injectLazy()
+
     private val authClient = client.newBuilder().addInterceptor(interceptor).build()
 
     suspend fun addLibManga(track: Track, user_id: String): Track {
@@ -60,14 +64,16 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
                 .appendQueryParameter("search", search)
                 .appendQueryParameter("limit", "20")
                 .build()
-            authClient.newCall(GET(url.toString()))
-                .awaitSuccess()
-                .parseAs<JsonArray>()
-                .let { response ->
-                    response.map {
-                        jsonToSearch(it.jsonObject)
+            with(json) {
+                authClient.newCall(GET(url.toString()))
+                    .awaitSuccess()
+                    .parseAs<JsonArray>()
+                    .let { response ->
+                        response.map {
+                            jsonToSearch(it.jsonObject)
+                        }
                     }
-                }
+            }
         }
     }
 
@@ -81,7 +87,7 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
             tracking_url = baseUrl + obj["url"]!!.jsonPrimitive.content
             publishing_status = obj["status"]!!.jsonPrimitive.content
             publishing_type = obj["kind"]!!.jsonPrimitive.content
-            start_date = obj.get("aired_on")!!.jsonPrimitive.contentOrNull ?: ""
+            start_date = obj["aired_on"]!!.jsonPrimitive.contentOrNull ?: ""
         }
     }
 
@@ -102,44 +108,52 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
             val urlMangas = "$apiUrl/mangas".toUri().buildUpon()
                 .appendPath(track.media_id.toString())
                 .build()
-            val mangas = authClient.newCall(GET(urlMangas.toString()))
-                .awaitSuccess()
-                .parseAs<JsonObject>()
+            val mangas = with(json) {
+                authClient.newCall(GET(urlMangas.toString()))
+                    .awaitSuccess()
+                    .parseAs<JsonObject>()
+            }
 
             val url = "$apiUrl/v2/user_rates".toUri().buildUpon()
                 .appendQueryParameter("user_id", user_id)
                 .appendQueryParameter("target_id", track.media_id.toString())
                 .appendQueryParameter("target_type", "Manga")
                 .build()
-            authClient.newCall(GET(url.toString()))
-                .awaitSuccess()
-                .parseAs<JsonArray>()
-                .let { response ->
-                    if (response.size > 1) {
-                        throw Exception("Too much mangas in response")
-                    }
-                    val entry = response.map {
-                        jsonToTrack(it.jsonObject, mangas)
+            with(json) {
+                authClient.newCall(GET(url.toString()))
+                    .awaitSuccess()
+                    .parseAs<JsonArray>()
+                    .let { response ->
+                        if (response.size > 1) {
+                            throw Exception("Too much mangas in response")
+                        }
+                        val entry = response.map {
+                            jsonToTrack(it.jsonObject, mangas)
+                        }
+                        entry.firstOrNull()
                     }
-                    entry.firstOrNull()
-                }
+            }
         }
     }
 
     suspend fun getCurrentUser(): Int {
-        return authClient.newCall(GET("$apiUrl/users/whoami"))
-            .awaitSuccess()
-            .parseAs<JsonObject>()
-            .let {
-                it["id"]!!.jsonPrimitive.int
-            }
+        return with(json) {
+            authClient.newCall(GET("$apiUrl/users/whoami"))
+                .awaitSuccess()
+                .parseAs<JsonObject>()
+                .let {
+                    it["id"]!!.jsonPrimitive.int
+                }
+        }
     }
 
     suspend fun accessToken(code: String): OAuth {
         return withIOContext {
-            client.newCall(accessTokenRequest(code))
-                .awaitSuccess()
-                .parseAs()
+            with(json) {
+                client.newCall(accessTokenRequest(code))
+                    .awaitSuccess()
+                    .parseAs()
+            }
         }
     }
 
@@ -164,18 +178,12 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
         private const val loginUrl = "$baseUrl/oauth/authorize"
 
         private const val redirectUrl = "tachiyomi://shikimori-auth"
-        private const val baseMangaUrl = "$apiUrl/mangas"
 
-        fun mangaUrl(remoteId: Int): String {
-            return "$baseMangaUrl/$remoteId"
-        }
-
-        fun authUrl() =
-            loginUrl.toUri().buildUpon()
-                .appendQueryParameter("client_id", clientId)
-                .appendQueryParameter("redirect_uri", redirectUrl)
-                .appendQueryParameter("response_type", "code")
-                .build()
+        fun authUrl() = loginUrl.toUri().buildUpon()
+            .appendQueryParameter("client_id", clientId)
+            .appendQueryParameter("redirect_uri", redirectUrl)
+            .appendQueryParameter("response_type", "code")
+            .build()
 
         fun refreshTokenRequest(token: String) = POST(
             oauthUrl,

+ 16 - 3
app/src/main/java/eu/kanade/tachiyomi/data/track/suwayomi/TachideskApi.kt

@@ -10,6 +10,7 @@ import eu.kanade.tachiyomi.network.NetworkHelper
 import eu.kanade.tachiyomi.network.PUT
 import eu.kanade.tachiyomi.network.awaitSuccess
 import eu.kanade.tachiyomi.network.parseAs
+import kotlinx.serialization.json.Json
 import okhttp3.Credentials
 import okhttp3.Dns
 import okhttp3.FormBody
@@ -23,11 +24,15 @@ import java.nio.charset.Charset
 import java.security.MessageDigest
 
 class TachideskApi {
-    private val network by injectLazy<NetworkHelper>()
+
+    private val network: NetworkHelper by injectLazy()
+    private val json: Json by injectLazy()
+
     val client: OkHttpClient =
         network.client.newBuilder()
             .dns(Dns.SYSTEM) // don't use DNS over HTTPS as it breaks IP addressing
             .build()
+
     fun headersBuilder(): Headers.Builder = Headers.Builder().apply {
         if (basePassword.isNotEmpty() && baseLogin.isNotEmpty()) {
             val credentials = Credentials.basic(baseLogin, basePassword)
@@ -50,7 +55,11 @@ class TachideskApi {
             trackUrl
         }
 
-        val manga = client.newCall(GET("$url/full", headers)).awaitSuccess().parseAs<MangaDataClass>()
+        val manga = with(json) {
+            client.newCall(GET("$url/full", headers))
+                .awaitSuccess()
+                .parseAs<MangaDataClass>()
+        }
 
         TrackSearch.create(TrackManager.SUWAYOMI).apply {
             title = manga.title
@@ -70,7 +79,11 @@ class TachideskApi {
 
     suspend fun updateProgress(track: Track): Track {
         val url = track.tracking_url
-        val chapters = client.newCall(GET("$url/chapters", headers)).awaitSuccess().parseAs<List<ChapterDataClass>>()
+        val chapters = with(json) {
+            client.newCall(GET("$url/chapters", headers))
+                .awaitSuccess()
+                .parseAs<List<ChapterDataClass>>()
+        }
         val lastChapterIndex = chapters.first { it.chapterNumber == track.last_chapter_read }.index
 
         client.newCall(

+ 20 - 15
app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt

@@ -7,6 +7,7 @@ import eu.kanade.tachiyomi.network.NetworkHelper
 import eu.kanade.tachiyomi.network.awaitSuccess
 import eu.kanade.tachiyomi.network.parseAs
 import eu.kanade.tachiyomi.util.system.isInstalledFromFDroid
+import kotlinx.serialization.json.Json
 import tachiyomi.core.preference.Preference
 import tachiyomi.core.preference.PreferenceStore
 import tachiyomi.core.util.lang.withIOContext
@@ -18,6 +19,8 @@ class AppUpdateChecker {
 
     private val networkService: NetworkHelper by injectLazy()
     private val preferenceStore: PreferenceStore by injectLazy()
+    private val json: Json by injectLazy()
+
     private val lastAppCheck: Preference<Long> by lazy {
         preferenceStore.getLong("last_app_check", 0)
     }
@@ -29,24 +32,26 @@ class AppUpdateChecker {
         }
 
         return withIOContext {
-            val result = networkService.client
-                .newCall(GET("https://api.github.com/repos/$GITHUB_REPO/releases/latest"))
-                .awaitSuccess()
-                .parseAs<GithubRelease>()
-                .let {
-                    lastAppCheck.set(Date().time)
-
-                    // Check if latest version is different from current version
-                    if (isNewVersion(it.version)) {
-                        if (context.isInstalledFromFDroid()) {
-                            AppUpdateResult.NewUpdateFdroidInstallation
+            val result = with(json) {
+                networkService.client
+                    .newCall(GET("https://api.github.com/repos/$GITHUB_REPO/releases/latest"))
+                    .awaitSuccess()
+                    .parseAs<GithubRelease>()
+                    .let {
+                        lastAppCheck.set(Date().time)
+
+                        // Check if latest version is different from current version
+                        if (isNewVersion(it.version)) {
+                            if (context.isInstalledFromFDroid()) {
+                                AppUpdateResult.NewUpdateFdroidInstallation
+                            } else {
+                                AppUpdateResult.NewUpdate(it)
+                            }
                         } else {
-                            AppUpdateResult.NewUpdate(it)
+                            AppUpdateResult.NoNewUpdate
                         }
-                    } else {
-                        AppUpdateResult.NoNewUpdate
                     }
-                }
+            }
 
             when (result) {
                 is AppUpdateResult.NewUpdate -> AppUpdateNotifier(context).promptUpdate(result.release)

+ 9 - 4
app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt

@@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.network.NetworkHelper
 import eu.kanade.tachiyomi.network.awaitSuccess
 import eu.kanade.tachiyomi.network.parseAs
 import kotlinx.serialization.Serializable
+import kotlinx.serialization.json.Json
 import logcat.LogPriority
 import tachiyomi.core.preference.Preference
 import tachiyomi.core.preference.PreferenceStore
@@ -24,10 +25,12 @@ internal class ExtensionGithubApi {
 
     private val networkService: NetworkHelper by injectLazy()
     private val preferenceStore: PreferenceStore by injectLazy()
+    private val extensionManager: ExtensionManager by injectLazy()
+    private val json: Json by injectLazy()
+
     private val lastExtCheck: Preference<Long> by lazy {
         preferenceStore.getLong("last_ext_check", 0)
     }
-    private val extensionManager: ExtensionManager by injectLazy()
 
     private var requiresFallbackSource = false
 
@@ -53,9 +56,11 @@ internal class ExtensionGithubApi {
                     .awaitSuccess()
             }
 
-            val extensions = response
-                .parseAs<List<ExtensionJsonObject>>()
-                .toExtensions()
+            val extensions = with(json) {
+                response
+                    .parseAs<List<ExtensionJsonObject>>()
+                    .toExtensions()
+            }
 
             // Sanity check - a small number of extensions probably means something broke
             // with the repo generator

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/ExtensionDetailsScreenModel.kt

@@ -120,7 +120,7 @@ class ExtensionDetailsScreenModel(
 
         val cleared = urls.sumOf {
             try {
-                network.cookieManager.remove(it.toHttpUrl())
+                network.cookieJar.remove(it.toHttpUrl())
             } catch (e: Exception) {
                 logcat(LogPriority.ERROR, e) { "Failed to clear cookies for $it" }
                 0

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/webview/WebViewActivity.kt

@@ -92,7 +92,7 @@ class WebViewActivity : BaseActivity() {
     }
 
     private fun clearCookies(url: String) {
-        val cleared = network.cookieManager.remove(url.toHttpUrl())
+        val cleared = network.cookieJar.remove(url.toHttpUrl())
         logcat { "Cleared $cleared cookies for: $url" }
     }
 

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/webview/WebViewScreenModel.kt

@@ -47,7 +47,7 @@ class WebViewScreenModel(
     }
 
     fun clearCookies(url: String) {
-        val cleared = network.cookieManager.remove(url.toHttpUrl())
+        val cleared = network.cookieJar.remove(url.toHttpUrl())
         logcat { "Cleared $cleared cookies for: $url" }
     }
 }

+ 7 - 4
core/build.gradle.kts

@@ -6,6 +6,13 @@ plugins {
 
 android {
     namespace = "eu.kanade.tachiyomi.core"
+
+    kotlinOptions {
+        freeCompilerArgs += listOf(
+            "-Xcontext-receivers",
+            "-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
+        )
+    }
 }
 
 dependencies {
@@ -24,12 +31,8 @@ dependencies {
     api(kotlinx.serialization.json)
     api(kotlinx.serialization.json.okio)
 
-    api(libs.injekt.core)
-
     api(libs.preferencektx)
 
-    implementation(androidx.corektx)
-
     // JavaScript engine
     implementation(libs.bundles.js.engine)
 }

+ 13 - 11
core/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt

@@ -6,26 +6,30 @@ import eu.kanade.tachiyomi.network.interceptor.UserAgentInterceptor
 import okhttp3.Cache
 import okhttp3.OkHttpClient
 import okhttp3.logging.HttpLoggingInterceptor
-import uy.kohesive.injekt.injectLazy
 import java.io.File
 import java.util.concurrent.TimeUnit
 
-class NetworkHelper(context: Context) {
-
-    private val preferences: NetworkPreferences by injectLazy()
+class NetworkHelper(
+    context: Context,
+    private val preferences: NetworkPreferences,
+) {
 
     private val cacheDir = File(context.cacheDir, "network_cache")
     private val cacheSize = 5L * 1024 * 1024 // 5 MiB
 
-    val cookieManager = AndroidCookieJar()
+    val cookieJar = AndroidCookieJar()
 
-    private val userAgentInterceptor by lazy { UserAgentInterceptor() }
-    private val cloudflareInterceptor by lazy { CloudflareInterceptor(context) }
+    private val userAgentInterceptor by lazy {
+        UserAgentInterceptor(::defaultUserAgentProvider)
+    }
+    private val cloudflareInterceptor by lazy {
+        CloudflareInterceptor(context, cookieJar, ::defaultUserAgentProvider)
+    }
 
     private val baseClientBuilder: OkHttpClient.Builder
         get() {
             val builder = OkHttpClient.Builder()
-                .cookieJar(cookieManager)
+                .cookieJar(cookieJar)
                 .connectTimeout(30, TimeUnit.SECONDS)
                 .readTimeout(30, TimeUnit.SECONDS)
                 .callTimeout(2, TimeUnit.MINUTES)
@@ -65,7 +69,5 @@ class NetworkHelper(context: Context) {
             .build()
     }
 
-    val defaultUserAgent by lazy {
-        preferences.defaultUserAgent().get().trim()
-    }
+    fun defaultUserAgentProvider() = preferences.defaultUserAgent().get().trim()
 }

+ 7 - 6
core/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt

@@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.network
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.suspendCancellableCoroutine
 import kotlinx.serialization.DeserializationStrategy
-import kotlinx.serialization.ExperimentalSerializationApi
 import kotlinx.serialization.json.Json
 import kotlinx.serialization.json.okio.decodeFromBufferedSource
 import kotlinx.serialization.serializer
@@ -16,8 +15,6 @@ import okhttp3.Response
 import rx.Observable
 import rx.Producer
 import rx.Subscription
-import uy.kohesive.injekt.Injekt
-import uy.kohesive.injekt.api.get
 import java.io.IOException
 import java.util.concurrent.atomic.AtomicBoolean
 import kotlin.coroutines.resumeWithException
@@ -131,14 +128,18 @@ fun OkHttpClient.newCachelessCallWithProgress(request: Request, listener: Progre
     return progressClient.newCall(request)
 }
 
+context(Json)
 inline fun <reified T> Response.parseAs(): T {
     return decodeFromJsonResponse(serializer(), this)
 }
 
-@OptIn(ExperimentalSerializationApi::class)
-fun <T> decodeFromJsonResponse(deserializer: DeserializationStrategy<T>, response: Response): T {
+context(Json)
+fun <T> decodeFromJsonResponse(
+    deserializer: DeserializationStrategy<T>,
+    response: Response,
+): T {
     return response.body.source().use {
-        Injekt.get<Json>().decodeFromBufferedSource(deserializer, it)
+        decodeFromBufferedSource(deserializer, it)
     }
 }
 

+ 9 - 8
core/src/main/java/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt

@@ -6,7 +6,7 @@ import android.webkit.WebView
 import android.widget.Toast
 import androidx.core.content.ContextCompat
 import eu.kanade.tachiyomi.core.R
-import eu.kanade.tachiyomi.network.NetworkHelper
+import eu.kanade.tachiyomi.network.AndroidCookieJar
 import eu.kanade.tachiyomi.util.system.WebViewClientCompat
 import eu.kanade.tachiyomi.util.system.isOutdated
 import eu.kanade.tachiyomi.util.system.toast
@@ -15,16 +15,17 @@ import okhttp3.HttpUrl.Companion.toHttpUrl
 import okhttp3.Interceptor
 import okhttp3.Request
 import okhttp3.Response
-import uy.kohesive.injekt.injectLazy
 import java.io.IOException
 import java.util.concurrent.CountDownLatch
 
-class CloudflareInterceptor(private val context: Context) : WebViewInterceptor(context) {
+class CloudflareInterceptor(
+    private val context: Context,
+    private val cookieManager: AndroidCookieJar,
+    defaultUserAgentProvider: () -> String,
+) : WebViewInterceptor(context, defaultUserAgentProvider) {
 
     private val executor = ContextCompat.getMainExecutor(context)
 
-    private val networkHelper: NetworkHelper by injectLazy()
-
     override fun shouldIntercept(response: Response): Boolean {
         // Check if Cloudflare anti-bot is on
         return response.code in ERROR_CODES && response.header("Server") in SERVER_CHECK
@@ -33,8 +34,8 @@ class CloudflareInterceptor(private val context: Context) : WebViewInterceptor(c
     override fun intercept(chain: Interceptor.Chain, request: Request, response: Response): Response {
         try {
             response.close()
-            networkHelper.cookieManager.remove(request.url, COOKIE_NAMES, 0)
-            val oldCookie = networkHelper.cookieManager.get(request.url)
+            cookieManager.remove(request.url, COOKIE_NAMES, 0)
+            val oldCookie = cookieManager.get(request.url)
                 .firstOrNull { it.name == "cf_clearance" }
             resolveWithWebView(request, oldCookie)
 
@@ -70,7 +71,7 @@ class CloudflareInterceptor(private val context: Context) : WebViewInterceptor(c
             webview?.webViewClient = object : WebViewClientCompat() {
                 override fun onPageFinished(view: WebView, url: String) {
                     fun isCloudFlareBypassed(): Boolean {
-                        return networkHelper.cookieManager.get(origRequestUrl.toHttpUrl())
+                        return cookieManager.get(origRequestUrl.toHttpUrl())
                             .firstOrNull { it.name == "cf_clearance" }
                             .let { it != null && it != oldCookie }
                     }

+ 4 - 6
core/src/main/java/eu/kanade/tachiyomi/network/interceptor/UserAgentInterceptor.kt

@@ -1,13 +1,11 @@
 package eu.kanade.tachiyomi.network.interceptor
 
-import eu.kanade.tachiyomi.network.NetworkHelper
 import okhttp3.Interceptor
 import okhttp3.Response
-import uy.kohesive.injekt.injectLazy
 
-class UserAgentInterceptor : Interceptor {
-
-    private val networkHelper: NetworkHelper by injectLazy()
+class UserAgentInterceptor(
+    private val defaultUserAgentProvider: () -> String,
+) : Interceptor {
 
     override fun intercept(chain: Interceptor.Chain): Response {
         val originalRequest = chain.request()
@@ -16,7 +14,7 @@ class UserAgentInterceptor : Interceptor {
             val newRequest = originalRequest
                 .newBuilder()
                 .removeHeader("User-Agent")
-                .addHeader("User-Agent", networkHelper.defaultUserAgent)
+                .addHeader("User-Agent", defaultUserAgentProvider())
                 .build()
             chain.proceed(newRequest)
         } else {

+ 5 - 6
core/src/main/java/eu/kanade/tachiyomi/network/interceptor/WebViewInterceptor.kt

@@ -6,7 +6,6 @@ import android.webkit.WebSettings
 import android.webkit.WebView
 import android.widget.Toast
 import eu.kanade.tachiyomi.core.R
-import eu.kanade.tachiyomi.network.NetworkHelper
 import eu.kanade.tachiyomi.util.system.DeviceUtil
 import eu.kanade.tachiyomi.util.system.WebViewUtil
 import eu.kanade.tachiyomi.util.system.setDefaultSettings
@@ -16,14 +15,14 @@ import okhttp3.Interceptor
 import okhttp3.Request
 import okhttp3.Response
 import tachiyomi.core.util.lang.launchUI
-import uy.kohesive.injekt.injectLazy
 import java.util.Locale
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.TimeUnit
 
-abstract class WebViewInterceptor(private val context: Context) : Interceptor {
-
-    private val networkHelper: NetworkHelper by injectLazy()
+abstract class WebViewInterceptor(
+    private val context: Context,
+    private val defaultUserAgentProvider: () -> String,
+) : Interceptor {
 
     /**
      * When this is called, it initializes the WebView if it wasn't already. We use this to avoid
@@ -85,7 +84,7 @@ abstract class WebViewInterceptor(private val context: Context) : Interceptor {
         return WebView(context).apply {
             setDefaultSettings()
             // Avoid sending empty User-Agent, Chromium WebView will reset to default if empty
-            settings.userAgentString = request.header("User-Agent") ?: networkHelper.defaultUserAgent
+            settings.userAgentString = request.header("User-Agent") ?: defaultUserAgentProvider()
         }
     }
 }

+ 1 - 0
data/build.gradle.kts

@@ -23,6 +23,7 @@ dependencies {
     implementation(project(":source-api"))
     implementation(project(":domain"))
     implementation(project(":core"))
+
     api(libs.sqldelight.android.driver)
     api(libs.sqldelight.coroutines)
     api(libs.sqldelight.android.paging)

+ 21 - 1
data/src/main/java/tachiyomi/data/updates/UpdatesRepositoryImpl.kt

@@ -6,12 +6,32 @@ import tachiyomi.domain.updates.model.UpdatesWithRelations
 import tachiyomi.domain.updates.repository.UpdatesRepository
 
 class UpdatesRepositoryImpl(
-    val databaseHandler: DatabaseHandler,
+    private val databaseHandler: DatabaseHandler,
 ) : UpdatesRepository {
 
+    override suspend fun awaitWithRead(read: Boolean, after: Long): List<UpdatesWithRelations> {
+        return databaseHandler.awaitList {
+            updatesViewQueries.getUpdatesByReadStatus(
+                read = read,
+                after = after,
+                mapper = updateWithRelationMapper,
+            )
+        }
+    }
+
     override fun subscribeAll(after: Long): Flow<List<UpdatesWithRelations>> {
         return databaseHandler.subscribeToList {
             updatesViewQueries.updates(after, updateWithRelationMapper)
         }
     }
+
+    override fun subscribeWithRead(read: Boolean, after: Long): Flow<List<UpdatesWithRelations>> {
+        return databaseHandler.subscribeToList {
+            updatesViewQueries.getUpdatesByReadStatus(
+                read = read,
+                after = after,
+                mapper = updateWithRelationMapper,
+            )
+        }
+    }
 }

+ 3 - 3
domain/build.gradle.kts

@@ -13,11 +13,11 @@ android {
 }
 
 dependencies {
-    implementation(platform(kotlinx.coroutines.bom))
-    implementation(kotlinx.bundles.coroutines)
-
     implementation(project(":source-api"))
     implementation(project(":core"))
 
+    implementation(platform(kotlinx.coroutines.bom))
+    implementation(kotlinx.bundles.coroutines)
+
     testImplementation(libs.junit)
 }

+ 8 - 0
domain/src/main/java/tachiyomi/domain/updates/interactor/GetUpdates.kt

@@ -9,9 +9,17 @@ class GetUpdates(
     private val repository: UpdatesRepository,
 ) {
 
+    suspend fun await(read: Boolean, after: Long): List<UpdatesWithRelations> {
+        return repository.awaitWithRead(read, after)
+    }
+
     fun subscribe(calendar: Calendar): Flow<List<UpdatesWithRelations>> = subscribe(calendar.time.time)
 
     fun subscribe(after: Long): Flow<List<UpdatesWithRelations>> {
         return repository.subscribeAll(after)
     }
+
+    fun subscribe(read: Boolean, after: Long): Flow<List<UpdatesWithRelations>> {
+        return repository.subscribeWithRead(read, after)
+    }
 }

+ 4 - 0
domain/src/main/java/tachiyomi/domain/updates/repository/UpdatesRepository.kt

@@ -5,5 +5,9 @@ import tachiyomi.domain.updates.model.UpdatesWithRelations
 
 interface UpdatesRepository {
 
+    suspend fun awaitWithRead(read: Boolean, after: Long): List<UpdatesWithRelations>
+
     fun subscribeAll(after: Long): Flow<List<UpdatesWithRelations>>
+
+    fun subscribeWithRead(read: Boolean, after: Long): Flow<List<UpdatesWithRelations>>
 }

+ 1 - 1
presentation-widget/build.gradle.kts

@@ -22,11 +22,11 @@ android {
 
 dependencies {
     implementation(project(":core"))
-    implementation(project(":data"))
     implementation(project(":domain"))
     implementation(project(":presentation-core"))
 
     implementation(androidx.glance)
 
     implementation(libs.coil.core)
+    api(libs.injekt.core)
 }

+ 6 - 10
presentation-widget/src/main/java/tachiyomi/presentation/widget/TachiyomiWidgetManager.kt

@@ -7,21 +7,17 @@ import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.drop
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
-import tachiyomi.data.DatabaseHandler
-import uy.kohesive.injekt.Injekt
-import uy.kohesive.injekt.api.get
+import tachiyomi.domain.updates.interactor.GetUpdates
 
 class TachiyomiWidgetManager(
-    private val database: DatabaseHandler = Injekt.get(),
+    private val getUpdates: GetUpdates,
 ) {
 
     fun Context.init(scope: LifecycleCoroutineScope) {
-        database.subscribeToList {
-            updatesViewQueries.getUpdatesByReadStatus(
-                read = false,
-                after = UpdatesGridGlanceWidget.DateLimit.timeInMillis,
-            )
-        }
+        getUpdates.subscribe(
+            read = false,
+            after = UpdatesGridGlanceWidget.DateLimit.timeInMillis,
+        )
             .drop(1)
             .distinctUntilChanged()
             .onEach {

+ 13 - 15
presentation-widget/src/main/java/tachiyomi/presentation/widget/UpdatesGridGlanceWidget.kt

@@ -25,13 +25,13 @@ import eu.kanade.tachiyomi.core.security.SecurityPreferences
 import eu.kanade.tachiyomi.util.system.dpToPx
 import kotlinx.coroutines.MainScope
 import tachiyomi.core.util.lang.launchIO
-import tachiyomi.data.DatabaseHandler
 import tachiyomi.domain.manga.model.MangaCover
+import tachiyomi.domain.updates.interactor.GetUpdates
+import tachiyomi.domain.updates.model.UpdatesWithRelations
 import tachiyomi.presentation.widget.components.CoverHeight
 import tachiyomi.presentation.widget.components.CoverWidth
 import tachiyomi.presentation.widget.components.LockedWidget
 import tachiyomi.presentation.widget.components.UpdatesWidget
-import tachiyomi.view.UpdatesView
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
 import uy.kohesive.injekt.injectLazy
@@ -39,6 +39,7 @@ import java.util.Calendar
 import java.util.Date
 
 class UpdatesGridGlanceWidget : GlanceAppWidget() {
+
     private val app: Application by injectLazy()
     private val preferences: SecurityPreferences by injectLazy()
 
@@ -58,7 +59,7 @@ class UpdatesGridGlanceWidget : GlanceAppWidget() {
         UpdatesWidget(data)
     }
 
-    fun loadData(list: List<UpdatesView>? = null) {
+    fun loadData(list: List<UpdatesWithRelations>? = null) {
         coroutineScope.launchIO {
             // Don't show anything when lock is active
             if (preferences.useAuthenticator().get()) {
@@ -71,13 +72,10 @@ class UpdatesGridGlanceWidget : GlanceAppWidget() {
             if (ids.isEmpty()) return@launchIO
 
             val processList = list
-                ?: Injekt.get<DatabaseHandler>()
-                    .awaitList {
-                        updatesViewQueries.getUpdatesByReadStatus(
-                            read = false,
-                            after = DateLimit.timeInMillis,
-                        )
-                    }
+                ?: Injekt.get<GetUpdates>().await(
+                    read = false,
+                    after = DateLimit.timeInMillis,
+                )
             val (rowCount, columnCount) = ids
                 .flatMap { manager.getAppWidgetSizes(it) }
                 .maxBy { it.height.value * it.width.value }
@@ -88,7 +86,7 @@ class UpdatesGridGlanceWidget : GlanceAppWidget() {
         }
     }
 
-    private fun prepareList(processList: List<UpdatesView>, take: Int): List<Pair<Long, Bitmap?>> {
+    private fun prepareList(processList: List<UpdatesWithRelations>, take: Int): List<Pair<Long, Bitmap?>> {
         // Resize to cover size
         val widthPx = CoverWidth.value.toInt().dpToPx
         val heightPx = CoverHeight.value.toInt().dpToPx
@@ -101,10 +99,10 @@ class UpdatesGridGlanceWidget : GlanceAppWidget() {
                     .data(
                         MangaCover(
                             mangaId = updatesView.mangaId,
-                            sourceId = updatesView.source,
-                            isMangaFavorite = updatesView.favorite,
-                            url = updatesView.thumbnailUrl,
-                            lastModified = updatesView.coverLastModified,
+                            sourceId = updatesView.sourceId,
+                            isMangaFavorite = true,
+                            url = updatesView.coverData.url,
+                            lastModified = updatesView.coverData.lastModified,
                         ),
                     )
                     .memoryCachePolicy(CachePolicy.DISABLED)

+ 1 - 7
source-api/build.gradle.kts

@@ -10,20 +10,14 @@ android {
     defaultConfig {
         consumerProguardFile("consumer-proguard.pro")
     }
-
 }
 
 dependencies {
-
     implementation(project(":core"))
 
     api(kotlinx.serialization.json)
-
+    api(libs.injekt.core)
     api(libs.rxjava)
-
     api(libs.preferencektx)
-
     api(libs.jsoup)
-
-    implementation(androidx.corektx)
 }

+ 1 - 1
source-api/src/main/java/eu/kanade/tachiyomi/source/online/HttpSource.kt

@@ -68,7 +68,7 @@ abstract class HttpSource : CatalogueSource {
      * Headers builder for requests. Implementations can override this method for custom headers.
      */
     protected open fun headersBuilder() = Headers.Builder().apply {
-        add("User-Agent", network.defaultUserAgent)
+        add("User-Agent", network.defaultUserAgentProvider())
     }
 
     /**