A Compose Multiplatform app (mobile/) targeting Android, iOS, and desktop,
sharing one Kotlin codebase: OIDC login via PKCE against the same Zitadel
instance the web player uses (loopback redirect on desktop, a musify://
custom scheme on mobile), a token store per platform, and a first pass at
browsing/streaming tracks through the existing API.
deploy/compose.yml's zitadel-init gains a third OIDC app registration
("Musify Native", public client, no secret) alongside the existing API and
web apps, writing its client id to NATIVE_CLIENT_ID in the env file the same
way the other two already do.
157 lines
5.6 KiB
Kotlin
157 lines
5.6 KiB
Kotlin
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
|
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
|
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
|
|
|
|
plugins {
|
|
alias(libs.plugins.kotlinMultiplatform)
|
|
alias(libs.plugins.androidApplication)
|
|
alias(libs.plugins.composeMultiplatform)
|
|
alias(libs.plugins.composeCompiler)
|
|
alias(libs.plugins.kotlinSerialization)
|
|
}
|
|
|
|
// Backend connection settings, read from gradle.properties so every target
|
|
// (Android, Desktop, iOS) is generated with the same values without
|
|
// duplicating them in three places. See mobile/README.md.
|
|
val apiBaseUrl: String = providers.gradleProperty("musify.apiBaseUrl").get()
|
|
val authIssuer: String = providers.gradleProperty("musify.authIssuer").get()
|
|
val authClientId: String = providers.gradleProperty("musify.authClientId").get()
|
|
|
|
// JavaFX Media's os/arch classifier — the demo only needs to run on this box, but
|
|
// this keeps `./gradlew :composeApp:run` working on any dev machine unmodified.
|
|
val javafxClassifier: String = run {
|
|
val os = org.gradle.internal.os.OperatingSystem.current()
|
|
val arch = System.getProperty("os.arch")
|
|
when {
|
|
os.isWindows -> "win"
|
|
os.isMacOsX -> if (arch.contains("aarch64")) "mac-aarch64" else "mac"
|
|
else -> if (arch.contains("aarch64")) "linux-aarch64" else "linux"
|
|
}
|
|
}
|
|
|
|
kotlin {
|
|
androidTarget {
|
|
@OptIn(ExperimentalKotlinGradlePluginApi::class)
|
|
compilerOptions {
|
|
jvmTarget.set(JvmTarget.JVM_11)
|
|
}
|
|
}
|
|
|
|
jvm("desktop")
|
|
|
|
listOf(
|
|
iosX64(),
|
|
iosArm64(),
|
|
iosSimulatorArm64()
|
|
).forEach { iosTarget: KotlinNativeTarget ->
|
|
iosTarget.binaries.framework {
|
|
baseName = "ComposeApp"
|
|
isStatic = true
|
|
}
|
|
}
|
|
|
|
sourceSets {
|
|
val commonMain by getting {
|
|
dependencies {
|
|
implementation(compose.runtime)
|
|
implementation(compose.foundation)
|
|
implementation(compose.material3)
|
|
implementation(compose.ui)
|
|
implementation(compose.components.resources)
|
|
implementation(compose.components.uiToolingPreview)
|
|
|
|
implementation(libs.kotlinx.coroutines.core)
|
|
implementation(libs.kotlinx.serialization.json)
|
|
|
|
implementation(libs.ktor.client.core)
|
|
implementation(libs.ktor.client.content.negotiation)
|
|
implementation(libs.ktor.serialization.kotlinx.json)
|
|
implementation(libs.ktor.client.logging)
|
|
|
|
implementation(libs.coil.compose)
|
|
implementation(libs.coil.network.ktor)
|
|
}
|
|
}
|
|
|
|
val androidMain by getting {
|
|
dependencies {
|
|
implementation(libs.ktor.client.okhttp)
|
|
implementation(libs.androidx.activity.compose)
|
|
implementation(libs.androidx.browser)
|
|
implementation(libs.androidx.datastore.preferences)
|
|
implementation(libs.androidx.media3.exoplayer)
|
|
implementation(libs.androidx.media3.common)
|
|
}
|
|
}
|
|
|
|
val desktopMain by getting {
|
|
dependencies {
|
|
implementation(compose.desktop.currentOs)
|
|
implementation(libs.ktor.client.cio)
|
|
// MediaPlayer only, via Platform.startup() — no javafx-swing/AWT needed.
|
|
// OpenJFX's sibling-module POM dependencies aren't classifier-aware in
|
|
// Gradle, so javafx-base/-graphics must be pinned explicitly too, or
|
|
// Gradle resolves their classifier-less (empty) stub jars instead.
|
|
val javafxVersion = libs.versions.javafx.get()
|
|
implementation("org.openjfx:javafx-base:$javafxVersion:$javafxClassifier")
|
|
implementation("org.openjfx:javafx-graphics:$javafxVersion:$javafxClassifier")
|
|
implementation("org.openjfx:javafx-media:$javafxVersion:$javafxClassifier")
|
|
}
|
|
}
|
|
|
|
val iosMain by creating {
|
|
dependsOn(commonMain)
|
|
dependencies {
|
|
implementation(libs.ktor.client.darwin)
|
|
}
|
|
}
|
|
getByName("iosX64Main").dependsOn(iosMain)
|
|
getByName("iosArm64Main").dependsOn(iosMain)
|
|
getByName("iosSimulatorArm64Main").dependsOn(iosMain)
|
|
}
|
|
}
|
|
|
|
android {
|
|
namespace = "com.musify.app"
|
|
compileSdk = libs.versions.compileSdk.get().toInt()
|
|
|
|
defaultConfig {
|
|
applicationId = "com.musify.app"
|
|
minSdk = libs.versions.minSdk.get().toInt()
|
|
targetSdk = libs.versions.targetSdk.get().toInt()
|
|
versionCode = 1
|
|
versionName = "1.0"
|
|
|
|
buildConfigField("String", "API_BASE_URL", "\"$apiBaseUrl\"")
|
|
buildConfigField("String", "AUTH_ISSUER", "\"$authIssuer\"")
|
|
buildConfigField("String", "AUTH_CLIENT_ID", "\"$authClientId\"")
|
|
}
|
|
buildFeatures {
|
|
buildConfig = true
|
|
compose = true
|
|
}
|
|
compileOptions {
|
|
sourceCompatibility = JavaVersion.VERSION_11
|
|
targetCompatibility = JavaVersion.VERSION_11
|
|
}
|
|
packaging {
|
|
resources.excludes.add("/META-INF/{AL2.0,LGPL2.1}")
|
|
}
|
|
}
|
|
|
|
compose.desktop {
|
|
application {
|
|
mainClass = "com.musify.app.MainKt"
|
|
jvmArgs += listOf(
|
|
"-Dmusify.apiBaseUrl=$apiBaseUrl",
|
|
"-Dmusify.authIssuer=$authIssuer",
|
|
"-Dmusify.authClientId=$authClientId"
|
|
)
|
|
nativeDistributions {
|
|
targetFormats(org.jetbrains.compose.desktop.application.dsl.TargetFormat.Msi)
|
|
packageName = "MusifyMobile"
|
|
packageVersion = "1.0.0"
|
|
}
|
|
}
|
|
}
|