diff --git a/android-app/app/build.gradle.kts b/android-app/app/build.gradle.kts index 76c36e1..82d7a5a 100755 --- a/android-app/app/build.gradle.kts +++ b/android-app/app/build.gradle.kts @@ -78,12 +78,12 @@ val ensureProductionApiBaseUrl = tasks.register("ensureProductionApiBaseUrl") { android { namespace = "de.harheimertc" - compileSdk = 35 + compileSdk = 36 defaultConfig { applicationId = "de.harheimertc" minSdk = 24 - targetSdk = 35 + targetSdk = 36 versionCode = androidVersionCode versionName = androidVersionName } diff --git a/android-app/app/production/release/app-production-release.aab b/android-app/app/production/release/app-production-release.aab old mode 100755 new mode 100644 index 8085ef2..7e800cb Binary files a/android-app/app/production/release/app-production-release.aab and b/android-app/app/production/release/app-production-release.aab differ diff --git a/android-app/app/src/main/java/de/harheimertc/data/ApiService.kt b/android-app/app/src/main/java/de/harheimertc/data/ApiService.kt index a1e9855..ce19e2a 100755 --- a/android-app/app/src/main/java/de/harheimertc/data/ApiService.kt +++ b/android-app/app/src/main/java/de/harheimertc/data/ApiService.kt @@ -166,6 +166,7 @@ data class MembershipResponse( val success: Boolean = false, val message: String? = null, val downloadUrl: String? = null, + val downloadToken: String? = null, ) data class LoginRequest( val email: String, @@ -660,7 +661,10 @@ interface ApiService { @Streaming @GET - suspend fun downloadMembershipPdf(@Url downloadUrl: String): Response + suspend fun downloadMembershipPdf( + @Url downloadUrl: String, + @retrofit2.http.Header("X-Membership-Download-Token") downloadToken: String? = null, + ): Response @POST("/api/auth/login") suspend fun login(@Body request: LoginRequest): Response diff --git a/android-app/app/src/main/java/de/harheimertc/notifications/HarheimerNotifications.kt b/android-app/app/src/main/java/de/harheimertc/notifications/HarheimerNotifications.kt index e67462a..634d79d 100755 --- a/android-app/app/src/main/java/de/harheimertc/notifications/HarheimerNotifications.kt +++ b/android-app/app/src/main/java/de/harheimertc/notifications/HarheimerNotifications.kt @@ -16,14 +16,16 @@ import de.harheimertc.R import de.harheimertc.ui.navigation.Destinations object HarheimerNotifications { - const val DEFAULT_CHANNEL_ID = "harheimer_tc_updates" + // A new ID is intentional: Android does not allow an app update to raise + // the importance of an already-created notification channel. + const val DEFAULT_CHANNEL_ID = "harheimer_tc_updates_v2" fun createChannels(context: Context) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return val channel = NotificationChannel( DEFAULT_CHANNEL_ID, "Harheimer TC", - NotificationManager.IMPORTANCE_DEFAULT, + NotificationManager.IMPORTANCE_HIGH, ).apply { description = "Benachrichtigungen des Harheimer TC" } @@ -47,7 +49,7 @@ object HarheimerNotifications { .setContentTitle(title) .setContentText(message) .setStyle(NotificationCompat.BigTextStyle().bigText(message)) - .setPriority(NotificationCompat.PRIORITY_DEFAULT) + .setPriority(NotificationCompat.PRIORITY_HIGH) .setContentIntent(createContentIntent(context, notificationId, data)) .setAutoCancel(true) .build() diff --git a/android-app/app/src/main/java/de/harheimertc/repositories/MembershipRepository.kt b/android-app/app/src/main/java/de/harheimertc/repositories/MembershipRepository.kt index 686703d..590f8d2 100755 --- a/android-app/app/src/main/java/de/harheimertc/repositories/MembershipRepository.kt +++ b/android-app/app/src/main/java/de/harheimertc/repositories/MembershipRepository.kt @@ -9,7 +9,7 @@ import java.io.File import javax.inject.Inject import javax.inject.Singleton -data class MembershipDocument(val message: String, val uri: String) +data class MembershipDocument(val message: String, val uri: String? = null) @Singleton class MembershipRepository @Inject constructor( @@ -18,21 +18,33 @@ class MembershipRepository @Inject constructor( ) { suspend fun submit(request: MembershipRequest): Result = runCatching { val response = api.generateMembershipPdf(request) - if (!response.isSuccessful) error("HTTP ${response.code()}") + if (!response.isSuccessful) { + val serverMessage = response.errorBody()?.string() + ?.let { Regex("\\\"(?:statusMessage|message)\\\"\\s*:\\s*\\\"([^\\\"]+)\\\"").find(it)?.groupValues?.getOrNull(1) ?: it } + ?.takeIf { it.isNotBlank() } + error(serverMessage ?: "Der Antrag konnte nicht übermittelt werden (HTTP ${response.code()}).") + } val body = response.body() ?: error("Leere Antwort") if (!body.success) error(body.message ?: "Antrag konnte nicht erstellt werden.") - val downloadUrl = body.downloadUrl ?: error("PDF-Download fehlt.") - val documentResponse = api.downloadMembershipPdf(downloadUrl) - if (!documentResponse.isSuccessful) error("PDF konnte nicht heruntergeladen werden.") - val directory = File(context.cacheDir, "membership").apply { mkdirs() } - val file = File(directory, "beitrittserklaerung.pdf") - documentResponse.body()?.byteStream()?.use { input -> - file.outputStream().use { output -> input.copyTo(output) } - } ?: error("Leere PDF-Antwort") - val uri = FileProvider.getUriForFile(context, "${context.packageName}.files", file) + val uri = body.downloadUrl?.let { downloadUrl -> + runCatching { + val documentResponse = api.downloadMembershipPdf(downloadUrl, body.downloadToken) + if (!documentResponse.isSuccessful) error("PDF konnte nicht heruntergeladen werden.") + val directory = File(context.cacheDir, "membership").apply { mkdirs() } + val file = File(directory, "beitrittserklaerung.pdf") + documentResponse.body()?.byteStream()?.use { input -> + file.outputStream().use { output -> input.copyTo(output) } + } ?: error("Leere PDF-Antwort") + FileProvider.getUriForFile(context, "${context.packageName}.files", file).toString() + }.getOrNull() + } MembershipDocument( - message = body.message ?: "Beitrittsformular erfolgreich erstellt.", - uri = uri.toString(), + message = if (uri == null) { + "${body.message ?: "Mitgliedschaftsantrag erfolgreich übermittelt."} Das PDF konnte nicht auf diesem Gerät gespeichert werden." + } else { + body.message ?: "Beitrittsformular erfolgreich erstellt." + }, + uri = uri, ) } } diff --git a/android-app/app/src/main/java/de/harheimertc/ui/screens/membership/MembershipViewModel.kt b/android-app/app/src/main/java/de/harheimertc/ui/screens/membership/MembershipViewModel.kt index c7700c7..359f859 100755 --- a/android-app/app/src/main/java/de/harheimertc/ui/screens/membership/MembershipViewModel.kt +++ b/android-app/app/src/main/java/de/harheimertc/ui/screens/membership/MembershipViewModel.kt @@ -81,7 +81,10 @@ class MembershipViewModel @Inject constructor(private val repository: Membership _state.value = _state.value.copy(sending = false, fieldErrors = emptyMap(), message = document.message, pdfUri = document.uri) } .onFailure { - _state.value = _state.value.copy(sending = false, error = "Beitrittsformular konnte nicht erstellt werden.") + _state.value = _state.value.copy( + sending = false, + error = it.message ?: "Beitrittsformular konnte nicht erstellt werden.", + ) } } } diff --git a/android-app/gradle.properties b/android-app/gradle.properties index 6a06329..df9ba17 100755 --- a/android-app/gradle.properties +++ b/android-app/gradle.properties @@ -8,8 +8,8 @@ LOCAL_API_BASE_URL=https://harheimertc.tsschulz.de/ PRODUCTION_API_BASE_URL=https://harheimertc.de/ # Android app versioning for Play Store uploads -ANDROID_VERSION_CODE=29 -ANDROID_VERSION_NAME=0.9.24 +ANDROID_VERSION_CODE=32 +ANDROID_VERSION_NAME=0.9.27 # Temporary hotfix: disable R8 minification for release to avoid Retrofit generic signature stripping. RELEASE_MINIFY_ENABLED=false diff --git a/app.vue b/app.vue index e583034..ee6b2ed 100755 --- a/app.vue +++ b/app.vue @@ -1,7 +1,7 @@