diff --git a/ANDROID_KOTLIN_PLAN.md b/ANDROID_KOTLIN_PLAN.md index 5999d73..518f43d 100644 --- a/ANDROID_KOTLIN_PLAN.md +++ b/ANDROID_KOTLIN_PLAN.md @@ -141,6 +141,11 @@ Kurz: Ziel ist eine native Android-App mit Kotlin + Jetpack Compose, die die Web [ ] 22. Analytics: Firebase / Matomo Integration (je nach Datenschutz) [x] 23. Crash-Reporting: Sentry / Crashlytics integrieren [ ] 24. Build/Release: App signing, Release-Notes, Play-Store-Metadaten vorbereiten + - [x] Technische Release-Basis vorbereitet: `ANDROID_VERSION_CODE` und `ANDROID_VERSION_NAME` als Gradle-Properties eingeführt (`android-app/gradle.properties`) und im App-Gradle verdrahtet. + - [x] Production-Release-Flavor auf Produktiv-Backend parametrisierbar gemacht (`PRODUCTION_API_BASE_URL`, Default `https://harheimertc.de/`). + - [x] Release-Signing per sicheren Gradle-Properties vorbereitet (`RELEASE_STORE_FILE`, `RELEASE_STORE_PASSWORD`, `RELEASE_KEY_ALIAS`, `RELEASE_KEY_PASSWORD`) statt Hardcoding. + - [x] `:app:assembleProductionRelease` erfolgreich gebaut (Stand 2026-05-29). + - [ ] Offen: Finales Upload-Keystore + Credentials in CI/Build-Host hinterlegen, Play-Store-Release-Notes und Store-Metadaten pflegen. [ ] 25. Dokumentation: `README-android.md` mit Setup, Architektur und Release-Anleitung 5) Kurzzeit-MVP (Priorität für erste Version) diff --git a/android-app/app/build.gradle.kts b/android-app/app/build.gradle.kts index 90cdb9b..7cb3483 100644 --- a/android-app/app/build.gradle.kts +++ b/android-app/app/build.gradle.kts @@ -8,9 +8,31 @@ plugins { val localApiBaseUrl = providers.gradleProperty("LOCAL_API_BASE_URL") .orElse("https://harheimertc.tsschulz.de/") .get() +val productionApiBaseUrl = providers.gradleProperty("PRODUCTION_API_BASE_URL") + .orElse("https://harheimertc.de/") + .get() val sentryDsn = providers.gradleProperty("SENTRY_DSN") .orElse("") .get() +val androidVersionCode = providers.gradleProperty("ANDROID_VERSION_CODE") + .orElse("2") + .get() + .toInt() +val androidVersionName = providers.gradleProperty("ANDROID_VERSION_NAME") + .orElse("1.0.0") + .get() +val releaseMinifyEnabled = providers.gradleProperty("RELEASE_MINIFY_ENABLED") + .orElse("true") + .get() + .toBoolean() +val releaseStoreFile = providers.gradleProperty("RELEASE_STORE_FILE").orNull +val releaseStorePassword = providers.gradleProperty("RELEASE_STORE_PASSWORD").orNull +val releaseKeyAlias = providers.gradleProperty("RELEASE_KEY_ALIAS").orNull +val releaseKeyPassword = providers.gradleProperty("RELEASE_KEY_PASSWORD").orNull +val hasReleaseSigning = !releaseStoreFile.isNullOrBlank() && + !releaseStorePassword.isNullOrBlank() && + !releaseKeyAlias.isNullOrBlank() && + !releaseKeyPassword.isNullOrBlank() android { namespace = "de.harheimertc" @@ -19,9 +41,38 @@ android { defaultConfig { applicationId = "de.harheimertc" minSdk = 24 - targetSdk = 34 - versionCode = 1 - versionName = "0.1.0" + targetSdk = 35 + versionCode = androidVersionCode + versionName = androidVersionName + } + + signingConfigs { + create("release") { + if (hasReleaseSigning) { + storeFile = file(releaseStoreFile!!) + storePassword = releaseStorePassword + keyAlias = releaseKeyAlias + keyPassword = releaseKeyPassword + } + } + } + + buildTypes { + getByName("release") { + isMinifyEnabled = releaseMinifyEnabled + isShrinkResources = false + ndk { + // Generate a native debug symbols archive for Play Console uploads. + debugSymbolLevel = "SYMBOL_TABLE" + } + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro", + ) + if (hasReleaseSigning) { + signingConfig = signingConfigs.getByName("release") + } + } } flavorDimensions += "environment" @@ -46,7 +97,7 @@ android { } create("production") { dimension = "environment" - buildConfigField("String", "API_BASE_URL", "\"https://harheimertc.tsschulz.de/\"") + buildConfigField("String", "API_BASE_URL", "\"$productionApiBaseUrl\"") buildConfigField("String", "SENTRY_DSN", "\"$sentryDsn\"") buildConfigField("String", "ENVIRONMENT_NAME", "\"\"") manifestPlaceholders["usesCleartextTraffic"] = "false" @@ -72,6 +123,44 @@ android { } } +val packageNativeDebugSymbolsForProductionRelease = tasks.register("packageNativeDebugSymbolsForProductionRelease") { + group = "distribution" + description = "Packages production release native libraries into a Play Console debug symbols zip." + dependsOn(":app:mergeProductionReleaseNativeLibs") + from(layout.buildDirectory.dir("intermediates/merged_native_libs/productionRelease/mergeProductionReleaseNativeLibs/out/lib")) + destinationDirectory.set(layout.buildDirectory.dir("outputs/native-debug-symbols/productionRelease")) + archiveFileName.set("native-debug-symbols.zip") +} + +val collectPlayStoreArtifacts = tasks.register("collectPlayStoreArtifacts") { + group = "distribution" + description = "Builds production release artifacts and collects AAB, mapping, and native symbols for Play Console upload." + dependsOn(":app:bundleProductionRelease") + dependsOn(packageNativeDebugSymbolsForProductionRelease) + + doLast { + val outputDir = layout.buildDirectory.dir("outputs/playstore/productionRelease").get().asFile + outputDir.mkdirs() + + val bundleFile = layout.buildDirectory.file("outputs/bundle/productionRelease/app-production-release.aab").get().asFile + if (bundleFile.exists()) { + bundleFile.copyTo(outputDir.resolve(bundleFile.name), overwrite = true) + } + + val mappingFile = layout.buildDirectory.file("outputs/mapping/productionRelease/mapping.txt").get().asFile + if (mappingFile.exists()) { + mappingFile.copyTo(outputDir.resolve("mapping.txt"), overwrite = true) + } + + val nativeSymbolsZip = layout.buildDirectory.file("outputs/native-debug-symbols/productionRelease/native-debug-symbols.zip").get().asFile + if (nativeSymbolsZip.exists()) { + nativeSymbolsZip.copyTo(outputDir.resolve("native-debug-symbols.zip"), overwrite = true) + } + + println("Play Store artifacts prepared in: ${outputDir.absolutePath}") + } +} + kotlin { compilerOptions { jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17) diff --git a/android-app/app/instantTest/release/app-instantTest-release.aab b/android-app/app/instantTest/release/app-instantTest-release.aab new file mode 100644 index 0000000..b3a9f0a Binary files /dev/null and b/android-app/app/instantTest/release/app-instantTest-release.aab differ diff --git a/android-app/app/local/release/app-local-release.aab b/android-app/app/local/release/app-local-release.aab new file mode 100644 index 0000000..83bdedd Binary files /dev/null and b/android-app/app/local/release/app-local-release.aab differ diff --git a/android-app/app/production/release/app-production-release.aab b/android-app/app/production/release/app-production-release.aab new file mode 100644 index 0000000..e80d41e Binary files /dev/null and b/android-app/app/production/release/app-production-release.aab differ diff --git a/android-app/app/proguard-rules.pro b/android-app/app/proguard-rules.pro new file mode 100644 index 0000000..c7013bd --- /dev/null +++ b/android-app/app/proguard-rules.pro @@ -0,0 +1,2 @@ +# Project-specific R8/ProGuard rules for release builds. +# Keep this file intentionally minimal and add rules only when needed. diff --git a/android-app/gradle.properties b/android-app/gradle.properties index 56dd67c..132cdec 100644 --- a/android-app/gradle.properties +++ b/android-app/gradle.properties @@ -3,3 +3,22 @@ org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 org.gradle.workers.max=2 # Local API base URL for running the app from Android Studio / Gradle LOCAL_API_BASE_URL=https://harheimertc.tsschulz.de/ + +# Production backend for Play Store build variant +PRODUCTION_API_BASE_URL=https://harheimertc.de/ + +# Android app versioning for Play Store uploads +ANDROID_VERSION_CODE=3 +ANDROID_VERSION_NAME=1.0.0 + +# Enable R8 for release by default so mapping.txt is generated for Play Console. +RELEASE_MINIFY_ENABLED=true + +# Release signing (set in local, untracked gradle.properties or via CI secrets) +# RELEASE_STORE_FILE=/absolute/path/to/keystore.jks +# RELEASE_STORE_PASSWORD=*** +# RELEASE_KEY_ALIAS=*** +# RELEASE_KEY_PASSWORD=*** + +# Build and collect Play Store upload artifacts: +# ./gradlew :app:collectPlayStoreArtifacts