diff --git a/PROGRESS.md b/PROGRESS.md index 69c4193..e318e18 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -13,6 +13,62 @@ Schritt (und beim Abbruch mitten im Schritt den Zwischenstand). Format: --- +## 2026-07-03 – M2: Modul-Schnitt definiert (docs/M2_MODULE_SCHNITT.md) + +**Erledigt:** Ausgehend von der Ist-Analyse (Eintrag darunter) die konkreten +Modul-Grenzen + öffentliche Schnittstellen entworfen, dokumentiert in +`docs/M2_MODULE_SCHNITT.md`. Reine Planung, kein Code geändert. Wichtigste +Erkenntnis beim genaueren Lesen von `voip_manager.dart` und +`livekit_call_manager.dart`: „Calls" ist architektonisch **kein einzelnes +Feature**, sondern zwei unabhängige Mechanismen, die getrennt bleiben sollten: +- `PyramidVoipManager` = natives Matrix-1:1-VoIP (`m.call.*`, direkte + WebRTC-Peer-Connection, kein SFU) +- `LiveKitCallManager` = SFU-basierte Voice-Channels (LiveKit-Raum, Presence + via Matrix-State-Event, Screensharing, Mehrpersonen) – das ist der + Discord-artige Kanal aus M3/M4 + +Modul-Liste im Dokument: `call_signaling`, `voice_channel`, `call_ui`, +`settings` (aufgesplittet in Sektionsdateien), `verification` (neu, aus +Settings herausgelöst – SAS/QR/Recovery-Key), `storage` (Ausbau der +bestehenden `settings_prefs.dart`-Abstraktion zur Pflicht), `auth` (schon +relativ sauber, kein akuter Bedarf), `push` (schon getrennt, eigener +`sqflite`-Zugriff in `background_push.dart` ist bewusst so, kein +Refactoring-Kandidat), `chat_timeline`/`rooms` (niedrigste Priorität, da +strukturell am wenigsten verwoben – Hauptproblem dort ist Dateigröße/Duplikate, +nicht Kopplung). Jeweils mit Dart-Interface-Skizze bzw. Datei-Zielliste. + +Empfohlene Umsetzungsreihenfolge: 1) Call-Pilot (`call_signaling` + +`voice_channel` + `call_ui`, deckt gleich zwei ROADMAP-Punkte ab), 2) +`settings_modal.dart` aufsplitten (geringes Risiko, reine Datei-Teilung), +3) `verification` herauslösen, 4) `storage`-Fassade schrittweise erzwingen, +5) `chat_timeline`/`rooms` zuletzt. + +**Offen/Nächster Schritt:** Wartet auf Bernds Entscheidung, ob der Call-Pilot +wie vorgeschlagen als Nächstes umgesetzt wird (siehe Frage unten). Erst danach +den ROADMAP-Punkt „Call-Schicht entwirren" wirklich anfassen – das ist die +erste Stelle mit echtem Verhaltensrisiko in M2 (CLAUDE.md: nach Refactoring- +Schritten immer App starten + Kernflows prüfen, hier zusätzlich brisant, weil +GUI-Test auf dem Pi nicht möglich ist). + +**Stolperfallen:** Der erste Ist-Analyse-Durchgang (Subagent) hatte einen +falschen Befund (`rooms_provider.dart`-Import in `app_state.dart` sei toter +Code) – beim genaueren Lesen für den Modul-Schnitt widerlegt +(`voiceParticipantsProvider` nutzt `roomListProvider` daraus). Bereits im +Ist-Analyse-Eintrag korrigiert. Lehre: Subagent-Ist-Analysen sind ein guter +Startpunkt, aber vor jedem darauf aufbauenden Schritt einzelne Befunde +gegenlesen, nicht blind übernehmen. + +**Fragen an Bernd:** +- Call-Pilot wie in `docs/M2_MODULE_SCHNITT.md` vorgeschlagen (zuerst + `call_signaling`/`voice_channel`/`call_ui` entwirren) als nächster + Umsetzungsschritt in Ordnung, oder lieber zuerst `settings_modal.dart` + aufsplitten (geringeres Risiko, aber deckt keinen ROADMAP-Punkt aus M2 + direkt ab)? Ohne GUI-Zugriff auf dem Pi kann der riskantere Call-Umbau + hier ohnehin nicht praktisch getestet werden – das spricht eher für + „auf dem PC anfangen", falls Zeitdruck besteht. + +--- + ## 2026-07-03 – M2: Ist-Analyse der Architektur geschrieben **Erledigt:** M1 hat aktuell keinen auf dem Pi bearbeitbaren Punkt mehr offen @@ -43,11 +99,13 @@ gefunden: zusammen mit der Call-Fassade angegangen werden, nicht isoliert. - **`core/` ist nicht rein generisch:** importiert Feature-Code direkt (`app.dart`/`router.dart` → `features/auth/*`, `matrix_client.dart` instanziiert - `PyramidVoipManager` direkt, `app_state.dart` importiert `rooms_provider.dart` - augenscheinlich ungenutzt – vermutlich Altlast). `app_state.dart` selbst ist ein - Sammelbecken für Theme-, Call-, Voice-, Share-Target- und Spaces-Provider aus - praktisch allen Features – zentraler Kopplungspunkt, der jedem Modulschnitt - im Weg steht. + `PyramidVoipManager` direkt). `app_state.dart` selbst ist ein Sammelbecken für + Theme-, Call-, Voice-, Share-Target- und Spaces-Provider aus praktisch allen + Features – zentraler Kopplungspunkt, der jedem Modulschnitt im Weg steht. + (Korrektur beim genaueren Hinsehen für den Modul-Schnitt-Punkt: der + `rooms_provider.dart`-Import dort ist entgegen der ersten Vermutung KEIN + toter Altlast-Import – `voiceParticipantsProvider` nutzt `roomListProvider` + daraus. Zeigt: Subagent-Befunde vor dem Weiterbauen selbst nachprüfen.) - **Keine gemeinsame Storage-Schnittstelle:** `SharedPreferences.getInstance()` wird an 12 Stellen direkt aufgerufen, obwohl `core/settings_prefs.dart` bereits eine `StateNotifier`-Abstraktion (`BoolPref`, `StringSetPref`) anbietet – wird diff --git a/ROADMAP.md b/ROADMAP.md index e16cfc5..83efb0e 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -65,9 +65,13 @@ lassen, ohne dass andere Module angefasst werden müssen. aller Features, keine gemeinsame Storage-Schnittstelle (12 direkte `SharedPreferences`-Zugriffe), toter Code (`message_bubble.dart`) + Duplikate (`_formatTime`/`_formatDate` 3-fach). 8 Modul-Kandidaten dokumentiert. -- [ ] Modul-Schnitt definieren: Module (auth, chat, rooms, calls, streaming, push, storage, …) - mit je einer schmalen öffentlichen Schnittstelle; Abhängigkeiten nur über Interfaces, - keine Quer-Importe zwischen Feature-Modulen +- [x] Modul-Schnitt definiert (`docs/M2_MODULE_SCHNITT.md`, PROGRESS.md 2026-07-03): + „Calls" ist architektonisch zwei getrennte Module (`call_signaling` = + Matrix-1:1-VoIP, `voice_channel` = LiveKit-SFU-Kanäle) + `call_ui`; + außerdem `settings` (aufgesplittet), `verification` (neu, aus Settings + herausgelöst), `storage` (Ausbau `settings_prefs.dart`), `auth`, `push`, + `chat_timeline`/`rooms`. Umsetzungsreihenfolge vorgeschlagen, wartet auf + 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) - [ ] Einheitliches State-Management (Riverpod konsequent, keine Misch-Patterns) diff --git a/docs/M2_MODULE_SCHNITT.md b/docs/M2_MODULE_SCHNITT.md new file mode 100644 index 0000000..9fe7a9d --- /dev/null +++ b/docs/M2_MODULE_SCHNITT.md @@ -0,0 +1,165 @@ +# M2 – Modul-Schnitt (Entwurf) + +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 +einem Jahr komplett neu schreiben lassen, ohne andere Module anzufassen. +Andere Module dürfen daher nur die hier genannte öffentliche Schnittstelle +importieren, nie interne Dateien. + +Korrektur zur Ist-Analyse: der Import von `rooms_provider.dart` in +`app_state.dart` ist **kein** toter Altlast-Import, wie dort vermutet – +`voiceParticipantsProvider` (Zeile 160) nutzt `roomListProvider` daraus, um +bei jedem Sync neu zu bauen. Der Eintrag in PROGRESS.md wird beim nächsten +Durchgang korrigiert. + +## Wichtige Erkenntnis vor dem Schnitt: „Calls" ist eigentlich ZWEI Features + +Beim genaueren Lesen von `voip_manager.dart` und `livekit_call_manager.dart` +zeigt sich: das sind keine zwei Hälften derselben Sache, sondern zwei +unabhängige Call-Mechanismen, die zufällig beide „Call" heißen: + +- **`PyramidVoipManager`** – natives Matrix-VoIP (`m.call.*`-Events über das + matrix-SDK, direkte 1:1-WebRTC-Peer-Connection, kein SFU). Vermutlich für + spontane 1:1-Anrufe gedacht. +- **`LiveKitCallManager`** – SFU-basierte, dauerhafte Voice-Channels (LiveKit- + Server-Raum, Presence über Matrix-State-Events, Screensharing, Mehrpersonen). + Das ist der Discord-artige Voice-Channel aus M3/M4. + +Beide Module sollten **getrennt** bleiben, nicht zu einem „calls"-Modul +verschmolzen werden. Nur die UI (`call_ui`) darf beide kennen, falls sie +gemeinsam dargestellt werden müssen (z. B. „du bist in einem Voice-Channel, +jemand ruft dich per 1:1-VoIP an"). + +## Modul-Liste mit öffentlicher Schnittstelle (Entwurf) + +### 1. `call_signaling` (Matrix-1:1-VoIP) +**Ersetzt:** `core/voip_manager.dart` +**Öffentliche Schnittstelle (Ausschnitt):** +```dart +abstract class CallSignalingService { + Stream get currentCallStream; + bool get isMicMuted; + bool get isCameraMuted; + Future startCall(String roomId, {bool video}); + Future hangup(); + Future toggleMic(); + Future toggleCamera(); + Future toggleScreenSharing(BuildContext? context); + RTCVideoRenderer get localRenderer; // bleibt vorerst WebRTC-typisiert, + RTCVideoRenderer get remoteRenderer; // da UI direkt rendert +} +``` +Interna (ICE-Server-Merge, `MediaDevicesWrapper`, Geräte-Prefs aus +`SharedPreferences`) bleiben privat im Modul. Geräte-Prefs sollten künftig +über die `storage`-Fassade laufen statt direktem `SharedPreferences`-Zugriff +(aktuell Zeile 260, 447). + +### 2. `voice_channel` (LiveKit-SFU, Discord-artige Kanäle) +**Ersetzt:** `core/livekit_call_manager.dart` +**Öffentliche Schnittstelle (Ausschnitt):** +```dart +abstract class VoiceChannelService { + Stream get stateStream; // isActive, isMuted, participants… + Future join({required String roomName, required String matrixRoomId, ...}); + Future leave(); + Future toggleMute(); + Future toggleDeafen(); + Future toggleCamera(); + Future toggleScreenShare(); + Future setPublishQuality(String key); // Streamer-Seite (M4) + Future setSubscribeQuality(String key); // Empfänger-Seite (M4) + Future setOutputVolume(double v); +} +``` +Presence-Writing (`_writePresence`, Matrix-State-Event +`io.pyramid.voice.presence`) bleibt intern, aber nutzt eine injizierte +`Client`-Referenz statt globalem Singleton-Zugriff – macht das Modul für +Tests austauschbar. + +### 3. `call_ui` +**Ersetzt:** `features/call/voice_channel.dart`, `features/call/mini_call_widget.dart` +Konsumiert ausschließlich `CallSignalingService` + `VoiceChannelService` über +Riverpod-Provider (`callSignalingProvider`, `voiceChannelProvider`). Keine +direkten Importe von `core/voip_manager.dart`/`core/livekit_call_manager.dart` +mehr – heute importieren `chat_view.dart`, `matrix_client.dart` und +`settings_modal.dart` diese Dateien direkt, das entfällt. + +### 4. `settings` (aufgesplittet) +Kein eigenes fachliches Modul, sondern reine Aggregations-UI. Jede Sektion +wird eine eigene Datei/Klasse mit einer schmalen `Widget build(...)`-Fläche, +die ihrerseits nur die Provider ihres Fachmoduls konsumiert (z. B. +`_VoiceSection` nutzt künftig `voiceChannelProvider`/`callSignalingProvider` +statt der Manager direkt). Vorschlag für Dateien: +`settings/profile_section.dart`, `settings/notifications_section.dart`, +`settings/appearance_section.dart`, `settings/sessions_section.dart`, +`settings/account_section.dart`, `settings/voice_section.dart`, +`settings/about_section.dart`. + +### 5. `verification` (neu, aus Settings herausgelöst) +**Quelle:** `widgets/settings_modal.dart` Zeile 2921–4545 (SAS-Dialog, +QR-Anzeige, Emoji-Vergleich, Recovery-Key-Flow). +```dart +abstract class VerificationService { + Future startSasVerification(DeviceKeys device); + Future confirmSas(); + Future declineSas(); + Stream get stepStream; +} +``` +Eigenständig, weil SAS/QR-Verifikation künftig auch außerhalb der Settings +gebraucht werden könnte (z. B. beim Login auf einem neuen Gerät – +`bootstrap_dialog.dart` macht heute schon etwas Ähnliches separat). + +### 6. `storage` (Fassade, kein Rewrite) +**Ausbau von:** `core/settings_prefs.dart` +```dart +abstract class Prefs { + Future getBool(String key, {bool fallback}); + Future setBool(String key, bool value); + // … String, double, StringSet analog zu BoolPref/StringSetPref +} +``` +Ziel ist NICHT, `SharedPreferences` zu ersetzen, sondern die bereits +vorhandene `BoolPref`/`StringSetPref`-Abstraktion zur Pflicht zu machen. +Die 12 identifizierten Direktzugriffsstellen (u. a. `voip_manager.dart:260`, +`livekit_call_manager.dart:246`, `settings_modal.dart:1804/1860/1870`) werden +schrittweise umgestellt – **nicht** in diesem Schritt, sondern sobald das +jeweilige Modul (z. B. `call_signaling`) ohnehin angefasst wird. +Krypto-/Access-Token-Speicherung (`flutter_secure_storage`, +`matrix_client.dart`) bleibt bewusst außen vor – hängt an der noch offenen +SQLCipher-Frage (M1) und wird dort separat behandelt, nicht hier. + +### 7. `auth` +**Dateien:** `features/auth/login_notifier.dart`, `login_page.dart`, +`server_page.dart`, `bootstrap_dialog.dart` (Bootstrap-Teil, Verifikations-Teil +wandert ggf. zu `verification`, s. o.). Schnittstelle bleibt der bereits +vorhandene `matrixClientProvider`/`login_notifier`-Zustandsautomat – heute +schon relativ sauber gekapselt, kein akuter Handlungsbedarf. + +### 8. `push` +**Dateien:** `core/background_push.dart`, `core/fcm_push_service.dart`, +`core/notification_service.dart`. Bereits über eigene Dateien getrennt +(nativ vs. Flutter-Engine, siehe Memory „Pyramid Push"). Einzige Lücke: +eigener `sqflite`-Zugriff in `background_push.dart` statt gemeinsamer +DB-Schicht – bewusst so gewollt (Background-Isolate-Zwang), daher **kein** +Refactoring-Kandidat, nur dokumentieren. + +### 9. `chat_timeline` / `rooms` +Niedrigste Priorität für den Pilot-Umbau, da laut Ist-Analyse strukturell +am wenigsten verwoben (Hauptproblem dort ist Dateigröße + Duplikate, nicht +Modul-Kopplung). Wird nach dem Call-Pilot angegangen. + +## Reihenfolge-Vorschlag für die Umsetzung + +1. **Pilot: `call_signaling` + `voice_channel` + `call_ui`** (Punkt „Call-Schicht + entwirren" in ROADMAP.md) – kleinster klar abgegrenzter Schnitt, deckt + gleich zwei ROADMAP-Punkte ab (Modul-Schnitt beweisen + Call-Entwirrung). +2. `settings_modal.dart` aufsplitten (reine Datei-Teilung, geringes Risiko, + keine Verhaltensänderung). +3. `verification` aus Settings herauslösen. +4. `storage`-Fassade schrittweise erzwingen, immer im Zuge anderer Umbauten. +5. `chat_timeline`/`rooms` zuletzt. + +**Bernd-Entscheidung nötig:** siehe Frage in PROGRESS.md – ob Punkt 1 +(Call-Pilot) so wie hier vorgeschlagen als Nächstes umgesetzt werden soll.