Member registration fixed
Some checks failed
Code Analysis and Production Deploy / analyze (push) Failing after 6m17s
Code Analysis and Production Deploy / deploy-production (push) Has been skipped
Code Analysis and Production Deploy / deploy-test (push) Has been skipped

This commit is contained in:
Torsten Schulz (local)
2026-08-12 11:15:07 +02:00
parent 281c25b05d
commit cb89fdd911
15 changed files with 263 additions and 41 deletions

View File

@@ -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<ResponseBody>
suspend fun downloadMembershipPdf(
@Url downloadUrl: String,
@retrofit2.http.Header("X-Membership-Download-Token") downloadToken: String? = null,
): Response<ResponseBody>
@POST("/api/auth/login")
suspend fun login(@Body request: LoginRequest): Response<LoginResponse>

View File

@@ -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()

View File

@@ -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<MembershipDocument> = 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,
)
}
}

View File

@@ -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.",
)
}
}
}