plugins { alias(libs.plugins.kotlin.multiplatform) alias(libs.plugins.kotlin.serialization) alias(libs.plugins.compose.multiplatform) alias(libs.plugins.compose.compiler) alias(libs.plugins.android.library) } kotlin { jvmToolchain(17) androidTarget { compilations.all { compilerOptions.configure { jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17) } } } jvm("desktop") { compilations.all { compilerOptions.configure { jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17) } } } // Hierarchical source sets: // commonMain ── platform-agnostic domain + ViewModels + Compose theme // └── jvmCommonMain ── JVM-only shared between android & desktop (jUPnP logic) // ├── androidMain ── Android holder, Android-flavour Media3 renderer // └── desktopMain ── DesktopUpnpServiceHolder, VlcjLocalRenderer sourceSets { val commonMain by getting { dependencies { implementation(compose.runtime) implementation(compose.foundation) implementation(compose.material3) implementation(compose.materialIconsExtended) implementation(compose.ui) @OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class) implementation(compose.components.resources) implementation(libs.kotlinx.coroutines.core) implementation(libs.kotlinx.serialization.json) // Multiplatform ViewModel + viewModelScope implementation(libs.androidx.lifecycle.viewmodel) implementation(libs.koin.core) implementation(libs.koin.compose) } } val jvmCommonMain by creating { dependsOn(commonMain) dependencies { // UPnP — jUPnP, matching upstream Android app. implementation(libs.jupnp.core) implementation(libs.jupnp.support) implementation(libs.okhttp) // RawCastClient parses Cast wire-protocol JSON payloads. Android // bundles org.json; on JVM we pull it from Maven. Tiny artifact // (~70KB), no transitive deps, used only inside cast/raw/. implementation("org.json:json:20240303") } } val androidMain by getting { dependsOn(jvmCommonMain) dependencies { implementation(libs.kotlinx.coroutines.android) implementation(libs.koin.android) implementation(libs.androidx.activity.compose) // Media3 / ExoPlayer stay in the :android app module, not :shared. } } val desktopMain by getting { dependsOn(jvmCommonMain) dependencies { implementation(compose.desktop.currentOs) implementation(libs.kotlinx.coroutines.swing) implementation(libs.vlcj) implementation(libs.chromecast.api) // jUPnP's default stream server is Jetty 9.x, whose deps (including the // javax.servlet API) are marked provided-scope. We supply them here. implementation(libs.javax.servlet.api) implementation(libs.jetty.server) implementation(libs.jetty.servlet) implementation(libs.jetty.client) // Both jUPnP and Jetty log through SLF4J. jUPnP 3.0.2 brings // slf4j-api 2.x in transitively, so the binding has to match that // line — 1.7.x bindings are ignored at runtime. Without a matching // binding loaded, errors during stream-server startup are silently // swallowed (e.g. when Jetty fails to bind we just see SSDP packets // advertising a port nothing's listening on). slf4j-simple writes // everything to stderr. implementation(libs.slf4j.simple) } } val commonTest by getting { dependencies { implementation(kotlin("test")) implementation(libs.kotest.assertions) } } } } android { namespace = "dev.stave.shared" compileSdk = libs.versions.android.compileSdk.get().toInt() defaultConfig { minSdk = libs.versions.android.minSdk.get().toInt() } compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } }