build.gradle.kts
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
}
}