Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions core/common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ dependencies {
implementation(projects.i18nSy)
// SY <--

// KMK --> Multiplatform preference abstraction (re-exported so existing
// tachiyomi.core.common.preference.* imports keep resolving)
api(projects.core.preference)
// KMK <--

api(libs.logcat)

api(libs.rxjava)
Expand Down
46 changes: 46 additions & 0 deletions core/network/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi

plugins {
id("mihon.library")
kotlin("multiplatform")
}

kotlin {
androidTarget()
// KMK --> Desktop (JVM) target sharing the OkHttp implementation with Android via `jvmShared`
jvm("desktop")
// KMK <--

applyDefaultHierarchyTemplate()

sourceSets {
val commonMain by getting {
dependencies {
api(project.dependencies.platform(kotlinx.coroutines.bom))
api(kotlinx.coroutines.core)
}
}

// Intermediate source set shared by the two JVM targets (Android + desktop). OkHttp is a
// JVM library, so its usage lives here rather than in commonMain. An iOS target would add a
// separate `iosMain` actual (e.g. backed by Ktor) without touching this code.
val jvmShared by creating {
dependsOn(commonMain)
dependencies {
api(libs.okhttp.core)
}
}

getByName("androidMain").dependsOn(jvmShared)
getByName("desktopMain").dependsOn(jvmShared)
}

@OptIn(ExperimentalKotlinGradlePluginApi::class)
compilerOptions {
freeCompilerArgs.add("-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi")
}
}

android {
namespace = "tachiyomi.core.network"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package tachiyomi.core.network

/**
* Minimal multiplatform HTTP client abstraction.
*
* The Android and desktop targets share a single OkHttp-backed implementation (see `jvmShared`).
* A future iOS (Kotlin/Native) target would provide its own actual, e.g. backed by Ktor's Darwin
* engine, without changing this common API.
*/
interface NetworkClient {
suspend fun get(url: String, headers: Map<String, String> = emptyMap()): NetworkResponse
}

/**
* Platform entry point that returns the default [NetworkClient] for the current target.
*/
expect fun httpClient(): NetworkClient
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package tachiyomi.core.network

/**
* Platform-agnostic representation of an HTTP response.
*/
data class NetworkResponse(
val statusCode: Int,
val isSuccessful: Boolean,
val body: String,
val headers: Map<String, String>,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package tachiyomi.core.network

import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.suspendCancellableCoroutine
import okhttp3.Call
import okhttp3.Callback
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import java.io.IOException
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException

/**
* OkHttp-backed [NetworkClient] shared by the Android and desktop (JVM) targets.
*/
actual fun httpClient(): NetworkClient = OkHttpNetworkClient()

class OkHttpNetworkClient(
private val client: OkHttpClient = OkHttpClient(),
) : NetworkClient {

override suspend fun get(url: String, headers: Map<String, String>): NetworkResponse {
val requestBuilder = Request.Builder().url(url)
headers.forEach { (name, value) -> requestBuilder.addHeader(name, value) }
val response = client.newCall(requestBuilder.build()).await()
return response.use {
NetworkResponse(
statusCode = it.code,
isSuccessful = it.isSuccessful,
body = it.body.string(),
headers = it.headers.toMultimap().mapValues { (_, values) -> values.joinToString(", ") },
)
}
}
}

@OptIn(ExperimentalCoroutinesApi::class)
private suspend fun Call.await(): Response = suspendCancellableCoroutine { continuation ->
enqueue(
object : Callback {
override fun onResponse(call: Call, response: Response) {
continuation.resume(response)
}

override fun onFailure(call: Call, e: IOException) {
if (continuation.isCancelled) return
continuation.resumeWithException(e)
}
},
)
continuation.invokeOnCancellation { runCatching { cancel() } }
}
33 changes: 33 additions & 0 deletions core/preference/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi

plugins {
id("mihon.library")
kotlin("multiplatform")
}

kotlin {
androidTarget()
// KMK --> Desktop (JVM) target so the preference abstraction is usable from Compose Desktop
jvm("desktop")
// KMK <--

applyDefaultHierarchyTemplate()

sourceSets {
commonMain {
dependencies {
api(project.dependencies.platform(kotlinx.coroutines.bom))
api(kotlinx.coroutines.core)
}
}
}

@OptIn(ExperimentalKotlinGradlePluginApi::class)
compilerOptions {
freeCompilerArgs.add("-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi")
}
}

android {
namespace = "tachiyomi.core.preference"
}
Loading
Loading