import 'dart:io'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/foundation.dart'; import 'package:matrix/matrix.dart'; import 'package:shared_preferences/shared_preferences.dart'; // Self-hosted Sygnal instance on the Pi — must be configured with the // Firebase service account for the 'chat-pyramid' Firebase project. // push.element.io only supports Element's own apps and will actively reject // chat.pyramid.pyramid pushkeys, causing the homeserver to auto-remove the pusher. const _kGatewayUrl = 'https://push.steggi-matrix.work/_matrix/push/v1/notify'; const _kAppId = 'chat.pyramid.pyramid'; // ── Foreground-Setup & Pusher-Registrierung ─────────────────────────────────── Future fcmDiagnostics(Client matrixClient) async { if (!Platform.isAndroid) return 'Kein Android'; final buf = StringBuffer(); try { await Firebase.initializeApp(); buf.writeln('✓ Firebase initialisiert'); } catch (e) { buf.writeln('✗ Firebase-Init: $e'); return buf.toString(); } try { final settings = await FirebaseMessaging.instance.requestPermission(); buf.writeln('✓ Berechtigung: ${settings.authorizationStatus.name}'); } catch (e) { buf.writeln('✗ Berechtigung: $e'); } try { final token = await FirebaseMessaging.instance.getToken(); if (token != null) { buf.writeln('✓ FCM-Token: ${token.substring(0, 20)}…'); } else { buf.writeln('✗ FCM-Token: null'); } } catch (e) { buf.writeln('✗ FCM-Token: $e'); } try { final pushers = await matrixClient.getPushers() ?? []; final fcmPushers = pushers.where((p) => p.appId == _kAppId).toList(); if (fcmPushers.isEmpty) { buf.writeln('✗ Pusher: keiner bei Synapse registriert!'); } else { buf.writeln('✓ Pusher: ${fcmPushers.length} registriert'); } } catch (e) { buf.writeln('✗ Pusher-Abfrage: $e'); } return buf.toString(); } Future initFcm(Client matrixClient) async { if (!Platform.isAndroid) return; try { await Firebase.initializeApp(); } catch (_) { // Bereits initialisiert (main.dart) oder google-services.json fehlt – weiter } // Background messages are handled by handleBackgroundMessage (background_push.dart) // via FlutterFirebaseMessagingService. It decrypts E2EE events using the Matrix // SDK and shows notifications via flutter_local_notifications with // showsUserInterface:false so no Activity opens when the user replies. // Notification-Berechtigung anfordern (Android 13+) await FirebaseMessaging.instance.requestPermission(); // Foreground-Notifications unterdrücken — App zeigt selbst Nachrichten await FirebaseMessaging.instance .setForegroundNotificationPresentationOptions( alert: false, badge: false, sound: false, ); // Re-register pusher if token was refreshed while the app was killed. final prefs = await SharedPreferences.getInstance(); final pendingToken = prefs.getString('flutter.fcm_pending_token'); if (pendingToken != null && matrixClient.isLogged()) { await _registerPusher(matrixClient, pendingToken); await prefs.remove('flutter.fcm_pending_token'); } final token = pendingToken ?? await _getToken(); if (token != null && matrixClient.isLogged()) { await _registerPusher(matrixClient, token); // Save homeserver + token for PushService.kt (Kotlin native notification handler) await _saveNotifCredentials(matrixClient); } // Bei Token-Refresh: Pusher + gespeicherte Credentials aktualisieren FirebaseMessaging.instance.onTokenRefresh.listen((newToken) async { if (matrixClient.isLogged()) { await _registerPusher(matrixClient, newToken); await _saveNotifCredentials(matrixClient); } }); } Future _saveNotifCredentials(Client client) async { try { final prefs = await SharedPreferences.getInstance(); await prefs.setString('notif_homeserver', client.homeserver.toString()); await prefs.setString('notif_access_token', client.accessToken ?? ''); } catch (_) {} } Future _getToken() async { try { return await FirebaseMessaging.instance.getToken(); } catch (_) { return null; } } Future _registerPusher(Client client, String fcmToken) async { try { await client.postPusher( Pusher( pushkey: fcmToken, kind: 'http', appId: _kAppId, appDisplayName: 'Pyramid', deviceDisplayName: client.deviceName ?? 'Android', lang: 'de', data: PusherData( url: Uri.parse(_kGatewayUrl), format: 'event_id_only', ), ), append: true, ); } catch (e) { debugPrint('[FCM] Pusher-Registrierung fehlgeschlagen: $e'); } } /// Tries to register the FCM pusher and returns a human-readable result string. Future retryPusherRegistration(Client client) async { if (!Platform.isAndroid) return 'Kein Android'; final buf = StringBuffer(); try { String? token; try { token = await FirebaseMessaging.instance.getToken(); } catch (e) { return '✗ FCM-Token: $e'; } if (token == null) { return '✗ FCM-Token: null (Google Play Services Problem?)'; } buf.writeln('✓ FCM-Token: ${token.substring(0, 20)}…'); buf.writeln(' Gateway: $_kGatewayUrl'); await client.postPusher( Pusher( pushkey: token, kind: 'http', appId: _kAppId, appDisplayName: 'Pyramid', deviceDisplayName: client.deviceName ?? 'Android', lang: 'de', data: PusherData( url: Uri.parse(_kGatewayUrl), format: 'event_id_only', ), ), append: true, ); buf.writeln('✓ Pusher registriert'); await _saveNotifCredentials(client); buf.writeln('✓ Credentials gespeichert'); } catch (e) { buf.writeln('✗ Fehler: $e'); } return buf.toString().trim(); }