leafittome/lib/features/notifications/data/push_registration_service.dart
cschlaefke 4bceb8747b Fix: Push-Registrierung griff nicht bei bereits eingeloggtem Nutzer
registerDevice lief nur bei Auth-Änderungen (ref.listen) – bestand der Login
schon beim App-Start, wurde nie ein FCM-Token gespeichert. Jetzt reaktiv über
pushSetupProvider (ref.watch). Zusätzlich: temporäre debugReminders-Function
zur Diagnose der Push-Strecke.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 15:39:02 +02:00

83 lines
2.9 KiB
Dart

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<void> registerDevice(User user);
Future<void> syncReminderTime(TimeOfDay time);
}
class FirebasePushRegistrationService implements PushRegistrationService {
FirebasePushRegistrationService(this._firestore, this._auth);
final FirebaseFirestore _firestore;
final FirebaseAuth _auth;
bool _tokenListenerAttached = false;
@override
Future<void> 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<void> _saveToken(String uid, String token) async {
final timezone = await FlutterTimezone.getLocalTimezone();
await _firestore.collection('users').doc(uid).set({
'fcmTokens': FieldValue.arrayUnion([token]),
'timezone': timezone,
}, SetOptions(merge: true));
}
@override
Future<void> 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<PushRegistrationService>((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<void>((ref) {
final user = ref.watch(authStateProvider).value;
if (user != null) {
ref.read(pushRegistrationServiceProvider).registerDevice(user);
}
});