docs: M2-Modul-Schnitt definieren (docs/M2_MODULE_SCHNITT.md)
Öffentliche Schnittstellen für call_signaling, voice_channel, call_ui, settings, verification, storage u.a. entworfen. Wichtigste Erkenntnis: "Calls" ist architektonisch zwei getrennte Module (Matrix-VoIP vs. LiveKit-SFU-Voice-Channels), keine gemeinsame Fassade. Korrigiert nebenbei einen falschen Ist-Analyse-Befund (rooms_provider-Import war fälschlich als toter Code eingestuft).
This commit is contained in:
+63
-5
@@ -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
|
||||
|
||||
+7
-3
@@ -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)
|
||||
|
||||
@@ -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<CallSession?> get currentCallStream;
|
||||
bool get isMicMuted;
|
||||
bool get isCameraMuted;
|
||||
Future<void> startCall(String roomId, {bool video});
|
||||
Future<void> hangup();
|
||||
Future<void> toggleMic();
|
||||
Future<void> toggleCamera();
|
||||
Future<void> 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<VoiceChannelState> get stateStream; // isActive, isMuted, participants…
|
||||
Future<void> join({required String roomName, required String matrixRoomId, ...});
|
||||
Future<void> leave();
|
||||
Future<void> toggleMute();
|
||||
Future<void> toggleDeafen();
|
||||
Future<void> toggleCamera();
|
||||
Future<void> toggleScreenShare();
|
||||
Future<void> setPublishQuality(String key); // Streamer-Seite (M4)
|
||||
Future<void> setSubscribeQuality(String key); // Empfänger-Seite (M4)
|
||||
Future<void> 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<void> startSasVerification(DeviceKeys device);
|
||||
Future<void> confirmSas();
|
||||
Future<void> declineSas();
|
||||
Stream<VerificationStep> 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<bool> getBool(String key, {bool fallback});
|
||||
Future<void> 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.
|
||||
Reference in New Issue
Block a user