import java.io.File import java.util.Properties import java.time.Instant import java.time.ZoneOffset import java.time.format.DateTimeFormatter plugins { id("com.android.application") } data class SharedReleaseSigning( val storeFile: File, val keyAlias: String, val storePassword: String, val keyPassword: String, ) val fallbackVersionCode = Instant.now().epochSecond .coerceAtMost(Int.MAX_VALUE.toLong()) .toInt() val fallbackVersionName = DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss") .withZone(ZoneOffset.UTC) .format(Instant.ofEpochSecond(fallbackVersionCode.toLong())) fun loadSharedReleaseSigning(project: org.gradle.api.Project): SharedReleaseSigning? { val userHome = System.getProperty("user.home") ?: return null val configFile = File(userHome, ".config/android/release-signing.properties") if (!configFile.isFile) { return null } val props = Properties().apply { configFile.inputStream().use(::load) } val rawStoreFile = props.getProperty("storeFile")?.trim().orEmpty() val keyAlias = props.getProperty("keyAlias")?.trim().orEmpty() val storePassword = project.providers.gradleProperty("androidReleaseStorePassword").orNull?.trim().orEmpty() val keyPassword = project.providers.gradleProperty("androidReleaseKeyPassword").orNull?.trim().orEmpty() if (rawStoreFile.isEmpty() || keyAlias.isEmpty() || storePassword.isEmpty() || keyPassword.isEmpty()) { return null } val storeFile = File(rawStoreFile).let { candidate -> if (candidate.isAbsolute) candidate else File(configFile.parentFile, rawStoreFile) } if (!storeFile.isFile) { return null } return SharedReleaseSigning( storeFile = storeFile, keyAlias = keyAlias, storePassword = storePassword, keyPassword = keyPassword, ) } val sharedReleaseSigning = loadSharedReleaseSigning(project) val releaseTaskRequested = gradle.startParameter.taskNames.any { taskName -> taskName.contains("Release", ignoreCase = true) || taskName.contains("Bundle", ignoreCase = true) } android { namespace = "se.ajpanton.statusbartweak" compileSdk = 36 defaultConfig { applicationId = "se.ajpanton.statusbartweak" minSdk = 34 targetSdk = 36 val buildVersionName = project.findProperty("sbtVersionName")?.toString() ?: fallbackVersionName val buildVersionCode = project.findProperty("sbtVersionCode")?.toString()?.toIntOrNull() ?: fallbackVersionCode versionCode = buildVersionCode versionName = buildVersionName testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } signingConfigs { if (sharedReleaseSigning != null) { create("release") { storeFile = sharedReleaseSigning.storeFile storePassword = sharedReleaseSigning.storePassword keyAlias = sharedReleaseSigning.keyAlias keyPassword = sharedReleaseSigning.keyPassword } } } buildTypes { getByName("debug") { // Intentionally unchanged: local debug installs keep using the default debug signing. } getByName("release") { if (sharedReleaseSigning != null) { signingConfig = signingConfigs.getByName("release") } } } compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } // Modern replacement for packagingOptions packaging { resources { excludes += setOf( "META-INF/LICENSE", "META-INF/NOTICE" ) } } } if (releaseTaskRequested && sharedReleaseSigning == null) { throw GradleException( buildString { appendLine("Release signing is not configured.") appendLine("Expected:") appendLine("- ~/.config/android/release-signing.properties with storeFile and keyAlias") appendLine("- ~/.gradle/gradle.properties with androidReleaseStorePassword and androidReleaseKeyPassword") } ) } dependencies { implementation("androidx.appcompat:appcompat:1.6.1") implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") implementation("com.google.android.material:material:1.9.0") // Modern Xposed API, provided by the official libxposed Maven artifact compileOnly("io.github.libxposed:api:101.0.1") testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") }