wip: Sicherungs-Commit aller Änderungen seit April + Arbeitsstruktur (CLAUDE.md, ROADMAP.md, PROGRESS.md, Autopilot)
6 Wochen uncommittete Arbeit (Voice-Channels, LiveKit-Manager, Settings-Modal u.v.m.) als ein WIP-Commit gesichert, damit nichts verloren geht und der Pi den aktuellen Stand klonen kann. Thematische Aufarbeitung: siehe ROADMAP M0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CPrAGBxBT6GfPXzeWQ4AXb
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
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<String> 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<void> 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<void> _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<String?> _getToken() async {
|
||||
try {
|
||||
return await FirebaseMessaging.instance.getToken();
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _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<String> 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();
|
||||
}
|
||||
Reference in New Issue
Block a user