import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_timezone/flutter_timezone.dart'; import '../../../core/firebase/firebase_providers.dart'; /// Registriert das Gerät für Push-Erinnerungen und hält die /// Erinnerungs-Einstellungen im Nutzer-Dokument aktuell. /// /// Abstrakte Basis, damit Tests eine No-Op-Variante einsetzen können. abstract class PushRegistrationService { Future registerDevice(User user); Future syncReminderTime(TimeOfDay time); } class FirebasePushRegistrationService implements PushRegistrationService { FirebasePushRegistrationService(this._firestore, this._auth); final FirebaseFirestore _firestore; final FirebaseAuth _auth; bool _tokenListenerAttached = false; @override Future registerDevice(User user) async { final messaging = FirebaseMessaging.instance; // Fragt beim ersten Mal die Push-Berechtigung ab (iOS-Dialog, // Android 13+ Systemdialog); danach ist der Aufruf ein No-Op. final settings = await messaging.requestPermission(); if (settings.authorizationStatus == AuthorizationStatus.denied) return; final token = await messaging.getToken(); if (token != null) { await _saveToken(user.uid, token); } if (!_tokenListenerAttached) { _tokenListenerAttached = true; messaging.onTokenRefresh.listen((newToken) { final current = _auth.currentUser; if (current != null) _saveToken(current.uid, newToken); }); } } Future _saveToken(String uid, String token) async { final timezone = await FlutterTimezone.getLocalTimezone(); await _firestore.collection('users').doc(uid).set({ 'fcmTokens': FieldValue.arrayUnion([token]), // IANA-Kennung, z. B. "Europe/Berlin" – das Objekt selbst ist nicht // Firestore-serialisierbar. 'timezone': timezone.identifier, }, SetOptions(merge: true)); } @override Future syncReminderTime(TimeOfDay time) async { final user = _auth.currentUser; if (user == null) return; final formatted = '${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}'; await _firestore.collection('users').doc(user.uid).set({ 'reminderTime': formatted, }, SetOptions(merge: true)); } } final pushRegistrationServiceProvider = Provider((ref) { return FirebasePushRegistrationService( ref.watch(firestoreProvider), ref.watch(firebaseAuthProvider), ); }); /// Sorgt dafür, dass das Gerät registriert ist, sobald (und solange) ein /// Nutzer angemeldet ist — auch wenn der Login schon vor dem App-Start /// bestand. Wird in app.dart per ref.watch aktiv gehalten. final pushSetupProvider = Provider((ref) { final user = ref.watch(authStateProvider).value; if (user != null) { ref.read(pushRegistrationServiceProvider).registerDevice(user).catchError( (Object error, StackTrace stack) { // Bewusst nur loggen: Push-Registrierung darf den App-Start nie // blockieren, aber Fehler müssen im Log sichtbar sein. debugPrint('Push-Registrierung fehlgeschlagen: $error'); }, ); } });