leafittome/lib/features/notifications/data/push_registration_service.dart
cschlaefke 52e72c25b1 Push-Erinnerungen: FCM-Registrierung in der App, geplante Function sendDailyReminders
- Function läuft alle 15 Min (europe-west3): prüft Erinnerungszeit pro Nutzer (Zeitzonen-korrekt), berechnet fällige Aufgaben, schickt eine Sammel-Push pro Tag, räumt ungültige Tokens auf
- App registriert Gerät nach Login (Berechtigung, Token, Zeitzone), Erinnerungszeit wird ins Nutzer-Dokument synchronisiert
- Doku Schritt 6 inkl. APNs-Anleitung für iOS (sobald Developer Account da ist)

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

73 lines
2.5 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),
);
});