From 3c7eb9ddcaefa195ec5b3363a12c26547f99b0ac Mon Sep 17 00:00:00 2001 From: Bernd Steckmeister Date: Mon, 6 Jul 2026 12:42:59 +0200 Subject: [PATCH] =?UTF-8?q?refactor:=20M2-Call-Pilot=20=E2=80=93=20Module?= =?UTF-8?q?=20call=5Fsignaling=20+=20voice=5Fchannel=20mit=20Fassaden=20(E?= =?UTF-8?q?ntscheidung=202)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CallSignalingService/callSignalingProvider und VoiceChannelService/ voiceChannelProvider als einzige oeffentliche Schnittstellen - voip_manager.dart / livekit_call_manager.dart in ihre Module verschoben, Logik unangetastet (nur Klassen-Koepfe, @override, Imports, Provider-Namen) - Alle Konsumenten entkoppelt; Kompositions-Punkt matrix_client.dart als dokumentierte Ausnahme. analyze sauber, 29 Tests gruen, Windows-Start ok. Call-Klick-/Geraetetest steht aus (PROGRESS.md) --- CHANGES.md | 56 ++++++++++++++ PROGRESS.md | 55 ++++++++++++++ ROADMAP.md | 11 +++ docs/M2_MODULE_SCHNITT.md | 16 ++++ lib/core/app_state.dart | 13 +--- lib/core/matrix_client.dart | 4 +- lib/features/call/mini_call_widget.dart | 16 ++-- lib/features/call/voice_channel.dart | 18 ++--- .../call_signaling_service.dart | 67 +++++++++++++++++ .../call_signaling}/voip_manager.dart | 27 ++++++- lib/features/chat/chat_view.dart | 3 +- lib/features/chat/view/chat_header.dart | 6 +- .../rooms/panel/rooms_panel_core.dart | 4 +- .../rooms/panel/rooms_space_list.dart | 4 +- lib/features/rooms/rooms_panel.dart | 2 + .../voice_channel}/livekit_call_manager.dart | 31 +++++++- .../voice_channel/voice_channel_service.dart | 74 +++++++++++++++++++ lib/layout/app_shell.dart | 13 ++-- lib/widgets/settings/settings_voice.dart | 12 +-- lib/widgets/settings_modal.dart | 4 +- 20 files changed, 383 insertions(+), 53 deletions(-) create mode 100644 lib/features/call_signaling/call_signaling_service.dart rename lib/{core => features/call_signaling}/voip_manager.dart (96%) rename lib/{core => features/voice_channel}/livekit_call_manager.dart (97%) create mode 100644 lib/features/voice_channel/voice_channel_service.dart diff --git a/CHANGES.md b/CHANGES.md index 7db591d..3789843 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1333,3 +1333,59 @@ 2026-07-06 12:29 [Edit] C:\Users\nordm\pyramid - Kopie\docs\SQLCIPHER_MIGRATION.md 2026-07-06 12:30 [Edit] C:\Users\nordm\pyramid - Kopie\ROADMAP.md 2026-07-06 12:31 [Edit] C:\Users\nordm\pyramid - Kopie\PROGRESS.md +2026-07-06 12:33 [Write] C:\Users\nordm\pyramid - Kopie\lib\features\call_signaling\call_signaling_service.dart +2026-07-06 12:33 [Write] C:\Users\nordm\pyramid - Kopie\lib\features\voice_channel\voice_channel_service.dart +2026-07-06 12:34 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call_signaling\voip_manager.dart +2026-07-06 12:34 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call_signaling\voip_manager.dart +2026-07-06 12:34 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call_signaling\voip_manager.dart +2026-07-06 12:34 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call_signaling\voip_manager.dart +2026-07-06 12:34 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call_signaling\voip_manager.dart +2026-07-06 12:34 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call_signaling\voip_manager.dart +2026-07-06 12:34 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call_signaling\voip_manager.dart +2026-07-06 12:34 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call_signaling\voip_manager.dart +2026-07-06 12:34 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call_signaling\voip_manager.dart +2026-07-06 12:34 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call_signaling\voip_manager.dart +2026-07-06 12:35 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\voice_channel\livekit_call_manager.dart +2026-07-06 12:35 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\voice_channel\livekit_call_manager.dart +2026-07-06 12:35 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\voice_channel\livekit_call_manager.dart +2026-07-06 12:35 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\voice_channel\livekit_call_manager.dart +2026-07-06 12:35 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\voice_channel\livekit_call_manager.dart +2026-07-06 12:35 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\voice_channel\livekit_call_manager.dart +2026-07-06 12:35 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\voice_channel\livekit_call_manager.dart +2026-07-06 12:35 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\voice_channel\livekit_call_manager.dart +2026-07-06 12:35 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\voice_channel\livekit_call_manager.dart +2026-07-06 12:35 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\voice_channel\livekit_call_manager.dart +2026-07-06 12:35 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\voice_channel\livekit_call_manager.dart +2026-07-06 12:35 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\voice_channel\livekit_call_manager.dart +2026-07-06 12:35 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\voice_channel\livekit_call_manager.dart +2026-07-06 12:35 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\voice_channel\livekit_call_manager.dart +2026-07-06 12:35 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\voice_channel\livekit_call_manager.dart +2026-07-06 12:36 [Edit] C:\Users\nordm\pyramid - Kopie\lib\core\app_state.dart +2026-07-06 12:36 [Edit] C:\Users\nordm\pyramid - Kopie\lib\core\app_state.dart +2026-07-06 12:36 [Edit] C:\Users\nordm\pyramid - Kopie\lib\core\matrix_client.dart +2026-07-06 12:37 [Edit] C:\Users\nordm\pyramid - Kopie\lib\layout\app_shell.dart +2026-07-06 12:37 [Edit] C:\Users\nordm\pyramid - Kopie\lib\layout\app_shell.dart +2026-07-06 12:37 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\rooms\rooms_panel.dart +2026-07-06 12:37 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call\mini_call_widget.dart +2026-07-06 12:37 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call\mini_call_widget.dart +2026-07-06 12:37 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call\mini_call_widget.dart +2026-07-06 12:37 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call\mini_call_widget.dart +2026-07-06 12:37 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call\voice_channel.dart +2026-07-06 12:37 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call\voice_channel.dart +2026-07-06 12:37 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call\voice_channel.dart +2026-07-06 12:38 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call\voice_channel.dart +2026-07-06 12:38 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call\voice_channel.dart +2026-07-06 12:38 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call\voice_channel.dart +2026-07-06 12:38 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call\mini_call_widget.dart +2026-07-06 12:38 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call\mini_call_widget.dart +2026-07-06 12:38 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\chat\view\chat_header.dart +2026-07-06 12:38 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\chat\view\chat_header.dart +2026-07-06 12:38 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\chat\chat_view.dart +2026-07-06 12:39 [Edit] C:\Users\nordm\pyramid - Kopie\lib\widgets\settings_modal.dart +2026-07-06 12:39 [Edit] C:\Users\nordm\pyramid - Kopie\lib\widgets\settings\settings_voice.dart +2026-07-06 12:39 [Edit] C:\Users\nordm\pyramid - Kopie\lib\widgets\settings\settings_voice.dart +2026-07-06 12:39 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\chat\chat_view.dart +2026-07-06 12:39 [Edit] C:\Users\nordm\pyramid - Kopie\lib\features\call_signaling\call_signaling_service.dart +2026-07-06 12:41 [Edit] C:\Users\nordm\pyramid - Kopie\docs\M2_MODULE_SCHNITT.md +2026-07-06 12:41 [Edit] C:\Users\nordm\pyramid - Kopie\ROADMAP.md +2026-07-06 12:42 [Edit] C:\Users\nordm\pyramid - Kopie\PROGRESS.md diff --git a/PROGRESS.md b/PROGRESS.md index 82f8ec4..eeb4a5f 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -13,6 +13,61 @@ Schritt (und beim Abbruch mitten im Schritt den Zwischenstand). Format: --- +## 2026-07-06 – M2-Call-Pilot: Module call_signaling + voice_channel mit Fassaden umgesetzt (Entscheidung 2) + +**Erledigt:** Das erste M2-Umbau-Modul nach `docs/M2_MODULE_SCHNITT.md` +(Bernds Entscheidung 2: Call-Pilot zuerst, nicht settings_modal): + +- **Zwei neue Module mit schmaler öffentlicher Schnittstelle:** + `lib/features/call_signaling/` (`CallSignalingService` + + `callSignalingProvider`; Implementierung `voip_manager.dart` hierher + verschoben) und `lib/features/voice_channel/` (`VoiceChannelService` + + `voiceChannelProvider`; Implementierung `livekit_call_manager.dart` + hierher verschoben). Die zwei Call-Mechanismen bleiben wie im Doc + begründet GETRENNT (Matrix-1:1-VoIP vs. LiveKit-SFU-Kanäle). +- **Alle Konsumenten entkoppelt:** app_shell, Call-UI (`voice_channel.dart`, + `mini_call_widget.dart`), Rooms-Panel (Join/Leave, Call-Dock), + Chat-Header (Call-Start), Settings (`_VoiceSection` ist jetzt + ConsumerStatefulWidget und nutzt die Provider statt der Singletons) – + niemand importiert mehr die Implementierungsdateien, repo-weit verifiziert. + Einzige dokumentierte Ausnahme: `matrix_client.dart` als Kompositions- + Punkt, der `PyramidVoipManager(client)` konstruiert. Die alten Provider + `callStateProvider`/`voipStateProvider` in app_state.dart sind ersatzlos + durch die Modul-Provider ersetzt (reine Umbenennung + Typ = Interface). +- **Bewusste Abweichungen vom Schnitt-Entwurf** (Verhaltensgleichheit vor + Schönheit, heiliger Call-Pfad; im Doc-Status-Kopf dokumentiert): + ChangeNotifier statt Streams, Methodennamen wie im Bestand + (`startCall`/`hangUp` statt `join`/`leave`), `CallSession` und + LiveKit-`Room` bleiben in den Schnittstellen sichtbar (UI rendert direkt + Renderer/Tracks – für die WebRTC-Renderer sieht das Doc das selbst vor). + Kein einziger Handler-/Logik-Block wurde verändert, nur Klassen-Köpfe + (`extends` + `@override`), Imports, Provider-Namen und Typ-Annotationen. +- **Geprüft:** `flutter analyze` sauber (nur bekannter onUpdate-Hinweis), + alle 29 Tests grün, `flutter run -d windows`: App startet, Login/Sync/E2EE + laufen, sauberer Quit. (Commit-Hash siehe git log dieses Eintrags.) + +**UNGETESTET (interaktiv/Gerät – deshalb ROADMAP-Punkt „Call-Schicht +entwirren" nur annotiert, nicht abgehakt):** echter Voice-Channel-Join per +Klick auf Windows und echter 1:1-Call zwischen zwei Geräten. Der Umbau ist +bewusst rein mechanisch (Fassade davor, Logik unangetastet), aber die +CLAUDE.md-Regel „nach Refactoring Kernflows prüfen" ist für die Call-Flows +erst nach einem Klick-/Gerätetest voll erfüllt – steht im PC_TESTPLAN +(Abschnitt 8) bzw. braucht Bernd/Uta. + +**Offen/Nächster Schritt:** Härtetest nach `docs/PC_TESTPLAN.md` (so weit +ohne Klick-Interaktion möglich); die inneren M2-Aufräumarbeiten der +Call-Schicht (UI-State aus den Managern, `call_ui`-Modul, storage-Fassade +für die voice_*-Prefs) folgen als eigene Schritte. + +**Stolperfallen:** (1) Bei part-Dateien (settings, rooms_panel, chat_view) +hängen die Imports an der EINEN Owner-Datei – wer eine part-Datei umstellt, +muss die Imports beim Owner ändern und findet Verwender nur per Repo-grep +auf den Provider-Namen. (2) Ein `ChangeNotifierProvider` braucht +ein Interface, das ChangeNotifier ERWEITERT (abstract class X extends +ChangeNotifier), sonst passt der Typparameter nicht. + +--- + ## 2026-07-06 – SQLCipher-Migration UMGESETZT + auf Windows mit echter DB praxisgetestet (M1, Bernds Prio 1) **Erledigt:** Der M1-Punkt „Sichere Speicherung von Access-Token & Krypto-DB" diff --git a/ROADMAP.md b/ROADMAP.md index bdfcf3a..b83565e 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -173,6 +173,17 @@ lassen, ohne dass andere Module angefasst werden müssen. Bernds Go für den Call-Pilot (Frage in PROGRESS.md). - [ ] Call-Schicht entwirren: EINE klare Zuständigkeit pro Klasse (Signalisierung / LiveKit-Verbindung / UI-State sauber getrennt) + **Teilschritt erledigt (2026-07-06, M2-Pilot laut Entscheidung 2):** + Module `call_signaling` und `voice_channel` mit Fassaden-Interfaces + + Providern existieren (`lib/features/call_signaling/`, + `lib/features/voice_channel/`), alle Konsumenten gehen nur noch über + `callSignalingProvider`/`voiceChannelProvider` – kein UI-Import der + Implementierungen mehr (Details/Abweichungen: Status-Kopf in + `docs/M2_MODULE_SCHNITT.md`). App startet, 29 Tests grün, Sync/E2EE + laufen. **UNGETESTET:** echter 1:1-Call und Voice-Channel-Join per + Klick (braucht Gerät bzw. interaktiven Test) – deshalb nicht abgehakt; + außerdem stehen die inneren Aufräumarbeiten (UI-State aus den Managern + heraus, `call_ui`-Modul) noch aus. - [ ] Einheitliches State-Management (Riverpod konsequent, keine Misch-Patterns) - [x] Toten Code & Duplikate entfernen, Ordnerstruktur vereinheitlichen (PROGRESS.md 2026-07-03: `message_bubble.dart` tot entfernt, doppelte diff --git a/docs/M2_MODULE_SCHNITT.md b/docs/M2_MODULE_SCHNITT.md index 9fe7a9d..a79ca5b 100644 --- a/docs/M2_MODULE_SCHNITT.md +++ b/docs/M2_MODULE_SCHNITT.md @@ -1,5 +1,21 @@ # M2 – Modul-Schnitt (Entwurf) +> **STATUS 2026-07-06: Pilot (Schritt 1) UMGESETZT.** Die Module +> `call_signaling` (`lib/features/call_signaling/`: `CallSignalingService` +> + `callSignalingProvider` + Implementierung `voip_manager.dart`) und +> `voice_channel` (`lib/features/voice_channel/`: `VoiceChannelService` + +> `voiceChannelProvider` + Implementierung `livekit_call_manager.dart`) +> existieren; ALLE Konsumenten (Shell, Call-UI, Rooms-Panel, Chat-Header, +> Settings) gehen nur noch über die Provider/Interfaces, kein direkter +> Implementierungs-Import mehr (einzige dokumentierte Ausnahme: +> Kompositions-Punkt `matrix_client.dart`). Bewusste Abweichungen vom +> Entwurf unten (Verhaltensgleichheit vor Schönheit, heiliger Call-Pfad): +> ChangeNotifier statt Streams, Methodennamen wie im Bestand +> (`startCall`/`hangUp` statt `join`/`leave`), `CallSession`/LiveKit-`Room` +> bleiben in den Schnittstellen sichtbar, weil die UI direkt rendert. +> Echter Call-/Voice-Channel-Test steht aus (Gerät bzw. Klick-Test) – +> siehe PROGRESS.md 2026-07-06. Schritte 2–5 unten sind weiter offen. + Stand: 2026-07-03. Reine Schnittstellen-Planung, noch kein Umbau. Grundlage: Ist-Analyse in `PROGRESS.md` (Eintrag „M2: Ist-Analyse der Architektur geschrieben"). Leitprinzip (Bernd, `CLAUDE.md`): jedes Modul muss sich in diff --git a/lib/core/app_state.dart b/lib/core/app_state.dart index fbf3bd6..8bc64e6 100644 --- a/lib/core/app_state.dart +++ b/lib/core/app_state.dart @@ -3,8 +3,6 @@ import 'package:matrix/matrix.dart'; import 'package:pyramid/core/settings_prefs.dart'; import 'package:pyramid/core/theme.dart'; import 'package:pyramid/core/matrix_client.dart'; -import 'package:pyramid/core/livekit_call_manager.dart'; -import 'package:pyramid/core/voip_manager.dart'; import 'package:pyramid/features/rooms/rooms_provider.dart'; // ─── Theme state ────────────────────────────────────────────────────────────── @@ -64,14 +62,9 @@ enum ModalKind { none, settings, search } final activeModalProvider = StateProvider((ref) => ModalKind.none); // ─── Call state ─────────────────────────────────────────────────────────────── - -final callStateProvider = ChangeNotifierProvider((ref) { - return LiveKitCallManager.instance; -}); - -final voipStateProvider = ChangeNotifierProvider((ref) { - return PyramidVoipManager.instance; -}); +// Die Call-Provider leben seit dem M2-Call-Pilot in ihren Modulen: +// `callSignalingProvider` (features/call_signaling/call_signaling_service.dart) +// und `voiceChannelProvider` (features/voice_channel/voice_channel_service.dart). final incomingCallProvider = StateProvider((ref) => null); diff --git a/lib/core/matrix_client.dart b/lib/core/matrix_client.dart index cc7e8c0..ee1ae87 100644 --- a/lib/core/matrix_client.dart +++ b/lib/core/matrix_client.dart @@ -9,7 +9,9 @@ import 'package:matrix/matrix.dart'; import 'package:pyramid/core/app_database.dart'; import 'package:pyramid/core/auth_log.dart'; import 'package:pyramid/core/soft_logout_guard.dart'; -import 'package:pyramid/core/voip_manager.dart'; +// Kompositions-Punkt: Einzige Stelle außerhalb des call_signaling-Moduls, +// die die Implementierung (statt der Fassade) kennen darf – sie erzeugt sie. +import 'package:pyramid/features/call_signaling/voip_manager.dart'; final matrixClientProvider = FutureProvider((ref) async { // Factory-Wahl, Verschlüsselung (SQLCipher), Migration und PRAGMAs stecken diff --git a/lib/features/call/mini_call_widget.dart b/lib/features/call/mini_call_widget.dart index 4aa872f..7609192 100644 --- a/lib/features/call/mini_call_widget.dart +++ b/lib/features/call/mini_call_widget.dart @@ -3,14 +3,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:pyramid/core/app_state.dart'; import 'package:pyramid/core/theme.dart'; -import 'package:pyramid/core/livekit_call_manager.dart'; -import 'package:pyramid/core/voip_manager.dart'; +import 'package:pyramid/features/call_signaling/call_signaling_service.dart'; +import 'package:pyramid/features/voice_channel/voice_channel_service.dart'; import 'package:pyramid/widgets/screen_share_picker.dart'; import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc; import 'package:livekit_client/livekit_client.dart'; class MiniCallWidget extends ConsumerStatefulWidget { - final dynamic call; // Can be LiveKitCallManager or PyramidVoipManager + final dynamic call; // CallSignalingService (1:1) oder VoiceChannelService const MiniCallWidget({super.key, required this.call}); @override @@ -45,7 +45,7 @@ class _MiniCallWidgetState extends ConsumerState { @override Widget build(BuildContext context) { final pt = PyramidTheme.of(context); - final isVoip = widget.call is PyramidVoipManager; + final isVoip = widget.call is CallSignalingService; final call = widget.call; final String displayName = isVoip @@ -95,7 +95,7 @@ class _MiniCallWidgetState extends ConsumerState { isStreaming ? call.localRenderer : call.remoteRenderer, objectFit: rtc.RTCVideoViewObjectFit.RTCVideoViewObjectFitCover, ) - : _LiveKitPreview(manager: call as LiveKitCallManager), + : _LiveKitPreview(manager: call as VoiceChannelService), ), // Top bar of preview @@ -259,9 +259,9 @@ class _MiniCallWidgetState extends ConsumerState { pt: pt, onTap: () async { if (isVoip) { - (call as PyramidVoipManager).toggleScreenSharing(context); + (call as CallSignalingService).toggleScreenSharing(context); } else { - final lk = call as LiveKitCallManager; + final lk = call as VoiceChannelService; if (lk.isScreenSharing) { await lk.stopScreenShare(); } else { @@ -351,7 +351,7 @@ class _ControlBtnState extends State<_ControlBtn> { } class _LiveKitPreview extends StatelessWidget { - final LiveKitCallManager manager; + final VoiceChannelService manager; const _LiveKitPreview({required this.manager}); @override diff --git a/lib/features/call/voice_channel.dart b/lib/features/call/voice_channel.dart index 9cce6cc..f7bd428 100644 --- a/lib/features/call/voice_channel.dart +++ b/lib/features/call/voice_channel.dart @@ -6,8 +6,8 @@ import 'package:collection/collection.dart'; import 'package:pyramid/core/app_state.dart'; import 'package:pyramid/widgets/screen_share_picker.dart'; import 'package:pyramid/core/theme.dart'; -import 'package:pyramid/core/livekit_call_manager.dart'; -import 'package:pyramid/core/voip_manager.dart'; +import 'package:pyramid/features/call_signaling/call_signaling_service.dart'; +import 'package:pyramid/features/voice_channel/voice_channel_service.dart'; import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc; import 'package:pyramid/widgets/hover_region.dart'; import 'package:pyramid/widgets/pyramid_loader.dart'; @@ -45,8 +45,8 @@ class VoiceChannelView extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final pt = PyramidTheme.of(context); - final call = ref.watch(callStateProvider); - final voip = ref.watch(voipStateProvider); + final call = ref.watch(voiceChannelProvider); + final voip = ref.watch(callSignalingProvider); if (voip.currentCall != null && voip.currentCall!.state != CallState.kEnded) { return _UnifiedCallView( @@ -101,7 +101,7 @@ class VoiceChannelView extends ConsumerWidget { ); } - List<_ParticipantData> _getLiveKitParticipants(LiveKitCallManager call) { + List<_ParticipantData> _getLiveKitParticipants(VoiceChannelService call) { final room = call.room; if (room == null) return []; final all = [ @@ -142,7 +142,7 @@ class VoiceChannelView extends ConsumerWidget { }).toList(); } - List<_ParticipantData> _getVoipParticipants(PyramidVoipManager voip) { + List<_ParticipantData> _getVoipParticipants(CallSignalingService voip) { final participants = <_ParticipantData>[]; double ar(rtc.RTCVideoRenderer r) { @@ -997,7 +997,7 @@ class _FullscreenVideoPage extends StatelessWidget { } } -void _handleLiveKitScreenShare(BuildContext context, LiveKitCallManager call) async { +void _handleLiveKitScreenShare(BuildContext context, VoiceChannelService call) async { if (call.isScreenSharing) { await call.stopScreenShare(); } else { @@ -1006,7 +1006,7 @@ void _handleLiveKitScreenShare(BuildContext context, LiveKitCallManager call) as } } -void _showLiveKitQualityMenu(BuildContext context, LiveKitCallManager call, Offset pos) { +void _showLiveKitQualityMenu(BuildContext context, VoiceChannelService call, Offset pos) { final pt = PyramidTheme.of(context); showMenu( context: context, @@ -1025,7 +1025,7 @@ void _showLiveKitQualityMenu(BuildContext context, LiveKitCallManager call, Offs ); } -void _showVoipQualityMenu(BuildContext context, PyramidVoipManager voip, Offset pos) { +void _showVoipQualityMenu(BuildContext context, CallSignalingService voip, Offset pos) { final pt = PyramidTheme.of(context); showMenu( context: context, diff --git a/lib/features/call_signaling/call_signaling_service.dart b/lib/features/call_signaling/call_signaling_service.dart new file mode 100644 index 0000000..3d869a7 --- /dev/null +++ b/lib/features/call_signaling/call_signaling_service.dart @@ -0,0 +1,67 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc; +import 'package:matrix/matrix.dart'; +import 'package:pyramid/features/call_signaling/voip_manager.dart'; + +/// Öffentliche Schnittstelle des Moduls `call_signaling` – natives +/// Matrix-1:1-VoIP (`m.call.*`-Events, direkte WebRTC-Peer-Connection, +/// kein SFU). Schnitt nach `docs/M2_MODULE_SCHNITT.md`. +/// +/// Andere Module (UI, Settings, Shell) konsumieren AUSSCHLIESSLICH diese +/// Schnittstelle über [callSignalingProvider] – nie `voip_manager.dart` +/// direkt. Einzige Ausnahme ist der Kompositions-Punkt in +/// `matrix_client.dart`, der die Implementierung konstruiert. +/// +/// Bewusste Abweichungen vom Entwurf im Doc (Verhaltensgleichheit vor +/// Schönheit, heiliger Call-Pfad): +/// * ChangeNotifier statt Streams – die gesamte UI hängt heute an +/// `ref.watch(...)` mit notifyListeners; ein Stream-Umbau wäre eine +/// Verhaltensänderung ohne Not. +/// * [currentCall] bleibt als matrix-SDK-`CallSession` sichtbar (die UI +/// braucht state/room/answer/reject), ebenso die WebRTC-Renderer – +/// das Doc sieht Letzteres ausdrücklich vor. +abstract class CallSignalingService extends ChangeNotifier { + /// Der laufende 1:1-Call (null = keiner). UI liest state/room und + /// beantwortet eingehende Calls über [safeAction] + answer()/reject(). + CallSession? get currentCall; + + rtc.RTCVideoRenderer get localRenderer; + rtc.RTCVideoRenderer get remoteRenderer; + + bool get isMicMuted; + bool get isCameraMuted; + bool get isScreensharing; + bool get isRemoteScreensharing; + bool get isDeafened; + + /// Sende-Qualität ('sd' | 'hd' | 'fhd' | '4k'). + String get currentQualityKey; + + Future startCall(String roomId, {bool video}); + Future hangup(); + Future toggleMic(); + Future toggleCamera(); + Future toggleDeafen(); + + /// Auf Windows öffnet ein [context] den Quellen-Picker fürs Screensharing. + Future toggleScreenSharing([BuildContext? context]); + + Future changeQuality(String key); + + /// Wendet die in den Einstellungen gewählte Ausgabelautstärke auf den + /// Remote-Stream an. Ohne [value] wird der Pref-Wert gelesen. + Future applyOutputVolume([double? value]); + + /// Führt eine Call-Aktion (answer/reject/…) fehlertolerant aus und stößt + /// danach die interne Zustands-/Renderer-Aktualisierung an. + Future safeAction(Future Function() action); +} + +/// Einziger Zugriffsweg der UI auf das 1:1-VoIP-Modul. Die Instanz wird beim +/// Client-Aufbau in `matrix_client.dart` erzeugt (existiert also, sobald die +/// eingeloggte Shell steht). +final callSignalingProvider = + ChangeNotifierProvider((ref) { + return PyramidVoipManager.instance; +}); diff --git a/lib/core/voip_manager.dart b/lib/features/call_signaling/voip_manager.dart similarity index 96% rename from lib/core/voip_manager.dart rename to lib/features/call_signaling/voip_manager.dart index b6047c3..22b888d 100644 --- a/lib/core/voip_manager.dart +++ b/lib/features/call_signaling/voip_manager.dart @@ -6,17 +6,25 @@ import 'package:matrix/matrix.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:webrtc_interface/webrtc_interface.dart'; import 'package:pyramid/core/ice_servers.dart'; +import 'package:pyramid/features/call_signaling/call_signaling_service.dart'; import 'package:pyramid/widgets/screen_share_picker.dart'; -class PyramidVoipManager extends ChangeNotifier implements WebRTCDelegate { +/// Implementierung von [CallSignalingService] (Modul `call_signaling`). +/// Wird ausschließlich vom Kompositions-Punkt (`matrix_client.dart`) +/// konstruiert; alle anderen gehen über `callSignalingProvider`. +class PyramidVoipManager extends CallSignalingService + implements WebRTCDelegate { static PyramidVoipManager? _instance; static PyramidVoipManager get instance => _instance!; final Client client; late final VoIP voip; + @override CallSession? currentCall; - + + @override final rtc.RTCVideoRenderer localRenderer = rtc.RTCVideoRenderer(); + @override final rtc.RTCVideoRenderer remoteRenderer = rtc.RTCVideoRenderer(); bool _renderersInitialized = false; @@ -29,11 +37,17 @@ class PyramidVoipManager extends ChangeNotifier implements WebRTCDelegate { String? _selectedSourceId; String _currentQualityKey = 'hd'; + @override bool get isMicMuted => _isMicMuted; + @override bool get isCameraMuted => _isCameraMuted; + @override bool get isScreensharing => _isScreensharing; + @override bool get isRemoteScreensharing => _isRemoteScreensharing; + @override bool get isDeafened => _isDeafened; + @override String get currentQualityKey => _currentQualityKey; PyramidVoipManager(this.client) { @@ -253,6 +267,7 @@ class PyramidVoipManager extends ChangeNotifier implements WebRTCDelegate { /// Wendet die in den Einstellungen gewählte Ausgabelautstärke auf den /// Remote-Audio-Stream an. Ohne [value] wird der Pref-Wert gelesen. + @override Future applyOutputVolume([double? value]) async { var v = value; if (v == null) { @@ -295,6 +310,7 @@ class PyramidVoipManager extends ChangeNotifier implements WebRTCDelegate { @override Future registerListeners(CallSession session) async {} + @override Future safeAction(Future Function() action) async { try { await action(); @@ -306,6 +322,7 @@ class PyramidVoipManager extends ChangeNotifier implements WebRTCDelegate { _updateRenderers(); } + @override Future toggleMic() async { if (currentCall == null) return; _isMicMuted = !_isMicMuted; @@ -318,6 +335,7 @@ class PyramidVoipManager extends ChangeNotifier implements WebRTCDelegate { } } + @override Future toggleCamera() async { if (currentCall == null) return; _isCameraMuted = !_isCameraMuted; @@ -337,6 +355,7 @@ class PyramidVoipManager extends ChangeNotifier implements WebRTCDelegate { await _updateRenderers(); } + @override Future toggleScreenSharing([BuildContext? context]) async { if (currentCall == null) return; final call = currentCall!; @@ -359,11 +378,13 @@ class PyramidVoipManager extends ChangeNotifier implements WebRTCDelegate { }); } + @override Future toggleDeafen() async { _isDeafened = !_isDeafened; notifyListeners(); } + @override Future hangup() async { if (currentCall == null) return; await safeAction(() async { @@ -372,6 +393,7 @@ class PyramidVoipManager extends ChangeNotifier implements WebRTCDelegate { }); } + @override Future startCall(String roomId, {bool video = false}) async { final room = client.getRoomById(roomId); if (room == null) return; @@ -394,6 +416,7 @@ class PyramidVoipManager extends ChangeNotifier implements WebRTCDelegate { notifyListeners(); } + @override Future changeQuality(String key) async { _currentQualityKey = key; if (currentCall == null) { diff --git a/lib/features/chat/chat_view.dart b/lib/features/chat/chat_view.dart index 3ff6f01..0d2cc04 100644 --- a/lib/features/chat/chat_view.dart +++ b/lib/features/chat/chat_view.dart @@ -22,7 +22,8 @@ import 'package:pyramid/widgets/hover_region.dart'; import 'package:pyramid/widgets/mxc_image.dart'; import 'package:pyramid/core/notification_service.dart'; -import 'package:pyramid/core/voip_manager.dart'; +import 'package:pyramid/features/call_signaling/call_signaling_service.dart'; +import 'package:pyramid/features/voice_channel/voice_channel_service.dart'; part 'view/chat_view_core.dart'; diff --git a/lib/features/chat/view/chat_header.dart b/lib/features/chat/view/chat_header.dart index 150ade7..d94d106 100644 --- a/lib/features/chat/view/chat_header.dart +++ b/lib/features/chat/view/chat_header.dart @@ -167,13 +167,13 @@ class _ChatHeader extends ConsumerWidget { if (room.isDirectChat) { // Use native Matrix VoIP for 1:1 DMs ref.read(viewModeProvider.notifier).state = ViewMode.voice; - await PyramidVoipManager.instance.startCall(room.id, video: cam); + await ref.read(callSignalingProvider).startCall(room.id, video: cam); return; } // Use LiveKit for group voice rooms ref.read(viewModeProvider.notifier).state = ViewMode.voice; - await ref.read(callStateProvider).startCall( + await ref.read(voiceChannelProvider).startCall( roomName: roomId, roomDisplayName: room.getLocalizedDisplayname(), identity: 'user_${DateTime.now().millisecondsSinceEpoch}', @@ -576,7 +576,7 @@ class _DmExpandingHeaderState extends ConsumerState<_DmExpandingHeader> { void _startCall({required bool cam}) async { ref.read(viewModeProvider.notifier).state = ViewMode.voice; - await PyramidVoipManager.instance.startCall(widget.room.id, video: cam); + await ref.read(callSignalingProvider).startCall(widget.room.id, video: cam); } } diff --git a/lib/features/rooms/panel/rooms_panel_core.dart b/lib/features/rooms/panel/rooms_panel_core.dart index a333be3..6fdd806 100644 --- a/lib/features/rooms/panel/rooms_panel_core.dart +++ b/lib/features/rooms/panel/rooms_panel_core.dart @@ -39,8 +39,8 @@ class _RoomsPanelState extends ConsumerState { final pt = PyramidTheme.of(context); final roomsAsync = ref.watch(roomListProvider); final activeSpaceId = ref.watch(activeSpaceIdProvider); - final call = ref.watch(callStateProvider); - final voip = ref.watch(voipStateProvider); + final call = ref.watch(voiceChannelProvider); + final voip = ref.watch(callSignalingProvider); final isRealSpace = activeSpaceId != null && activeSpaceId != 'dms' && diff --git a/lib/features/rooms/panel/rooms_space_list.dart b/lib/features/rooms/panel/rooms_space_list.dart index 102cd72..9942244 100644 --- a/lib/features/rooms/panel/rooms_space_list.dart +++ b/lib/features/rooms/panel/rooms_space_list.dart @@ -95,7 +95,7 @@ class _SpaceRoomsListState extends ConsumerState<_SpaceRoomsList> { if (jr.isVoice) { // LiveKit only needs the room id + a display name, so connect even if the // matrix room object hasn't synced locally yet. - final call = ref.read(callStateProvider); + final call = ref.read(voiceChannelProvider); if (call.isActive) await call.hangUp(); ref.read(activeVoiceRoomIdProvider.notifier).state = jr.roomId; await call.startCall( @@ -199,7 +199,7 @@ class _SpaceRoomsListState extends ConsumerState<_SpaceRoomsList> { } void joinVoiceChannel(Room room) async { - final call = ref.read(callStateProvider); + final call = ref.read(voiceChannelProvider); final activeVoice = ref.read(activeVoiceRoomIdProvider); // Toggle: leave if already in this channel if (call.isVoiceChannel && activeVoice == room.id) { diff --git a/lib/features/rooms/rooms_panel.dart b/lib/features/rooms/rooms_panel.dart index ae9f69f..fa688fb 100644 --- a/lib/features/rooms/rooms_panel.dart +++ b/lib/features/rooms/rooms_panel.dart @@ -11,6 +11,8 @@ import 'package:pyramid/core/matrix_client.dart'; import 'package:pyramid/core/theme.dart'; import 'package:pyramid/features/rooms/rooms_provider.dart'; import 'package:pyramid/features/call/mini_call_widget.dart'; +import 'package:pyramid/features/call_signaling/call_signaling_service.dart'; +import 'package:pyramid/features/voice_channel/voice_channel_service.dart'; import 'package:pyramid/features/rooms/user_panel.dart'; import 'package:pyramid/features/spaces/space_admin_dialog.dart'; import 'package:pyramid/widgets/create_join_dialog.dart'; diff --git a/lib/core/livekit_call_manager.dart b/lib/features/voice_channel/livekit_call_manager.dart similarity index 97% rename from lib/core/livekit_call_manager.dart rename to lib/features/voice_channel/livekit_call_manager.dart index 025c018..60f2c6b 100644 --- a/lib/core/livekit_call_manager.dart +++ b/lib/features/voice_channel/livekit_call_manager.dart @@ -6,11 +6,14 @@ import 'package:livekit_client/livekit_client.dart'; import 'package:matrix/matrix.dart' hide Room; import 'package:pyramid/core/ice_servers.dart'; import 'package:pyramid/core/livekit_token.dart'; +import 'package:pyramid/features/voice_channel/voice_channel_service.dart'; import 'package:shared_preferences/shared_preferences.dart'; const _kVoicePresenceType = 'io.pyramid.voice.presence'; -class LiveKitCallManager with ChangeNotifier { +/// Implementierung von [VoiceChannelService] (Modul `voice_channel`). +/// Andere Module gehen ausschließlich über `voiceChannelProvider`. +class LiveKitCallManager extends VoiceChannelService { static final LiveKitCallManager instance = LiveKitCallManager._(); LiveKitCallManager._(); @@ -32,17 +35,24 @@ class LiveKitCallManager with ChangeNotifier { Room? _room; EventsListener? _roomEventListener; String _displayName = ''; + @override bool isConnecting = false; + @override String? error; + @override bool isMuted = false; + @override bool isCameraOff = false; + @override bool isScreenSharing = false; String _localQualityKey = 'hd'; String _subscribeQualityKey = 'auto'; // 'auto' | 'low' | 'medium' | 'high' String? _currentScreenShareSourceId; LocalVideoTrack? _screenShareTrack; + @override bool isDeafened = false; bool _mutedBeforeDeafen = false; + @override bool isVoiceChannel = false; String? _matrixRoomId; Client? _matrixClient; @@ -50,12 +60,18 @@ class LiveKitCallManager with ChangeNotifier { Timer? _statsTimer; + @override bool get isActive => _room != null || isConnecting; + @override Room? get room => _room; + @override String? get currentRoomId => _matrixRoomId; + @override String get displayName => _displayName; bool get noiseSuppression => false; + @override String get currentQualityKey => _localQualityKey; + @override String get subscribeQualityKey => _subscribeQualityKey; VideoParameters get _currentQuality => @@ -122,6 +138,7 @@ class LiveKitCallManager with ChangeNotifier { } } + @override Future startCall({ required String roomName, required String roomDisplayName, @@ -307,6 +324,7 @@ class LiveKitCallManager with ChangeNotifier { double _outputVolume = 1.0; /// Live-Anwendung des Lautstärke-Sliders auf alle Remote-Audio-Tracks. + @override Future setOutputVolume(double v) async { _outputVolume = v; final room = _room; @@ -323,6 +341,7 @@ class LiveKitCallManager with ChangeNotifier { } } + @override Future setSubscribeQuality(String key) async { debugPrint('[LiveKit] Viewer changing subscribe quality to: $key'); _subscribeQualityKey = key; @@ -340,12 +359,14 @@ class LiveKitCallManager with ChangeNotifier { notifyListeners(); } + @override Future toggleMute() async { isMuted = !isMuted; notifyListeners(); await _room?.localParticipant?.setMicrophoneEnabled(!isMuted); } + @override Future toggleDeafen() async { if (!isDeafened) { _mutedBeforeDeafen = isMuted; @@ -377,6 +398,7 @@ class LiveKitCallManager with ChangeNotifier { } } + @override Future toggleCamera() async { if (isCameraOff) { try { @@ -399,6 +421,7 @@ class LiveKitCallManager with ChangeNotifier { notifyListeners(); } + @override Future toggleScreenShare() async { if (isScreenSharing) { await stopScreenShare(); @@ -426,6 +449,7 @@ class LiveKitCallManager with ChangeNotifier { const VideoEncoding(maxBitrate: 1_500_000, maxFramerate: 15); } + @override Future startScreenShare(String sourceId) async { if (isScreenSharing) return; try { @@ -480,6 +504,7 @@ class LiveKitCallManager with ChangeNotifier { } } + @override Future stopScreenShare() async { final local = _room?.localParticipant; if (local != null) { @@ -501,6 +526,7 @@ class LiveKitCallManager with ChangeNotifier { notifyListeners(); } + @override Future hangUp() async { _stopStatsLogging(); _presenceHeartbeat?.cancel(); @@ -529,6 +555,7 @@ class LiveKitCallManager with ChangeNotifier { notifyListeners(); } + @override Future changeQuality(String qualityKey) async { debugPrint('[LiveKit] Streamer changing publish quality to: $qualityKey'); _localQualityKey = qualityKey; @@ -562,6 +589,7 @@ class LiveKitCallManager with ChangeNotifier { notifyListeners(); } + @override Future changeAudioInput(String deviceId) async { final participant = _room?.localParticipant; if (participant == null) return; @@ -575,6 +603,7 @@ class LiveKitCallManager with ChangeNotifier { } catch (e) { debugPrint('[Audio] error: $e'); } } + @override void setRemoteVolume(double volume) { if (_room == null) return; final mute = volume < 0.05; diff --git a/lib/features/voice_channel/voice_channel_service.dart b/lib/features/voice_channel/voice_channel_service.dart new file mode 100644 index 0000000..50da51d --- /dev/null +++ b/lib/features/voice_channel/voice_channel_service.dart @@ -0,0 +1,74 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:livekit_client/livekit_client.dart' as lk; +import 'package:matrix/matrix.dart' hide Room; +import 'package:pyramid/features/voice_channel/livekit_call_manager.dart'; + +/// Öffentliche Schnittstelle des Moduls `voice_channel` – LiveKit-SFU-basierte, +/// dauerhafte Voice-Channels (Discord-artig: Presence über Matrix-State-Events, +/// Screensharing, Mehrpersonen). Schnitt nach `docs/M2_MODULE_SCHNITT.md`; +/// bewusst GETRENNT vom 1:1-Modul `call_signaling`, siehe dort. +/// +/// Andere Module konsumieren ausschließlich diese Schnittstelle über +/// [voiceChannelProvider] – nie `livekit_call_manager.dart` direkt. +/// +/// Bewusste Abweichungen vom Entwurf im Doc (Verhaltensgleichheit vor +/// Schönheit): ChangeNotifier statt `stateStream`, Methodennamen wie im +/// Bestand (`startCall`/`hangUp` statt `join`/`leave`), und [room] bleibt +/// als LiveKit-Typ sichtbar, weil die Teilnehmer-Kacheln der UI direkt +/// LiveKit-Tracks rendern (analog zu den WebRTC-Renderern im Signaling). +abstract class VoiceChannelService extends ChangeNotifier { + bool get isActive; + bool get isConnecting; + String? get error; + bool get isMuted; + bool get isCameraOff; + bool get isScreenSharing; + bool get isDeafened; + bool get isVoiceChannel; + + /// Der verbundene LiveKit-Raum (null = nicht verbunden). + lk.Room? get room; + + /// Matrix-Raum-ID des aktiven Voice-Channels. + String? get currentRoomId; + String get displayName; + + /// Sende-Qualität ('sd' | 'hd' | 'fhd' | '4k'). + String get currentQualityKey; + + /// Empfangs-Qualität ('auto' | 'low' | 'medium' | 'high'). + String get subscribeQualityKey; + + Future startCall({ + required String roomName, + required String roomDisplayName, + required String identity, + bool audioOnly, + bool voiceChannel, + Client? matrixClient, + String? matrixRoomId, + }); + Future hangUp(); + + Future toggleMute(); + Future toggleDeafen(); + Future toggleCamera(); + Future toggleScreenShare(); + Future startScreenShare(String sourceId); + Future stopScreenShare(); + + Future changeQuality(String qualityKey); + Future setSubscribeQuality(String key); + + /// Live-Anwendung des Lautstärke-Sliders auf alle Remote-Audio-Tracks. + Future setOutputVolume(double v); + Future changeAudioInput(String deviceId); + void setRemoteVolume(double volume); +} + +/// Einziger Zugriffsweg der UI auf das Voice-Channel-Modul. +final voiceChannelProvider = + ChangeNotifierProvider((ref) { + return LiveKitCallManager.instance; +}); diff --git a/lib/layout/app_shell.dart b/lib/layout/app_shell.dart index 538ddd0..b8256f6 100644 --- a/lib/layout/app_shell.dart +++ b/lib/layout/app_shell.dart @@ -9,9 +9,10 @@ import 'package:pretty_qr_code/pretty_qr_code.dart'; import 'package:pyramid/core/app_state.dart'; import 'package:pyramid/core/matrix_client.dart'; import 'package:pyramid/core/theme.dart'; -import 'package:pyramid/core/voip_manager.dart'; import 'package:pyramid/features/auth/bootstrap_dialog.dart'; import 'package:pyramid/features/call/voice_channel.dart'; +import 'package:pyramid/features/call_signaling/call_signaling_service.dart'; +import 'package:pyramid/features/voice_channel/voice_channel_service.dart'; import 'package:pyramid/features/chat/chat_view.dart'; import 'package:pyramid/features/members/members_panel.dart'; import 'package:pyramid/features/rooms/rooms_panel.dart'; @@ -160,7 +161,7 @@ class _AppShellState extends ConsumerState // notifyListeners (u.a. Render-Timer im Sekundentakt); ohne Flankenerkennung // würde der Nutzer permanent zurück in die Call-UI gezwungen und könnte // sie nie minimieren. - ref.listen(voipStateProvider, (prev, next) { + ref.listen(callSignalingProvider, (prev, next) { final wasConnected = _wasVoipConnected; final isConnected = next.currentCall != null && next.currentCall!.state == CallState.kConnected; @@ -172,7 +173,7 @@ class _AppShellState extends ConsumerState } }); - ref.listen(callStateProvider, (prev, next) { + ref.listen(voiceChannelProvider, (prev, next) { // Only auto-switch to voice view for video/audio calls, NOT voice channels // (voice channels should stay in background with a mini bar) final wasActive = _wasLiveKitActive; @@ -200,7 +201,7 @@ class _AppShellState extends ConsumerState final activeModal = ref.watch(activeModalProvider); final membersOpen = ref.watch(membersPanelOpenProvider); final innerPt = PyramidTheme.of(context); - final voip = ref.watch(voipStateProvider); + final voip = ref.watch(callSignalingProvider); final size = MediaQuery.sizeOf(context); final isMobile = size.width < 900 || size.height < 500; @@ -332,7 +333,7 @@ class _MobileShell extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final voip = ref.watch(voipStateProvider); + final voip = ref.watch(callSignalingProvider); Widget body; if (viewMode == ViewMode.voice) { @@ -456,7 +457,7 @@ class _NotificationBlockedBanner extends ConsumerWidget { class _IncomingCallOverlay extends ConsumerWidget { final CallSession call; - final PyramidVoipManager voip; + final CallSignalingService voip; final PyramidTheme pt; const _IncomingCallOverlay({ diff --git a/lib/widgets/settings/settings_voice.dart b/lib/widgets/settings/settings_voice.dart index 98b1d2f..845258a 100644 --- a/lib/widgets/settings/settings_voice.dart +++ b/lib/widgets/settings/settings_voice.dart @@ -1,14 +1,14 @@ part of '../settings_modal.dart'; -class _VoiceSection extends StatefulWidget { +class _VoiceSection extends ConsumerStatefulWidget { final PyramidTheme pt; const _VoiceSection({required this.pt}); @override - State<_VoiceSection> createState() => _VoiceSectionState(); + ConsumerState<_VoiceSection> createState() => _VoiceSectionState(); } -class _VoiceSectionState extends State<_VoiceSection> { +class _VoiceSectionState extends ConsumerState<_VoiceSection> { List _audioInputs = []; List _audioOutputs = []; List _videoInputs = []; @@ -89,10 +89,10 @@ class _VoiceSectionState extends State<_VoiceSection> { setState(() => _outputVolume = v); final prefs = await SharedPreferences.getInstance(); await prefs.setDouble(_kOutputVolume, v); - // Live auf laufende Calls anwenden. - LiveKitCallManager.instance.setOutputVolume(v).catchError((_) {}); + // Live auf laufende Calls anwenden (über die Modul-Fassaden). + ref.read(voiceChannelProvider).setOutputVolume(v).catchError((_) {}); try { - PyramidVoipManager.instance.applyOutputVolume(v).catchError((_) {}); + ref.read(callSignalingProvider).applyOutputVolume(v).catchError((_) {}); } catch (_) {} // Instanz existiert evtl. noch nicht } diff --git a/lib/widgets/settings_modal.dart b/lib/widgets/settings_modal.dart index 756abfb..183c713 100644 --- a/lib/widgets/settings_modal.dart +++ b/lib/widgets/settings_modal.dart @@ -24,9 +24,9 @@ import 'package:pyramid/core/app_state.dart'; import 'package:pyramid/core/auth_log.dart'; import 'package:pyramid/core/e2ee_diagnostics.dart'; import 'package:pyramid/features/auth/bootstrap_dialog.dart' show isKeyBackupMissing; -import 'package:pyramid/core/livekit_call_manager.dart'; +import 'package:pyramid/features/call_signaling/call_signaling_service.dart'; +import 'package:pyramid/features/voice_channel/voice_channel_service.dart'; import 'package:pyramid/core/matrix_client.dart'; -import 'package:pyramid/core/voip_manager.dart'; import 'package:pyramid/core/settings_prefs.dart'; import 'package:pyramid/core/theme.dart'; import 'package:pyramid/core/update_checker.dart';