dev #53
Binary file not shown.
@@ -27,7 +27,10 @@ class PushTokenRepository @Inject constructor(
|
||||
appVersion = "${BuildConfig.VERSION_NAME}+${BuildConfig.VERSION_CODE}",
|
||||
),
|
||||
)
|
||||
if (!response.isSuccessful) error("Push-Token konnte nicht registriert werden.")
|
||||
if (!response.isSuccessful) {
|
||||
val detail = response.errorBody()?.string().orEmpty().replace(Regex("\\s+"), " ").take(180)
|
||||
error("Push-Token konnte nicht registriert werden (HTTP ${response.code()})${if (detail.isBlank()) "" else ": $detail"}")
|
||||
}
|
||||
}
|
||||
}.onFailure { error ->
|
||||
Log.w("PushTokenRepository", "Push-Token Registrierung fehlgeschlagen", error)
|
||||
|
||||
@@ -40,6 +40,7 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.navigation.NavController
|
||||
import de.harheimertc.notifications.HarheimerNotifications
|
||||
import de.harheimertc.BuildConfig
|
||||
import de.harheimertc.repositories.Mannschaft
|
||||
import de.harheimertc.repositories.NotificationPreferences
|
||||
import de.harheimertc.ui.components.LoadingState
|
||||
@@ -103,6 +104,23 @@ fun NotificationSettingsScreen(
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
NotificationCard("Geräte-Registrierung") {
|
||||
Text("Der Push-Token wird dem aktuell angemeldeten Konto auf diesem Server zugeordnet:", color = Accent700)
|
||||
Text(BuildConfig.API_BASE_URL, color = Accent900, style = MaterialTheme.typography.bodySmall)
|
||||
Button(
|
||||
onClick = viewModel::registerCurrentDevice,
|
||||
enabled = !state.deviceRegistrationInProgress,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Text(if (state.deviceRegistrationInProgress) "Gerät wird registriert..." else "Android-Gerät jetzt registrieren")
|
||||
}
|
||||
state.deviceRegistrationMessage?.let { message ->
|
||||
Text(message, color = if (state.deviceRegistrationError) MaterialTheme.colorScheme.error else Color(0xFF166534))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (state.loading) {
|
||||
item { LoadingState("Benachrichtigungseinstellungen werden geladen...") }
|
||||
} else {
|
||||
|
||||
@@ -8,6 +8,7 @@ import de.harheimertc.repositories.LoginRepository
|
||||
import de.harheimertc.repositories.MannschaftenRepository
|
||||
import de.harheimertc.repositories.NotificationPreferences
|
||||
import de.harheimertc.repositories.NotificationPreferencesRepository
|
||||
import de.harheimertc.repositories.PushTokenRepository
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -22,6 +23,9 @@ data class NotificationSettingsUiState(
|
||||
val seasons: List<String> = emptyList(),
|
||||
val error: String? = null,
|
||||
val saveError: String? = null,
|
||||
val deviceRegistrationMessage: String? = null,
|
||||
val deviceRegistrationError: Boolean = false,
|
||||
val deviceRegistrationInProgress: Boolean = false,
|
||||
)
|
||||
|
||||
@HiltViewModel
|
||||
@@ -29,6 +33,7 @@ class NotificationSettingsViewModel @Inject constructor(
|
||||
private val preferencesRepository: NotificationPreferencesRepository,
|
||||
private val mannschaftenRepository: MannschaftenRepository,
|
||||
private val loginRepository: LoginRepository,
|
||||
private val pushTokenRepository: PushTokenRepository,
|
||||
) : ViewModel() {
|
||||
private val _state = MutableStateFlow(NotificationSettingsUiState())
|
||||
val state: StateFlow<NotificationSettingsUiState> = _state
|
||||
@@ -82,6 +87,30 @@ class NotificationSettingsViewModel @Inject constructor(
|
||||
update(current.copy(selectedTeamSlugs = nextTeams))
|
||||
}
|
||||
|
||||
fun registerCurrentDevice() {
|
||||
viewModelScope.launch {
|
||||
_state.value = _state.value.copy(
|
||||
deviceRegistrationInProgress = true,
|
||||
deviceRegistrationMessage = null,
|
||||
deviceRegistrationError = false,
|
||||
)
|
||||
pushTokenRepository.registerCurrentDevice()
|
||||
.onSuccess {
|
||||
_state.value = _state.value.copy(
|
||||
deviceRegistrationInProgress = false,
|
||||
deviceRegistrationMessage = "Dieses Android-Gerät wurde beim Server registriert.",
|
||||
)
|
||||
}
|
||||
.onFailure { error ->
|
||||
_state.value = _state.value.copy(
|
||||
deviceRegistrationInProgress = false,
|
||||
deviceRegistrationError = true,
|
||||
deviceRegistrationMessage = error.message ?: "Android-Gerät konnte nicht registriert werden.",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadTeams(settings: NotificationPreferences, seasons: List<String>, currentUserName: String, syncRemote: Boolean = false) {
|
||||
mannschaftenRepository.fetchMannschaften(settings.selectedTeamSeason)
|
||||
.onSuccess { teams ->
|
||||
|
||||
@@ -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=34
|
||||
ANDROID_VERSION_NAME=0.9.29
|
||||
ANDROID_VERSION_CODE=35
|
||||
ANDROID_VERSION_NAME=0.9.30
|
||||
|
||||
# Temporary hotfix: disable R8 minification for release to avoid Retrofit generic signature stripping.
|
||||
RELEASE_MINIFY_ENABLED=false
|
||||
|
||||
@@ -47,6 +47,6 @@ const formattedLastImport = computed(() => status.value.lastSuccessfulImportAt ?
|
||||
async function load() { status.value = await $fetch('/api/cms/mytischtennis') }
|
||||
async function save() { busy.value = true; message.value = ''; error.value = false; try { status.value = await $fetch('/api/cms/mytischtennis', { method: 'PUT', body: form }); form.password = ''; message.value = 'Zugang verschlüsselt gespeichert.' } catch { error.value = true; message.value = 'Zugang konnte nicht gespeichert werden.' } finally { busy.value = false } }
|
||||
async function runImport() { busy.value = true; message.value = ''; error.value = false; try { const result = await $fetch('/api/cms/mytischtennis/import', { method: 'POST' }); message.value = `${result.rowCount} Werte wurden aktualisiert.`; await load() } catch (caught) { error.value = true; message.value = caught?.data?.message || 'Abruf fehlgeschlagen. Details stehen im Serverlog.'; await load().catch(() => {}) } finally { busy.value = false } }
|
||||
async function sendTestPush() { busy.value = true; message.value = ''; error.value = false; try { const result = await $fetch('/api/cms/notifications/test', { method: 'POST' }); message.value = result.sent > 0 ? 'Push-Test wurde an deine registrierten Android-Geräte gesendet.' : 'Kein registriertes Android-Gerät gefunden. Bitte App einmal öffnen und Benachrichtigungen erlauben.' } catch (caught) { error.value = true; message.value = caught?.data?.statusMessage || caught?.data?.message || 'Push-Test konnte nicht gesendet werden.' } finally { busy.value = false } }
|
||||
async function sendTestPush() { busy.value = true; message.value = ''; error.value = false; try { const result = await $fetch('/api/cms/notifications/test', { method: 'POST' }); if (result.registeredTokenCount === 0) { message.value = 'Kein Android-Token für dieses Konto gespeichert.'; error.value = true } else if (result.skipped) { message.value = `${result.registeredTokenCount} Android-Gerät(e) registriert, aber der FCM-Push-Dienst ist auf dem Server nicht konfiguriert.`; error.value = true } else if (result.deliveryError) { message.value = `${result.registeredTokenCount} Android-Gerät(e) registriert, aber: ${result.deliveryError}`; error.value = true } else if (result.sent > 0) { message.value = `Push-Test wurde an ${result.sent} registrierte(s) Android-Gerät(e) gesendet.` } else { message.value = `${result.registeredTokenCount} Android-Gerät(e) registriert, Zustellung fehlgeschlagen. Bitte Serverlog prüfen.`; error.value = true } } catch (caught) { error.value = true; message.value = caught?.data?.statusMessage || caught?.data?.message || 'Push-Test konnte nicht gesendet werden.' } finally { busy.value = false } }
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getUserFromToken, hasRole } from '../../../utils/auth.js'
|
||||
import { sendTestPushToUser } from '../../../utils/push-notifications.js'
|
||||
import { androidPushTokenCountForUser, sendTestPushToUser } from '../../../utils/push-notifications.js'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const token = getCookie(event, 'auth_token') || getHeader(event, 'authorization')?.replace(/^Bearer\s+/i, '')
|
||||
@@ -7,6 +7,16 @@ export default defineEventHandler(async (event) => {
|
||||
if (!user) throw createError({ statusCode: 401, statusMessage: 'Nicht authentifiziert' })
|
||||
if (!hasRole(user, 'admin')) throw createError({ statusCode: 403, statusMessage: 'Nur Administratoren dürfen einen Push-Test auslösen.' })
|
||||
|
||||
const result = await sendTestPushToUser(user.id)
|
||||
return { success: true, ...result }
|
||||
const registeredTokenCount = await androidPushTokenCountForUser(user.id)
|
||||
try {
|
||||
const result = await sendTestPushToUser(user.id)
|
||||
return { success: true, registeredTokenCount, ...result }
|
||||
} catch (error) {
|
||||
console.error('FCM Test-Push konnte nicht vorbereitet werden:', error)
|
||||
return {
|
||||
success: false,
|
||||
registeredTokenCount,
|
||||
deliveryError: 'Die FCM-Serververbindung konnte nicht aufgebaut werden. Details stehen im Serverlog.'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -102,6 +102,11 @@ export function upsertPushToken(user, { token, platform = 'android', appVersion
|
||||
return user
|
||||
}
|
||||
|
||||
export async function androidPushTokenCountForUser(userId) {
|
||||
const user = (await readUsers()).find(entry => entry?.id === userId)
|
||||
return pushTokensForUser(user).length
|
||||
}
|
||||
|
||||
async function sendFcmMessage({ serviceAccount, accessToken, token, data = {} }) {
|
||||
const projectId = projectIdFromServiceAccount(serviceAccount)
|
||||
if (!projectId) throw new Error('FCM project_id fehlt.')
|
||||
|
||||
Reference in New Issue
Block a user