diff --git a/androidApp/build.gradle.kts b/androidApp/build.gradle.kts
new file mode 100644
index 000000000..1e658cae4
--- /dev/null
+++ b/androidApp/build.gradle.kts
@@ -0,0 +1,118 @@
+import java.util.Properties
+
+fun readXcconfigValue(file: File, key: String): String? {
+ if (!file.exists()) return null
+ return file.readLines()
+ .asSequence()
+ .map(String::trim)
+ .filter { it.isNotEmpty() && !it.startsWith("#") && it.contains('=') }
+ .map { line ->
+ val separatorIndex = line.indexOf('=')
+ line.substring(0, separatorIndex).trim() to line.substring(separatorIndex + 1).trim()
+ }
+ .firstOrNull { (entryKey, _) -> entryKey == key }
+ ?.second
+}
+
+plugins {
+ alias(libs.plugins.androidApplication)
+}
+
+val localProps = Properties().apply {
+ val propsFile = rootProject.file("local.properties")
+ if (propsFile.exists()) propsFile.inputStream().use { load(it) }
+}
+val releaseStoreFile = localProps.getProperty("NUVIO_RELEASE_STORE_FILE")?.takeIf { it.isNotBlank() }
+val releaseStorePassword = localProps.getProperty("NUVIO_RELEASE_STORE_PASSWORD")?.takeIf { it.isNotBlank() }
+val releaseKeyAlias = localProps.getProperty("NUVIO_RELEASE_KEY_ALIAS")?.takeIf { it.isNotBlank() }
+val releaseKeyPassword = localProps.getProperty("NUVIO_RELEASE_KEY_PASSWORD")?.takeIf { it.isNotBlank() }
+val releaseKeystore = releaseStoreFile?.let(rootProject::file)
+val appVersionConfigFile = rootProject.file("iosApp/Configuration/Version.xcconfig")
+val releaseAppVersionName = readXcconfigValue(appVersionConfigFile, "MARKETING_VERSION")
+ ?: error("MARKETING_VERSION is missing from ${appVersionConfigFile.path}")
+val releaseAppVersionCode = readXcconfigValue(appVersionConfigFile, "CURRENT_PROJECT_VERSION")
+ ?.toIntOrNull()
+ ?: error("CURRENT_PROJECT_VERSION is missing or invalid in ${appVersionConfigFile.path}")
+
+android {
+ namespace = "com.nuvio.android"
+ compileSdk = libs.versions.android.compileSdk.get().toInt()
+ compileSdkMinor = libs.versions.android.compileSdkMinor.get().toInt()
+
+ signingConfigs {
+ create("release") {
+ if (releaseKeystore != null && releaseStorePassword != null && releaseKeyAlias != null && releaseKeyPassword != null) {
+ storeFile = releaseKeystore
+ storePassword = releaseStorePassword
+ keyAlias = releaseKeyAlias
+ keyPassword = releaseKeyPassword
+ }
+ }
+ }
+
+ defaultConfig {
+ applicationId = "com.nuvio.app"
+ minSdk = libs.versions.android.minSdk.get().toInt()
+ targetSdk = libs.versions.android.targetSdk.get().toInt()
+ versionCode = releaseAppVersionCode
+ versionName = releaseAppVersionName
+ }
+
+ flavorDimensions += "distribution"
+ productFlavors {
+ create("full") {
+ dimension = "distribution"
+ }
+ create("playstore") {
+ dimension = "distribution"
+ }
+ }
+
+ sourceSets.getByName("full") {
+ manifest.srcFile("src/full/AndroidManifest.xml")
+ jniLibs.directories.add("../composeApp/src/full/jniLibs")
+ }
+
+ packaging {
+ resources {
+ excludes += "/META-INF/{AL2.0,LGPL2.1}"
+ }
+ jniLibs {
+ useLegacyPackaging = true
+ pickFirsts += listOf(
+ "lib/*/libc++_shared.so",
+ "lib/*/libavcodec.so",
+ "lib/*/libavutil.so",
+ "lib/*/libswscale.so",
+ "lib/*/libswresample.so"
+ )
+ }
+ }
+
+ buildTypes {
+ getByName("release") {
+ isMinifyEnabled = true
+ isShrinkResources = true
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "../composeApp/proguard-rules.pro",
+ )
+ signingConfig = signingConfigs.getByName("release")
+ ndk {
+ debugSymbolLevel = "FULL"
+ }
+ }
+ }
+
+ compileOptions {
+ isCoreLibraryDesugaringEnabled = true
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+}
+
+dependencies {
+ implementation(project(":composeApp"))
+ coreLibraryDesugaring(libs.desugar.jdk.libs)
+ debugImplementation(libs.compose.uiTooling)
+}
diff --git a/androidApp/src/full/AndroidManifest.xml b/androidApp/src/full/AndroidManifest.xml
new file mode 100644
index 000000000..47497cd7b
--- /dev/null
+++ b/androidApp/src/full/AndroidManifest.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/androidApp/src/main/AndroidManifest.xml b/androidApp/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..0d702e768
--- /dev/null
+++ b/androidApp/src/main/AndroidManifest.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build.gradle.kts b/build.gradle.kts
index cf780d5a6..11682b292 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -3,7 +3,8 @@ plugins {
// in each subproject's classloader
alias(libs.plugins.androidApplication) apply false
alias(libs.plugins.androidLibrary) apply false
+ alias(libs.plugins.androidKotlinMultiplatformLibrary) apply false
alias(libs.plugins.composeMultiplatform) apply false
alias(libs.plugins.composeCompiler) apply false
alias(libs.plugins.kotlinMultiplatform) apply false
-}
\ No newline at end of file
+}
diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts
index 964b76961..7ceba3d37 100644
--- a/composeApp/build.gradle.kts
+++ b/composeApp/build.gradle.kts
@@ -1,4 +1,3 @@
-import org.jetbrains.compose.desktop.application.dsl.TargetFormat
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
@@ -176,7 +175,7 @@ fun readXcconfigValue(file: File, key: String): String? {
plugins {
alias(libs.plugins.kotlinMultiplatform)
- alias(libs.plugins.androidApplication)
+ alias(libs.plugins.androidKotlinMultiplatformLibrary)
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
alias(libs.plugins.kotlinxSerialization)
@@ -186,11 +185,6 @@ val supabaseProps = Properties().apply {
val propsFile = rootProject.file("local.properties")
if (propsFile.exists()) propsFile.inputStream().use { load(it) }
}
-val releaseStoreFile = supabaseProps.getProperty("NUVIO_RELEASE_STORE_FILE")?.takeIf { it.isNotBlank() }
-val releaseStorePassword = supabaseProps.getProperty("NUVIO_RELEASE_STORE_PASSWORD")?.takeIf { it.isNotBlank() }
-val releaseKeyAlias = supabaseProps.getProperty("NUVIO_RELEASE_KEY_ALIAS")?.takeIf { it.isNotBlank() }
-val releaseKeyPassword = supabaseProps.getProperty("NUVIO_RELEASE_KEY_PASSWORD")?.takeIf { it.isNotBlank() }
-val releaseKeystore = releaseStoreFile?.let(rootProject::file)
val appVersionConfigFile = rootProject.file("iosApp/Configuration/Version.xcconfig")
val releaseAppVersionName = readXcconfigValue(appVersionConfigFile, "MARKETING_VERSION")
?: error("MARKETING_VERSION is missing from ${appVersionConfigFile.path}")
@@ -217,13 +211,38 @@ val generatedRuntimeConfigDir = layout.buildDirectory.dir("generated/runtime-con
val requestedGradleTasks = gradle.startParameter.taskNames.map { taskName ->
taskName.substringAfterLast(':').lowercase()
}
-val isAndroidAppBundleBuild = requestedGradleTasks.any { taskName ->
- taskName == "bundle" ||
- taskName == "bundlerelease" ||
- taskName == "bundledebug" ||
- taskName.startsWith("bundleplaystore") ||
- taskName.startsWith("bundlefull") ||
- taskName.endsWith("bundle")
+val requestedAndroidDistributions = requestedGradleTasks.mapNotNull { taskName ->
+ when {
+ "playstore" in taskName -> "playstore"
+ "full" in taskName -> "full"
+ else -> null
+ }
+}.toSet()
+require(requestedAndroidDistributions.size <= 1) {
+ "Build Android full and playstore distributions separately, or set -Pnuvio.android.distribution=full|playstore."
+}
+val configuredAndroidDistribution = providers.gradleProperty("nuvio.android.distribution").orNull
+ ?: supabaseProps.getProperty("NUVIO_ANDROID_DISTRIBUTION")
+val isAmbiguousAndroidPackageTask = requestedGradleTasks.any { taskName ->
+ taskName == "build" ||
+ taskName.startsWith("assemble") ||
+ taskName.startsWith("bundle")
+} && requestedAndroidDistributions.isEmpty()
+require(configuredAndroidDistribution != null || !isAmbiguousAndroidPackageTask) {
+ "Set -Pnuvio.android.distribution=full|playstore for aggregate Android assemble/bundle tasks."
+}
+val androidDistribution = (
+ configuredAndroidDistribution
+ ?: requestedAndroidDistributions.singleOrNull()
+ ?: "playstore"
+ ).trim().lowercase()
+require(androidDistribution == "playstore" || androidDistribution == "full") {
+ "nuvio.android.distribution must be 'playstore' or 'full'."
+}
+val androidDistributionSourceDir = if (androidDistribution == "full") {
+ "src/androidFull/kotlin"
+} else {
+ "src/androidPlaystore/kotlin"
}
val runtimeLocalProperties = Properties().apply {
val file = rootProject.file("local.properties")
@@ -254,7 +273,16 @@ tasks.withType>().configureEach {
}
kotlin {
- androidTarget {
+ android {
+ namespace = "com.nuvio.app"
+ compileSdk {
+ version = release(libs.versions.android.compileSdk.get().toInt()) {
+ minorApiLevel = libs.versions.android.compileSdkMinor.get().toInt()
+ }
+ }
+ minSdk = libs.versions.android.minSdk.get().toInt()
+ androidResources.enable = true
+
compilerOptions {
jvmTarget.set(JvmTarget.JVM_11)
}
@@ -298,30 +326,41 @@ kotlin {
val commonMain by getting {
kotlin.srcDir(generatedRuntimeConfigDir)
}
- androidMain.dependencies {
- implementation(libs.compose.uiToolingPreview)
- implementation(libs.androidx.appcompat)
- implementation(libs.androidx.activity.compose)
- implementation(libs.androidx.core.splashscreen)
- implementation(libs.androidx.work.runtime)
- implementation(libs.coil.gif)
- implementation("androidx.recyclerview:recyclerview:1.4.0")
- implementation("com.squareup.okhttp3:okhttp:4.12.0")
- implementation("com.google.code.gson:gson:2.11.0")
- implementation("io.github.peerless2012:ass-media:0.4.0-beta01")
- implementation(libs.ktor.client.android)
- implementation(libs.androidx.media3.exoplayer.hls)
- implementation(libs.androidx.media3.exoplayer.dash)
- implementation(libs.androidx.media3.exoplayer.smoothstreaming)
- implementation(libs.androidx.media3.exoplayer.rtsp)
- implementation(libs.androidx.media3.datasource)
- implementation(libs.androidx.media3.datasource.okhttp)
- implementation(libs.androidx.media3.decoder)
- implementation(libs.androidx.media3.session)
- implementation(libs.androidx.media3.common)
- implementation(libs.androidx.media3.container)
- implementation(libs.androidx.media3.extractor)
- implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("lib-*.aar"))))
+ androidMain {
+ kotlin.srcDir(project.file(androidDistributionSourceDir))
+ if (androidDistribution == "full") {
+ kotlin.srcDir(fullCommonSourceDir)
+ }
+
+ dependencies {
+ implementation(libs.compose.uiToolingPreview)
+ implementation(libs.androidx.appcompat)
+ implementation(libs.androidx.activity.compose)
+ implementation(libs.androidx.core.splashscreen)
+ implementation(libs.androidx.work.runtime)
+ implementation(libs.coil.gif)
+ implementation("androidx.recyclerview:recyclerview:1.4.0")
+ implementation("com.squareup.okhttp3:okhttp:4.12.0")
+ implementation("com.google.code.gson:gson:2.11.0")
+ implementation("io.github.peerless2012:ass-media:0.4.0-beta01")
+ implementation(libs.ktor.client.android)
+ implementation(libs.androidx.media3.exoplayer.hls)
+ implementation(libs.androidx.media3.exoplayer.dash)
+ implementation(libs.androidx.media3.exoplayer.smoothstreaming)
+ implementation(libs.androidx.media3.exoplayer.rtsp)
+ implementation(libs.androidx.media3.datasource)
+ implementation(libs.androidx.media3.datasource.okhttp)
+ implementation(libs.androidx.media3.decoder)
+ implementation(libs.androidx.media3.session)
+ implementation(libs.androidx.media3.common)
+ implementation(libs.androidx.media3.container)
+ implementation(libs.androidx.media3.extractor)
+ implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("lib-*.aar"))))
+ if (androidDistribution == "full") {
+ implementation(files("libs/quickjs-kt-android-1.0.5-nuvio.aar"))
+ implementation(libs.ksoup)
+ }
+ }
}
commonMain.dependencies {
implementation(libs.coil.compose)
@@ -351,94 +390,11 @@ kotlin {
}
}
-afterEvaluate {
- dependencies {
- add("fullImplementation", files("libs/quickjs-kt-android-1.0.5-nuvio.aar"))
- add("fullImplementation", libs.ksoup)
- }
-}
-
configurations.matching { it.name == "iosMainImplementation" }.configureEach {
project.dependencies.add(name, libs.ktor.client.darwin)
}
-dependencies {
- coreLibraryDesugaring(libs.desugar.jdk.libs)
- debugImplementation(libs.compose.uiTooling)
-}
-
configurations.all {
exclude(group = "androidx.media3", module = "media3-exoplayer")
exclude(group = "androidx.media3", module = "media3-ui")
}
-
-android {
- namespace = "com.nuvio.app"
- compileSdk = libs.versions.android.compileSdk.get().toInt()
-
- signingConfigs {
- create("release") {
- if (releaseKeystore != null && releaseStorePassword != null && releaseKeyAlias != null && releaseKeyPassword != null) {
- storeFile = releaseKeystore
- storePassword = releaseStorePassword
- keyAlias = releaseKeyAlias
- keyPassword = releaseKeyPassword
- }
- }
- }
-
- defaultConfig {
- applicationId = "com.nuvio.app"
- minSdk = libs.versions.android.minSdk.get().toInt()
- targetSdk = libs.versions.android.targetSdk.get().toInt()
- versionCode = releaseAppVersionCode
- versionName = releaseAppVersionName
- }
- flavorDimensions += "distribution"
- productFlavors {
- create("full") {
- dimension = "distribution"
- }
- create("playstore") {
- dimension = "distribution"
- }
- }
- sourceSets.getByName("full") {
- manifest.srcFile("src/androidFull/AndroidManifest.xml")
- java.srcDir(fullCommonSourceDir)
- }
- packaging {
- resources {
- excludes += "/META-INF/{AL2.0,LGPL2.1}"
- }
- jniLibs {
- useLegacyPackaging = true
- pickFirsts += listOf(
- "lib/*/libc++_shared.so",
- "lib/*/libavcodec.so",
- "lib/*/libavutil.so",
- "lib/*/libswscale.so",
- "lib/*/libswresample.so"
- )
- }
- }
- buildTypes {
- getByName("release") {
- isMinifyEnabled = true
- isShrinkResources = true
- proguardFiles(
- getDefaultProguardFile("proguard-android-optimize.txt"),
- "proguard-rules.pro",
- )
- signingConfig = signingConfigs.getByName("release")
- ndk {
- debugSymbolLevel = "FULL"
- }
- }
- }
- compileOptions {
- isCoreLibraryDesugaringEnabled = true
- sourceCompatibility = JavaVersion.VERSION_11
- targetCompatibility = JavaVersion.VERSION_11
- }
-}
diff --git a/composeApp/src/androidMain/AndroidManifest.xml b/composeApp/src/androidMain/AndroidManifest.xml
index 855965355..8072ee00d 100644
--- a/composeApp/src/androidMain/AndroidManifest.xml
+++ b/composeApp/src/androidMain/AndroidManifest.xml
@@ -1,57 +1,2 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 39ae4c826..a9b1b5475 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,25 +1,26 @@
[versions]
-agp = "8.13.2"
-android-compileSdk = "36"
+agp = "9.2.0"
+android-compileSdk = "37"
+android-compileSdkMinor = "0"
android-minSdk = "24"
android-targetSdk = "36"
androidx-activity = "1.12.2"
-androidx-navigation = "2.9.2"
+androidx-navigation = "2.10.0-alpha02"
androidx-appcompat = "1.7.1"
androidx-core = "1.17.0"
androidx-core-splashscreen = "1.0.1"
androidx-espresso = "3.7.0"
-androidx-lifecycle = "2.11.0-beta01"
+androidx-lifecycle = "2.11.0-beta02"
androidx-work = "2.10.3"
androidx-testExt = "1.3.0"
-composeMultiplatform = "1.11.1"
+composeMultiplatform = "1.12.0-alpha02"
coil = "3.5.0-beta01"
kermit = "2.0.5"
junit = "4.13.2"
kotlin = "2.3.0"
kotlinx-serialization = "1.8.1"
ktor = "3.4.1"
-material3 = "1.11.0-alpha07"
+material3 = "1.12.0-alpha02"
androidx-media3 = "1.8.0"
supabase = "3.4.1"
quickjsKt = "1.0.5"
@@ -80,6 +81,7 @@ desugar-jdk-libs = { module = "com.android.tools:desugar_jdk_libs", version.ref
[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
androidLibrary = { id = "com.android.library", version.ref = "agp" }
+androidKotlinMultiplatformLibrary = { id = "com.android.kotlin.multiplatform.library", version.ref = "agp" }
composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "composeMultiplatform" }
composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index d4081da47..c61a118f7 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
diff --git a/settings.gradle.kts b/settings.gradle.kts
index e08f97b53..884dc8e94 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -28,4 +28,5 @@ dependencyResolutionManagement {
}
}
-include(":composeApp")
\ No newline at end of file
+include(":composeApp")
+include(":androidApp")