189 lines
6.6 KiB
Kotlin
189 lines
6.6 KiB
Kotlin
import java.util.Properties
|
|
import org.gradle.api.tasks.testing.Test
|
|
|
|
plugins {
|
|
alias(libs.plugins.android.application)
|
|
alias(libs.plugins.kotlin.compose)
|
|
}
|
|
|
|
// Optional release signing.
|
|
//
|
|
// Create a `keystore.properties` file in the project ROOT (it is git-ignored; see
|
|
// keystore.properties.example) with:
|
|
//
|
|
// storeFile=/absolute/path/to/upload-keystore.jks
|
|
// storePassword=********
|
|
// keyAlias=upload
|
|
// keyPassword=********
|
|
//
|
|
// Without that file the release build still runs R8/minification but produces an
|
|
// unsigned APK, which is fine for local checks but cannot be uploaded to Play.
|
|
val keystorePropertiesFile = rootProject.file("keystore.properties")
|
|
val keystoreProperties = Properties().apply {
|
|
if (keystorePropertiesFile.exists()) {
|
|
keystorePropertiesFile.inputStream().use { load(it) }
|
|
}
|
|
}
|
|
val hasReleaseSigning = keystoreProperties.getProperty("storeFile") != null
|
|
|
|
if (hasReleaseSigning) {
|
|
// Fail early and readably instead of letting signReleaseBundle blow up later
|
|
// with an opaque NullPointerException.
|
|
val missing = listOf("storePassword", "keyAlias")
|
|
.filter { keystoreProperties.getProperty(it).isNullOrBlank() }
|
|
check(missing.isEmpty()) {
|
|
"keystore.properties is incomplete — missing: ${missing.joinToString()}"
|
|
}
|
|
}
|
|
|
|
android {
|
|
namespace = "de.softwareapp_hb.filemanager"
|
|
compileSdk = 36
|
|
|
|
defaultConfig {
|
|
applicationId = "de.softwareapp_hb.filemanager"
|
|
minSdk = 26
|
|
targetSdk = 36
|
|
versionCode = 1
|
|
versionName = "1.0.0"
|
|
|
|
vectorDrawables {
|
|
useSupportLibrary = true
|
|
}
|
|
}
|
|
|
|
signingConfigs {
|
|
if (hasReleaseSigning) {
|
|
create("release") {
|
|
storeFile = file(keystoreProperties.getProperty("storeFile"))
|
|
storePassword = keystoreProperties.getProperty("storePassword")
|
|
keyAlias = keystoreProperties.getProperty("keyAlias")
|
|
// Falls back to the store password. That matches reality: PKCS12
|
|
// keystores (the default on modern JDKs) cannot have a key password
|
|
// that differs from the store password, and leaving this null makes
|
|
// AGP's signReleaseBundle crash with an NPE.
|
|
keyPassword = keystoreProperties.getProperty("keyPassword")
|
|
?: keystoreProperties.getProperty("storePassword")
|
|
}
|
|
}
|
|
}
|
|
|
|
buildTypes {
|
|
debug {
|
|
applicationIdSuffix = ".debug"
|
|
versionNameSuffix = "-debug"
|
|
}
|
|
release {
|
|
if (hasReleaseSigning) {
|
|
signingConfig = signingConfigs.getByName("release")
|
|
}
|
|
isMinifyEnabled = true
|
|
isShrinkResources = true
|
|
proguardFiles(
|
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
|
"proguard-rules.pro",
|
|
)
|
|
}
|
|
}
|
|
|
|
compileOptions {
|
|
sourceCompatibility = JavaVersion.VERSION_17
|
|
targetCompatibility = JavaVersion.VERSION_17
|
|
}
|
|
|
|
buildFeatures {
|
|
compose = true
|
|
}
|
|
|
|
testOptions {
|
|
unitTests {
|
|
// Robolectric needs the merged resources and manifest to stand up the framework.
|
|
isIncludeAndroidResources = true
|
|
}
|
|
}
|
|
|
|
packaging {
|
|
resources {
|
|
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
|
}
|
|
}
|
|
|
|
lint {
|
|
// Dependency versions are pinned deliberately — Compose 1.12+/Coil 3.6+ need
|
|
// compileSdk 37, which AGP 9.1 does not support yet (see the README version
|
|
// note). These advisories are noise rather than signal here.
|
|
disable += setOf(
|
|
"GradleDependency",
|
|
"AndroidGradlePluginVersion",
|
|
"NewerVersionAvailable",
|
|
)
|
|
}
|
|
}
|
|
|
|
kotlin {
|
|
compilerOptions {
|
|
// With AGP built-in Kotlin this already defaults to
|
|
// android.compileOptions.targetCompatibility; set explicitly for clarity.
|
|
jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17
|
|
}
|
|
}
|
|
|
|
// Robolectric reflects into JDK internals (notably java.io.FileDescriptor), which the module
|
|
// system blocks from JDK 17 onwards. Without these it fails with "Failed to interact with raw
|
|
// FileDescriptor internals".
|
|
tasks.withType<Test>().configureEach {
|
|
jvmArgs(
|
|
"--add-opens=java.base/java.io=ALL-UNNAMED",
|
|
"--add-opens=java.base/java.lang=ALL-UNNAMED",
|
|
"--add-opens=java.base/java.lang.reflect=ALL-UNNAMED",
|
|
"--add-opens=java.base/java.util=ALL-UNNAMED",
|
|
"--add-opens=java.base/java.text=ALL-UNNAMED",
|
|
"--add-opens=java.base/java.nio=ALL-UNNAMED",
|
|
"--add-opens=java.base/sun.nio.ch=ALL-UNNAMED",
|
|
// Robolectric reaches FileDescriptor internals through jdk.internal.access.SharedSecrets, and
|
|
// that needs an *export* rather than an *open*: without it the run dies with
|
|
// "IllegalAccessException ... module java.base does not export jdk.internal.access".
|
|
"--add-exports=java.base/jdk.internal.access=ALL-UNNAMED",
|
|
"--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED",
|
|
"--add-exports=java.base/jdk.internal.reflect=ALL-UNNAMED",
|
|
)
|
|
maxHeapSize = "2g"
|
|
testLogging { showStandardStreams = true }
|
|
}
|
|
|
|
dependencies {
|
|
implementation(libs.androidx.core.ktx)
|
|
// SAF backend for the "All files access denied" fallback path.
|
|
implementation(libs.androidx.documentfile)
|
|
|
|
implementation(libs.androidx.lifecycle.runtime.ktx)
|
|
implementation(libs.androidx.lifecycle.runtime.compose)
|
|
implementation(libs.androidx.lifecycle.viewmodel.compose)
|
|
|
|
implementation(libs.androidx.activity.compose)
|
|
|
|
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.material3)
|
|
implementation(libs.androidx.compose.material.icons.core)
|
|
implementation(libs.androidx.compose.material.icons.extended)
|
|
|
|
implementation(libs.kotlinx.coroutines.android)
|
|
|
|
// Image loading for thumbnails and the built-in image viewer.
|
|
implementation(libs.coil.compose)
|
|
|
|
// ZIP/TAR/GZ archive support. Pure Java — no native code, no NDK.
|
|
implementation(libs.commons.compress)
|
|
|
|
debugImplementation(libs.androidx.compose.ui.tooling)
|
|
|
|
testImplementation(libs.junit)
|
|
// Runs the real Android framework on the JVM, so the data layer is testable without a device.
|
|
testImplementation(libs.robolectric)
|
|
// Lets a ViewModel test drive `viewModelScope` on a deterministic dispatcher.
|
|
testImplementation(libs.kotlinx.coroutines.test)
|
|
}
|