refactor: Don't use context receiver

Deprecated on Kotlin 2.x, scheduled for removal in v2.1.x, will be replaced with context parameters

REF: https://github.com/Kotlin/KEEP/issues/259#issuecomment-2278319746
REF: https://youtrack.jetbrains.com/issue/KT-67119/Migration-warning-from-context-receivers-to-context-parameters
REF: https://github.com/Kotlin/KEEP/issues/367
This commit is contained in:
Ahmad Ansori Palembani 2025-01-01 07:10:30 +07:00
parent 1b92ae2e5f
commit d02f1bdd11
Signed by: null2264
GPG key ID: BA64F8B60AF3EFB6
17 changed files with 265 additions and 347 deletions

View file

@ -278,7 +278,6 @@ tasks {
// See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api(-markers)
withType<KotlinCompile> {
compilerOptions.freeCompilerArgs.addAll(
"-Xcontext-receivers",
// "-opt-in=kotlin.Experimental",
"-opt-in=kotlin.RequiresOptIn",
"-opt-in=kotlin.ExperimentalStdlibApi",

View file

@ -49,7 +49,6 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
put("completedAt", createDate(track.finished_reading_date))
}
}
with(json) {
authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime)))
.awaitSuccess()
.parseAs<ALAddMangaResult>()
@ -59,7 +58,6 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
}
}
}
}
suspend fun updateLibraryManga(track: Track): Track {
return withIOContext {
@ -74,13 +72,11 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
put("completedAt", createDate(track.finished_reading_date))
}
}
with(json) {
authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime)))
.awaitSuccess()
track
}
}
}
suspend fun search(search: String): List<TrackSearch> {
return withIOContext {
@ -90,7 +86,6 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
put("query", search)
}
}
with(json) {
authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime)))
.awaitSuccess()
.parseAs<ALSearchResult>()
@ -98,7 +93,6 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
.map { it.toALManga().toTrack() }
}
}
}
suspend fun findLibManga(track: Track, userid: Int): Track? {
return withIOContext {
@ -109,7 +103,6 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
put("manga_id", track.media_id)
}
}
with(json) {
authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime)))
.awaitSuccess()
.parseAs<ALUserListMangaQueryResult>()
@ -119,7 +112,6 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
?.toTrack()
}
}
}
suspend fun getLibManga(track: Track, userid: Int): Track {
return findLibManga(track, userid) ?: throw Exception("Could not find manga")

View file

@ -75,7 +75,6 @@ class BangumiApi(
.appendQueryParameter("responseGroup", "large")
.appendQueryParameter("max_results", "20")
.build()
with(json) {
authClient.newCall(GET(url.toString()))
.awaitSuccess()
.parseAs<BGMSearchResult>()
@ -88,18 +87,15 @@ class BangumiApi(
}
}
}
}
suspend fun findLibManga(track: Track): Track? {
return withIOContext {
with(json) {
authClient.newCall(GET("$API_URL/subject/${track.media_id}"))
.awaitSuccess()
.parseAs<BGMSearchItem>()
.toTrackSearch(trackId)
}
}
}
suspend fun statusLibManga(track: Track): Track? {
return withIOContext {
@ -111,7 +107,6 @@ class BangumiApi(
.build()
// TODO: get user readed chapter here
with(json) {
authClient.newCall(requestUserRead)
.awaitSuccess()
.parseAs<BGMCollectionResponse>()
@ -125,17 +120,14 @@ class BangumiApi(
}
}
}
}
suspend fun accessToken(code: String): BGMOAuth {
return withIOContext {
with(json) {
client.newCall(accessTokenRequest(code))
.awaitSuccess()
.parseAs()
}
}
}
private fun accessTokenRequest(code: String) = POST(
OAUTH_URL,

View file

@ -44,7 +44,7 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
try {
client.newCall(request).execute().use {
when (it.code) {
200 -> return with(json) { it.parseAs<AuthenticationDto>().token }
200 -> return it.parseAs<AuthenticationDto>().token
401 -> {
Logger.w { "Unauthorized / api key not valid: Cleaned api URL: $apiUrl, Api key is empty: ${apiKey.isEmpty()}" }
throw IOException("Unauthorized / api key not valid")
@ -89,11 +89,10 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
private fun getTotalChapters(url: String): Long {
val requestUrl = getApiVolumesUrl(url)
try {
val listVolumeDto = with(json) {
val listVolumeDto =
authClient.newCall(GET(requestUrl))
.execute()
.parseAs<List<VolumeDto>>()
}
var volumeNumber = 0L
var maxChapterNumber = 0L
for (volume in listVolumeDto) {
@ -117,9 +116,7 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
try {
authClient.newCall(GET(requestUrl)).execute().use {
if (it.code == 200) {
return with(json) {
it.parseAs<ChapterDto>().number!!.replace(",", ".").toFloat()
}
return it.parseAs<ChapterDto>().number!!.replace(",", ".").toFloat()
}
if (it.code == 204) {
return 0F
@ -134,11 +131,10 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
suspend fun getTrackSearch(url: String): TrackSearch = withIOContext {
try {
val serieDto: SeriesDto = with(json) {
val serieDto: SeriesDto =
authClient.newCall(GET(url))
.awaitSuccess()
.parseAs()
}
val track = serieDto.toTrack()
track.apply {

View file

@ -131,7 +131,6 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
suspend fun search(query: String): List<TrackSearch> {
return withIOContext {
with(json) {
authClient.newCall(GET(ALGOLIA_KEY_URL))
.awaitSuccess()
.parseAs<KitsuSearchResult>()
@ -140,14 +139,12 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
}
}
}
}
private suspend fun algoliaSearch(key: String, query: String): List<TrackSearch> {
return withIOContext {
val jsonObject = buildJsonObject {
put("params", "query=$query$ALGOLIA_FILTER")
}
with(json) {
client.newCall(
POST(
ALGOLIA_URL,
@ -167,7 +164,6 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
.map { it.toTrack() }
}
}
}
suspend fun findLibManga(track: Track, userId: String): Track? {
return withIOContext {
@ -175,7 +171,6 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
.encodedQuery("filter[manga_id]=${track.media_id}&filter[user_id]=$userId")
.appendQueryParameter("include", "manga")
.build()
with(json) {
authClient.newCall(GET(url.toString()))
.awaitSuccess()
.parseAs<KitsuListSearchResult>()
@ -188,7 +183,6 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
}
}
}
}
suspend fun getLibManga(track: Track): Track {
return withIOContext {
@ -196,7 +190,6 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
.encodedQuery("filter[id]=${track.media_id}")
.appendQueryParameter("include", "manga")
.build()
with(json) {
authClient.newCall(GET(url.toString()))
.awaitSuccess()
.parseAs<KitsuListSearchResult>()
@ -209,7 +202,6 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
}
}
}
}
suspend fun login(username: String, password: String): KitsuOAuth {
return withIOContext {
@ -220,20 +212,17 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
.add("client_id", CLIENT_ID)
.add("client_secret", CLIENT_SECRET)
.build()
with(json) {
client.newCall(POST(LOGIN_URL, body = formBody))
.awaitSuccess()
.parseAs()
}
}
}
suspend fun getCurrentUser(): String {
return withIOContext {
val url = "${BASE_URL}users".toUri().buildUpon()
.encodedQuery("filter[self]=true")
.build()
with(json) {
authClient.newCall(GET(url.toString()))
.awaitSuccess()
.parseAs<KitsuCurrentUserResult>()
@ -241,7 +230,6 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
.id
}
}
}
companion object {
private const val CLIENT_ID =

View file

@ -26,7 +26,6 @@ class KomgaApi(private val client: OkHttpClient) {
withIOContext {
try {
val track =
with(json) {
if (url.contains(READLIST_API)) {
client.newCall(GET(url))
.awaitSuccess()
@ -38,9 +37,8 @@ class KomgaApi(private val client: OkHttpClient) {
.parseAs<SeriesDto>()
.toTrack()
}
}
val progress = with(json) {
val progress =
client
.newCall(
GET(
@ -59,7 +57,6 @@ class KomgaApi(private val client: OkHttpClient) {
it.parseAs<ReadProgressDto>().toV2()
}
}
}
track.apply {
cover_url = "$url/thumbnail"
tracking_url = url

View file

@ -37,7 +37,6 @@ class MangaUpdatesApi(
suspend fun getSeriesListItem(track: Track): Pair<MUListItem, MURating?> {
val listItem =
with(json) {
authClient.newCall(
GET(
url = "$BASE_URL/v1/lists/series/${track.media_id}",
@ -45,7 +44,6 @@ class MangaUpdatesApi(
)
.awaitSuccess()
.parseAs<MUListItem>()
}
val rating = getSeriesRating(track)
@ -104,7 +102,6 @@ class MangaUpdatesApi(
private suspend fun getSeriesRating(track: Track): MURating? {
return try {
with(json) {
authClient.newCall(
GET(
url = "$BASE_URL/v1/series/${track.media_id}/rating",
@ -112,7 +109,6 @@ class MangaUpdatesApi(
)
.awaitSuccess()
.parseAs<MURating>()
}
} catch (e: Exception) {
null
}
@ -151,8 +147,7 @@ class MangaUpdatesApi(
},
)
}
return with(json) {
client.newCall(
return client.newCall(
POST(
url = "$BASE_URL/v1/series/search",
body = body.toString().toRequestBody(CONTENT_TYPE),
@ -163,15 +158,13 @@ class MangaUpdatesApi(
.results
.map { it.record }
}
}
suspend fun authenticate(username: String, password: String): MUContext? {
val body = buildJsonObject {
put("username", username)
put("password", password)
}
return with(json) {
client.newCall(
return client.newCall(
PUT(
url = "$BASE_URL/v1/account/login",
body = body.toString().toRequestBody(CONTENT_TYPE),
@ -181,7 +174,6 @@ class MangaUpdatesApi(
.parseAs<MULoginResponse>()
.context
}
}
companion object {
private const val BASE_URL = "https://api.mangaupdates.com"

View file

@ -45,13 +45,11 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.add("code_verifier", codeVerifier)
.add("grant_type", "authorization_code")
.build()
with(json) {
client.newCall(POST("$BASE_OAUTH_URL/token", body = formBody))
.awaitSuccess()
.parseAs()
}
}
}
suspend fun getCurrentUser(): String {
return withIOContext {
@ -59,14 +57,12 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.url("$BASE_API_URL/users/@me")
.get()
.build()
with(json) {
authClient.newCall(request)
.awaitSuccess()
.parseAs<MALUser>()
.name
}
}
}
suspend fun search(query: String): List<TrackSearch> {
return withIOContext {
@ -75,7 +71,6 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.appendQueryParameter("q", query.take(64))
.appendQueryParameter("nsfw", "true")
.build()
with(json) {
authClient.newCall(GET(url.toString()))
.awaitSuccess()
.parseAs<MALSearchResult>()
@ -85,7 +80,6 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.filter { !it.publishing_type.contains("novel") }
}
}
}
suspend fun getMangaDetails(id: Int): TrackSearch {
return withIOContext {
@ -93,7 +87,6 @@ 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()
with(json) {
authClient.newCall(GET(url.toString()))
.awaitSuccess()
.parseAs<MALManga>()
@ -112,7 +105,6 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
}
}
}
}
suspend fun updateItem(track: Track): Track {
return withIOContext {
@ -132,14 +124,12 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.url(mangaUrl(track.media_id).toString())
.put(formBodyBuilder.build())
.build()
with(json) {
authClient.newCall(request)
.awaitSuccess()
.parseAs<MALListItemStatus>()
.let { parseMangaItem(it, track) }
}
}
}
suspend fun findListItem(track: Track): Track? {
return withIOContext {
@ -147,7 +137,6 @@ 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()
with(json) {
authClient.newCall(GET(uri.toString()))
.awaitSuccess()
.parseAs<MALListItem>()
@ -157,7 +146,6 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
}
}
}
}
suspend fun findListItems(query: String, offset: Int = 0): List<TrackSearch> {
return withIOContext {
@ -190,13 +178,11 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.url(urlBuilder.build().toString())
.get()
.build()
with(json) {
authClient.newCall(request)
.awaitSuccess()
.parseAs()
}
}
}
private fun parseMangaItem(listStatus: MALListItemStatus, track: Track): Track {
return track.apply {

View file

@ -66,7 +66,7 @@ class MyAnimeListInterceptor(private val myanimelist: MyAnimeList) : Interceptor
return runCatching {
if (response.isSuccessful) {
with(json) { response.parseAs<MALOAuth>() }
response.parseAs<MALOAuth>()
} else {
response.close()
null

View file

@ -74,14 +74,12 @@ class ShikimoriApi(
.appendQueryParameter("search", search)
.appendQueryParameter("limit", "20")
.build()
with(json) {
authClient.newCall(GET(url.toString()))
.awaitSuccess()
.parseAs<List<SMManga>>()
.map { it.toTrack(trackId) }
}
}
}
suspend fun remove(track: Track): Boolean {
return try {
@ -102,18 +100,16 @@ class ShikimoriApi(
val urlMangas = "$API_URL/mangas".toUri().buildUpon()
.appendPath(track.media_id.toString())
.build()
val manga = with(json) {
val manga =
authClient.newCall(GET(urlMangas.toString()))
.awaitSuccess()
.parseAs<SMManga>()
}
val url = "$API_URL/v2/user_rates".toUri().buildUpon()
.appendQueryParameter("user_id", user_id)
.appendQueryParameter("target_id", track.media_id.toString())
.appendQueryParameter("target_type", "Manga")
.build()
with(json) {
authClient.newCall(GET(url.toString()))
.execute()
.parseAs<List<SMUserListEntry>>()
@ -127,28 +123,23 @@ class ShikimoriApi(
}
}
}
}
suspend fun getCurrentUser(): Int {
return withIOContext {
with(json) {
authClient.newCall(GET("$API_URL/users/whoami"))
.awaitSuccess()
.parseAs<SMUser>()
.id
}
}
}
suspend fun accessToken(code: String): SMOAuth {
return withIOContext {
with(json) {
client.newCall(accessTokenRequest(code))
.awaitSuccess()
.parseAs()
}
}
}
private fun accessTokenRequest(code: String) = POST(
OAUTH_URL,

View file

@ -52,9 +52,7 @@ class TachideskApi {
trackUrl
}
val manga = with(json) {
client.newCall(GET("$url/full", headers)).awaitSuccess().parseAs<MangaDataClass>()
}
val manga = client.newCall(GET("$url/full", headers)).awaitSuccess().parseAs<MangaDataClass>()
TrackSearch.create(TrackManager.SUWAYOMI).apply {
title = manga.title
@ -74,9 +72,7 @@ class TachideskApi {
suspend fun updateProgress(track: Track): Track {
val url = track.tracking_url
val chapters = with(json) {
client.newCall(GET("$url/chapters", headers)).awaitSuccess().parseAs<List<ChapterDataClass>>()
}
val chapters = client.newCall(GET("$url/chapters", headers)).awaitSuccess().parseAs<List<ChapterDataClass>>()
val lastChapterIndex = chapters.first { it.chapterNumber == track.last_chapter_read }.index
client.newCall(

View file

@ -11,12 +11,12 @@ import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.network.parseAs
import eu.kanade.tachiyomi.util.system.localeContext
import eu.kanade.tachiyomi.util.system.withIOContext
import java.util.Date
import java.util.concurrent.TimeUnit
import kotlinx.serialization.json.Json
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import yokai.domain.base.models.Version
import java.util.*
import java.util.concurrent.*
class AppUpdateChecker(
private val json: Json = Injekt.get(),
@ -31,8 +31,7 @@ class AppUpdateChecker(
}
return withIOContext {
val result = with(json) {
if (preferences.checkForBetas().get()) {
val result = if (preferences.checkForBetas().get()) {
networkService.client
.newCall(GET("https://api.github.com/repos/$GITHUB_REPO/releases"))
.await()
@ -73,7 +72,6 @@ class AppUpdateChecker(
}
}
}
}
if (doExtrasAfterNewUpdate && result is AppUpdateResult.NewUpdate) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
preferences.appShouldAutoUpdate().get() != AppDownloadInstallJob.NEVER

View file

@ -14,7 +14,6 @@ import eu.kanade.tachiyomi.util.system.withIOContext
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
@ -24,7 +23,6 @@ import yokai.domain.extension.repo.model.ExtensionRepo
internal class ExtensionApi {
private val json: Json by injectLazy()
private val networkService: NetworkHelper by injectLazy()
private val getExtensionRepo: GetExtensionRepo by injectLazy()
private val updateExtensionRepo: UpdateExtensionRepo by injectLazy()
@ -47,11 +45,9 @@ internal class ExtensionApi {
.newCall(GET("$repoBaseUrl/index.min.json"))
.awaitSuccess()
with(json) {
response
.parseAs<List<ExtensionJsonObject>>()
.toExtensions(repoBaseUrl)
}
} catch (e: Throwable) {
Logger.e(e) { "Failed to get extensions from $repoBaseUrl" }
emptyList()

View file

@ -25,6 +25,7 @@ import eu.kanade.tachiyomi.util.chapter.ChapterFilter
import eu.kanade.tachiyomi.util.manga.MangaShortcutManager
import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory
import kotlinx.serialization.json.Json
import kotlinx.serialization.protobuf.ProtoBuf
import nl.adaptivity.xmlutil.XmlDeclMode
import nl.adaptivity.xmlutil.core.XmlVersion
import nl.adaptivity.xmlutil.serialization.XML
@ -145,6 +146,9 @@ fun appModule(app: Application) = module {
xmlVersion = XmlVersion.XML10
}
}
single<ProtoBuf> {
ProtoBuf
}
single { ChapterFilter() }

View file

@ -6,17 +6,13 @@ import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.network.parseAs
import eu.kanade.tachiyomi.util.system.withIOContext
import kotlinx.serialization.json.Json
import okhttp3.OkHttpClient
import uy.kohesive.injekt.injectLazy
import yokai.domain.extension.repo.model.ExtensionRepo
class ExtensionRepoService(
private val client: OkHttpClient,
) {
private val json: Json by injectLazy()
suspend fun fetchRepoDetails(
repo: String,
): ExtensionRepo? {
@ -24,12 +20,10 @@ class ExtensionRepoService(
val url = "$repo/repo.json".toUri()
try {
with(json) {
client.newCall(GET(url.toString()))
.awaitSuccess()
.parseAs<ExtensionRepoMetaDto>()
.toExtensionRepo(baseUrl = repo)
}
} catch (e: Exception) {
Logger.e(e) { "Failed to fetch repo details" }
null

View file

@ -66,7 +66,6 @@ android {
tasks {
withType<KotlinCompile> {
compilerOptions.freeCompilerArgs.addAll(
"-Xcontext-receivers",
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
)

View file

@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.network
import java.io.IOException
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.coroutines.resumeWithException
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.json.Json
@ -18,6 +17,8 @@ import okhttp3.Response
import rx.Observable
import rx.Producer
import rx.Subscription
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
val jsonMime = "application/json; charset=utf-8".toMediaType()
@ -68,7 +69,6 @@ fun Call.asObservableSuccess(): Observable<Response> {
}
// Based on https://github.com/gildor/kotlin-coroutines-okhttp
@OptIn(ExperimentalCoroutinesApi::class)
private suspend fun Call.await(callStack: Array<StackTraceElement>): Response {
return suspendCancellableCoroutine { continuation ->
val callback =
@ -131,13 +131,11 @@ fun OkHttpClient.newCachelessCallWithProgress(request: Request, listener: Progre
return progressClient.newCall(request)
}
context(Json)
inline fun <reified T> Response.parseAs(): T {
return decodeFromJsonResponse(serializer(), this)
return Injekt.get<Json>().decodeFromJsonResponse(serializer(), this)
}
context(Json)
fun <T> decodeFromJsonResponse(
fun <T> Json.decodeFromJsonResponse(
deserializer: DeserializationStrategy<T>,
response: Response,
): T {