Wholphin/app/build.gradle.kts
Damontecres 5363db0b80
Update API level to Android 17 (#1908)
## Description
Updates the compile & target SDKs to 37 (Android 17).

The only required change is to support the new `ACCESS_LOCAL_NETWORK`
permission. This is required for Android 17 devices to auto discover
Jellyfin servers on the local network. Wholphin will prompt for
permission and if granted, use the discover servers feature. If
permission is not granted, the user must enter the server URL manually.

### Related issues
N/A, but several dependency renovate PRs are blocked by this

### Testing
I tested with a regular Android 17 emulator. There are no Android TV 17
images (or probably even devices) available yet.

## Screenshots
N/A

## AI or LLM usage
None
2026-08-29 21:35:58 -04:00

411 lines
14 KiB
Kotlin

import com.android.build.api.dsl.ApplicationExtension
import com.android.build.api.dsl.ProductFlavor
import com.android.build.api.variant.FilterConfiguration
import com.google.protobuf.gradle.id
import com.mikepenz.aboutlibraries.plugin.DuplicateMode
import com.mikepenz.aboutlibraries.plugin.DuplicateRule
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import java.util.Base64
import java.util.Properties
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.ksp)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.hilt)
alias(libs.plugins.room)
alias(libs.plugins.protobuf)
alias(libs.plugins.kotlin.plugin.serialization)
alias(libs.plugins.aboutLibraries)
alias(libs.plugins.openapi.generator)
}
val isCI = providers.environmentVariable("CI").orElse("false").map { it.toBoolean() }
val shouldSign =
isCI.zip(
providers.environmentVariable("KEY_ALIAS").orElse("").map { it.isNotBlank() },
) { isCI, hasKey ->
isCI && hasKey
}
val ffmpegModuleExists =
providers.provider { project.file("libs/lib-decoder-ffmpeg-release.aar").exists() }
val av1ModuleExists =
providers.provider { project.file("libs/lib-decoder-av1-release.aar").exists() }
val mpvModuleExists =
providers.provider { project.file("libs/wholphin-mpv-release.aar").exists() }
val extensionsRepoActive =
providers.provider { project.hasProperty("WholphinExtensionsUsername") }
// See https://issuetracker.google.com/issues/402800800
val isBuildingBundle =
providers.provider {
gradle.startParameter.taskNames.any { it.lowercase().contains("bundle") }
}
val gitTags =
providers
.exec { commandLine("git", "tag", "--list", "v*", "p*") }
.standardOutput.asText
.get()
val gitDescribe =
providers
.exec { commandLine("git", "describe", "--tags", "--long", "--match=v*") }
.standardOutput.asText
.getOrElse("v0.0.0")
kotlin {
compilerOptions {
languageVersion = org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_3
jvmTarget = JvmTarget.JVM_11
javaParameters = true
// Workaround for https://github.com/google/ksp/issues/2964
moduleName = "${project.group}_${project.name}"
}
}
private fun Provider<String>.getInt() = get().toInt()
configure<ApplicationExtension> {
namespace = "com.github.damontecres.wholphin"
compileSdk = libs.versions.compileSdk.getInt()
defaultConfig {
applicationId = "com.github.damontecres.wholphin"
minSdk = libs.versions.minSdk.getInt()
targetSdk = libs.versions.targetSdk.getInt()
versionCode = gitTags.trim().lines().size
versionName = gitDescribe.trim().removePrefix("v").ifBlank { "0.0.0" }
testInstrumentationRunner = "com.github.damontecres.wholphin.test.WholphinTestRunner"
buildConfigField("long", "BUILD_TIME", System.currentTimeMillis().toString())
}
signingConfigs {
if (shouldSign.get()) {
create("ci") {
file("ci.keystore").writeBytes(
Base64.getDecoder().decode(System.getenv("SIGNING_KEY")),
)
keyAlias = System.getenv("KEY_ALIAS")
keyPassword = System.getenv("KEY_PASSWORD")
storePassword = System.getenv("KEY_STORE_PASSWORD")
storeFile = file("ci.keystore")
enableV1Signing = true
enableV2Signing = true
enableV3Signing = true
enableV4Signing = true
}
}
}
buildTypes {
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
)
isDebuggable = false
if (shouldSign.get()) {
signingConfig = signingConfigs.getByName("ci")
} else {
val localPropertiesFile = project.rootProject.file("local.properties")
if (localPropertiesFile.exists()) {
val properties = Properties()
properties.load(localPropertiesFile.inputStream())
val signingConfigName = properties["release.signing.config"]?.toString()
if (signingConfigName != null) {
signingConfig = signingConfigs.getByName(signingConfigName)
}
}
}
}
debug {
isMinifyEnabled = false
isShrinkResources = false
isDebuggable = true
applicationIdSuffix = ".debug"
}
}
flavorDimensions += "version"
productFlavors {
val featureLeanback = "leanback"
val featureUpdate = "UPDATING_ENABLED"
val featureDiscover = "DISCOVER_ENABLED"
fun ProductFlavor.setFeatureFlag(
name: String,
enabled: Boolean,
) {
this.buildConfigField("boolean", name, "Boolean.parseBoolean(\"${enabled}\")")
}
create("default") {
dimension = "version"
isDefault = true
manifestPlaceholders += mapOf(featureLeanback to false)
setFeatureFlag(featureUpdate, true)
setFeatureFlag(featureDiscover, true)
}
create("appstore") {
dimension = "version"
manifestPlaceholders += mapOf(featureLeanback to true)
setFeatureFlag(featureUpdate, false)
setFeatureFlag(featureDiscover, true)
}
create("firetv") {
dimension = "version"
manifestPlaceholders += mapOf(featureLeanback to true)
setFeatureFlag(featureUpdate, false)
setFeatureFlag(featureDiscover, false)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
isCoreLibraryDesugaringEnabled = true
}
buildFeatures {
buildConfig = true
compose = true
}
splits {
abi {
// Disable split abis when building bundles
isEnable = !isBuildingBundle.get()
reset()
include("armeabi-v7a", "arm64-v8a", "x86_64")
isUniversalApk = true
}
}
packaging {
jniLibs {
// Work around because libass-android & wholphin-mpv both (incorrectly) package libc++_shared.so
pickFirsts += "lib/*/libc++_shared.so"
}
}
sourceSets {
getByName("main") {
kotlin.directories += "$buildDir/generated/seerr_api/src/main/kotlin"
}
}
testOptions {
unitTests {
isIncludeAndroidResources = true
}
}
lint {
disable.add("MissingTranslation")
}
androidResources {
generateLocaleConfig = true
}
}
androidComponents {
onVariants(selector().all()) { variant ->
variant.outputs
.map { it as com.android.build.api.variant.impl.VariantOutputImpl }
.forEach { output ->
val abi =
output
.getFilter(FilterConfiguration.FilterType.ABI)
.let { if (it != null) "-${it.identifier}" else "" }
val outputFileName =
"Wholphin-${variant.flavorName}-${variant.buildType}-${output.versionName.get()}-${output.versionCode.get()}$abi.apk"
output.outputFileName = outputFileName
}
}
}
room {
schemaDirectory("$projectDir/schemas")
}
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:${libs.protobuf.kotlin.lite.get().version}"
}
generateProtoTasks {
all().forEach {
it.plugins {
id("java") {
option("lite")
}
}
it.builtins {
id("kotlin") {
option("lite")
}
}
}
}
}
aboutLibraries {
collect {
configPath = file("config")
}
library {
duplicationMode = DuplicateMode.MERGE
duplicationRule = DuplicateRule.SIMPLE
}
}
openApiGenerate {
generatorName.set("kotlin")
inputSpec.set("$projectDir/src/main/seerr/seerr-api.yml")
templateDir.set("$projectDir/src/main/seerr/templates")
outputDir.set("$buildDir/generated/seerr_api")
apiPackage.set("com.github.damontecres.wholphin.api.seerr")
modelPackage.set("com.github.damontecres.wholphin.api.seerr.model")
groupId.set("com.github.damontecres.wholphin.api.seerr")
id.set("seerr-api")
packageName.set("com.github.damontecres.wholphin.api.seerr")
additionalProperties.apply {
put("serializationLibrary", "kotlinx_serialization")
put("sortModelPropertiesByRequiredFlag", true)
put("sortParamsByRequiredFlag", true)
put("useCoroutines", true)
put("enumPropertyNaming", "UPPERCASE")
put("modelMutable", false)
// Note: this is only for downloading files, so it's not necessary to enable
put("supportAndroidApiLevel25AndBelow", false)
}
}
tasks.named("preBuild") {
dependsOn.add(tasks.named("openApiGenerate"))
}
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.graphics)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.compose.runtime)
implementation(libs.androidx.tv.foundation)
implementation(libs.androidx.tv.material)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.datastore)
implementation(libs.androidx.datastore.preferences)
implementation(libs.protobuf.kotlin.lite)
implementation(libs.androidx.tvprovider)
implementation(libs.androidx.work.runtime.ktx)
implementation(libs.androidx.hilt.work)
implementation(libs.androidx.media3.exoplayer)
implementation(libs.androidx.media3.session)
implementation(libs.androidx.media3.datasource.okhttp)
implementation(libs.androidx.media3.exoplayer.hls)
implementation(libs.androidx.media3.exoplayer.dash)
implementation(libs.androidx.media3.ui)
implementation(libs.androidx.media3.ui.compose)
implementation(libs.ass.media)
implementation(libs.coil.core)
implementation(libs.coil.compose)
implementation(libs.coil.network.cachecontrol)
implementation(libs.coil.network.okhttp)
implementation(libs.coil.gif)
implementation(libs.coil.svg)
implementation(libs.jellyfin.core)
implementation(libs.jellyfin.api)
implementation(libs.jellyfin.api.okhttp)
implementation(libs.androidx.navigation3.ui)
implementation(libs.androidx.navigation3.runtime)
implementation(libs.androidx.lifecycle.viewmodel.navigation3)
implementation(libs.androidx.material3.adaptive.navigation3)
implementation(libs.kotlinx.serialization.core)
implementation(libs.kotlinx.serialization.json)
implementation(libs.hilt.android)
implementation(libs.androidx.room.common.jvm)
implementation(libs.androidx.room.ktx)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.hilt.navigation.compose)
implementation(libs.androidx.preference.ktx)
implementation(libs.androidx.room.testing)
implementation(libs.androidx.palette.ktx)
implementation(libs.androidx.media3.effect)
implementation(libs.androidx.runner)
ksp(libs.androidx.room.compiler)
ksp(libs.hilt.android.compiler)
ksp(libs.androidx.hilt.compiler)
implementation(libs.timber)
implementation(libs.slf4j2.timber)
implementation(libs.aboutlibraries.core)
implementation(libs.aboutlibraries.compose.m3)
implementation(libs.multiplatform.markdown.renderer)
implementation(libs.multiplatform.markdown.renderer.m3)
implementation(libs.programguide)
implementation(libs.acra.http)
implementation(libs.acra.dialog)
implementation(libs.acra.limiter)
compileOnly(libs.auto.service.annotations)
ksp(libs.auto.service.ksp)
implementation(platform(libs.okhttp.bom))
implementation(libs.okhttp)
implementation(libs.kache)
implementation(libs.kache.file)
coreLibraryDesugaring(libs.desugar.jdk.libs)
if (ffmpegModuleExists.get()) {
logger.info("Using local ffmpeg decoder")
implementation(files("libs/lib-decoder-ffmpeg-release.aar"))
} else if (extensionsRepoActive.get()) {
logger.info("Using prebuilt ffmpeg decoder")
implementation(libs.wholphin.extensions.ffmpeg)
} else {
logger.warn("Media3 ffmpeg decoder was NOT found")
}
if (av1ModuleExists.get()) {
logger.info("Using local av1 decoder")
implementation(files("libs/lib-decoder-av1-release.aar"))
} else if (extensionsRepoActive.get()) {
logger.info("Using prebuilt av1 decoder")
implementation(libs.wholphin.extensions.av1)
} else {
logger.warn("Media3 av1 decoder was NOT found")
}
if (mpvModuleExists.get()) {
logger.info("Using local libMPV build")
implementation(files("libs/wholphin-mpv-release.aar"))
} else if (extensionsRepoActive.get()) {
logger.info("Using prebuilt libMPV")
implementation(libs.wholphin.extensions.mpv)
} else {
logger.warn("libMPV was NOT found, using stub library")
implementation(project(":wholphin-mpv-stub"))
}
debugImplementation(libs.androidx.compose.ui.test.manifest)
debugImplementation(libs.androidx.compose.ui.tooling)
testImplementation(libs.mockk.android)
testImplementation(libs.mockk.agent)
testImplementation(libs.kotlinx.coroutines.test)
testImplementation(libs.androidx.core.testing)
testImplementation(libs.robolectric)
testImplementation(libs.hilt.android.testing)
testImplementation(libs.androidx.compose.ui.test.junit4)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.mockk.android)
androidTestImplementation(libs.hilt.android.testing)
androidTestImplementation(libs.androidx.compose.ui.test.manifest)
}