Files
pyramid/PROTOKOLL.md
T
Bernd Steckmeister 25ed765a03 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
2026-07-03 05:47:18 +02:00

387 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Pyramid Entwicklungsprotokoll
Stand: 24.04.2026
---
## Projektübersicht
**Pyramid** ist ein Flutter-basierter Matrix-Client, modelliert nach einem FluffyChat-Fork,
mit zwei parallelen Call-Systemen:
| System | Einsatz | Datei |
|--------|---------|-------|
| **LiveKit** (`livekit_client`) | Gruppen-Sprachräume | `lib/core/livekit_call_manager.dart` |
| **Matrix VoIP** (`matrix` SDK) | 1:1 Direktnachrichten | `lib/core/voip_manager.dart` |
Server-Infrastruktur läuft auf einem Raspberry Pi (`steggi-matrix.work`), LiveKit unter
`livekit.steggi-matrix.work`.
---
## Erledigte Änderungen
### 1. LiveKit Qualitäts-Bugs behoben (`livekit_call_manager.dart`)
**Problem:** Streamer und Zuschauer konnten die Übertragungsqualität nicht ändern.
**Ursachen:**
- `_screenShareEncoding()` hatte einen hardcodierten `switch ('balanced')` — gab immer
halbe Bitrate zurück, unabhängig vom eingestellten Qualitäts-Key.
- `setSubscribeQuality('auto')` sprang vorzeitig raus und setzte die Qualität nie.
**Fix:**
```dart
VideoEncoding _screenShareEncoding() {
return _screenShareEncodings[_localQualityKey] ??
const VideoEncoding(maxBitrate: 1_500_000, maxFramerate: 15);
}
```
`setSubscribeQuality` wendet jetzt immer die Qualität an, auch bei 'auto'.
---
### 2. LiveKit Track-Events lösen UI-Rebuild aus (`livekit_call_manager.dart`)
**Problem:** Kamera-Feed wurde nach dem Publishen nicht angezeigt, weil kein Widget-Rebuild ausgelöst wurde.
**Fix:** Handler für alle Track-Events hinzugefügt:
```dart
..on<LocalTrackPublishedEvent>((_) => notifyListeners())
..on<TrackPublishedEvent>((_) => notifyListeners())
..on<TrackUnpublishedEvent>((_) => notifyListeners())
..on<TrackSubscribedEvent>((_) => notifyListeners())
..on<TrackUnsubscribedEvent>((_) => notifyListeners())
..on<ParticipantConnectedEvent>((_) => notifyListeners())
..on<ParticipantDisconnectedEvent>((_) => notifyListeners())
```
---
### 3. LiveKit Optimistic Mute (`livekit_call_manager.dart`)
**Problem:** Mute-Button hatte eine spürbare Verzögerung (async API-Call).
**Fix:** `notifyListeners()` wird jetzt vor dem `await` aufgerufen:
```dart
Future<void> toggleMute() async {
isMuted = !isMuted;
notifyListeners(); // Sofort
await _room?.localParticipant?.setMicrophoneEnabled(!isMuted);
}
```
---
### 4. VoIP Kamera-Fix (`voip_manager.dart`)
**Problem:** Kamera-Tracks wurden nicht korrekt zugewiesen, weil der Renderer bereits belegt
wurde bevor Video-Daten verfügbar waren.
**Fix:**
- Track-Zuweisung erfolgt jetzt erst wenn `getVideoTracks().isNotEmpty` — kein Renderer-Update
auf leere Streams mehr.
- `_isCameraMuted` wird bei eingehenden Calls korrekt aus dem Call-Typ gesetzt:
`_isCameraMuted = call.type == CallType.kVoice;`
- Bei `kConnected`-State wird `setLocalVideoMuted(false)` nochmals aufgerufen falls Kamera
aktiv sein soll.
- Poll-Intervall von 1500ms auf 1000ms reduziert für schnellere Feed-Erkennung.
---
### 5. VoIP Optimistic Mic/Camera Toggle (`voip_manager.dart`)
**Problem:** `toggleMic()` und `toggleCamera()` nutzten `safeAction()` mit 1500ms Delay.
**Fix:**
- `toggleMic()` flippt State sofort, reverted bei Fehler automatisch.
- `toggleCamera()` flippt `_isCameraMuted` sofort (optimistic), ruft `_updateRenderers()`
mehrfach nach kurzen Delays auf.
---
### 6. VoIP Desktop Speakerphone Fix (`voip_manager.dart`)
**Problem:** `MissingPluginException: No implementation found for method enableSpeakerphone`
auf Windows/Desktop.
**Fix:** Speakerphone-Callback wird im Konstruktor unterdrückt:
```dart
try {
(voip as dynamic).onConfigSpeakerphone = (bool _) async {};
} catch (_) {}
```
---
### 7. SQLite-Stabilität auf Windows
**Problem:** Sporadische "Database is locked"-Fehler durch parallele Zugriffe.
**Fix:** WAL-Modus aktiviert und `busy_timeout` gesetzt, um Locking-Konflikte zu verhindern.
---
### 8. Einheitliches Call-UI (`voice_channel.dart`) Kompletter Rewrite
Die gesamte `voice_channel.dart` wurde neu strukturiert mit einer einheitlichen Architektur
für LiveKit und VoIP.
#### 8a. Unified Participant Model
```dart
class _ParticipantData {
final String id, name;
final Widget? videoWidget;
final bool isSpeaking, isMuted, hasVideo, isScreenShare;
final Color avatarColor;
final dynamic originalParticipant;
}
```
#### 8b. Adaptive Seitenverhältnisse (`voice_channel.dart`)
**Problem:** Video-Tiles waren auf `AspectRatio(16/9)` festgelegt — Hochkant-Videos vom
Handy wurden stark beschnitten und falsch dargestellt.
**Fix:**
- `_ParticipantData` bekommt ein `aspectRatio`-Feld (Default `16.0/9.0`).
- VoIP: echte Dimension aus `RTCVideoRenderer.videoWidth/videoHeight` ausgelesen.
- LiveKit: echte Dimension aus `(videoTrack as dynamic).currentDimensions` ausgelesen.
- Alle Renderer nutzen `VideoViewFit.contain` / `RTCVideoViewObjectFitContain` statt `cover`
— kein Beschnitt mehr.
- `_ParticipantGrid` wechselt von `GridView(childAspectRatio: 16/9)` zu `Wrap` mit
individuell berechneter `SizedBox(width, height)` pro Tile.
- `_ParticipantStrip` passt Tile-Breite an: `101 * p.aspectRatio` für Landscape-Tiles.
#### 8c. Hero/Spotlight-System
- **Auto-Spotlight:** Screenshares werden automatisch als Hero angezeigt
- **Manuelles Pinnen:** Klick auf Tile → wird Hero; erneuter Klick = unpin
- Gear-Icon im Hero-Overlay öffnet das Qualitätsmenü (hover-reveal)
- Fullscreen-Button öffnet eine eigene Fullscreen-Seite via `Navigator.push`
#### 8d. Aktive Sprecher
Pulsierende Rahmen-Animation (`AnimationController`) für Teilnehmer, die gerade sprechen.
#### 8e. Teilnehmer-Sortierung
```
Screensharer → Kamera-an → Sprechend → Audio-only
```
#### 8f. Responsive Layouts
**Portrait:** Hero oben (Expanded) + horizontaler Teilnehmer-Strip unten (120px)
**Landscape:** Hero links (Expanded) + vertikale Sidebar rechts (200px)
Beide Layouts sind **einklappbar** (Swipe-Geste + Pfeil-Button).
#### 8g. Zentrales Mirroring
`_MirrorWrapper` spiegelt nur die eigene Kamera (`isLocal && !isScreenShare`).
Screenshares und Remote-Teilnehmer bleiben ungespiegelt.
#### 8h. Grid-Layout mit einheitlichen Tiles
`SliverGridDelegateWithFixedCrossAxisCount`:
- < 600px → 2 Spalten
- 600900px → 3 Spalten
- > 900px → 4 Spalten
#### 8i. Steuerungs-Reihenfolge (alle Views)
```
[ Mic ] [ Deaf ] [ Kamera ] [ Screen ] | [ Auflegen ]
```
---
### 9. Mini-Call-Widget Steuerungs-Reihenfolge (`mini_call_widget.dart`)
Gleiche Reihenfolge wie Haupt-Call-Ansicht: Mic → Deaf → Kamera → Screen.
---
### 10. 4K Support
4K (2160p) als Qualitätsoption in LiveKit- und VoIP-Qualitätsmenüs hinzugefügt.
---
### 11. App-Icon Pyramid-Logo (`flutter_launcher_icons.yaml`)
**Problem:** App zeigte das Flutter-Standard-Icon statt dem Pyramid-Logo.
**Fix:**
- `flutter_launcher_icons.yaml` auf `assets/logo.png` (goldene 3D-Pyramide, 1024px) umgestellt.
- macOS-Generierung explizit aktiviert (`macos: generate: true`).
- Windows ICO nach `dart run flutter_launcher_icons` mit Pillow neu generiert
(16/32/48/64/128/256px multi-size ICO, verhindert das 48px-Limit des Packages).
---
### 12. Lade-Animation 3D Pyramiden-Painter (`pyramid_loader.dart`)
**Problem:** Lottie-JSON zeigte inkorrekte Farben, SVG wurde nur statisch gerendert
(flutter_svg führt kein JavaScript aus).
**Lösung:** JavaScript-Animations-Logik aus dem Original-SVG exakt nach Dart portiert
als `CustomPainter`:
- `_ease(t, exp)`: kubische Ease-In/Out wie im JS-Original
- `_yawAt(t)`: Yaw-Rotation mit Hold-Phase (80% Rotation, 20% Pause bei π/4)
- `_PyramidPainter`: 4 Flächen, Y-Rotation, Z-Clipping, orthografische Projektion,
20°-Tilt, Depth-Sorting (Painter's Algorithm)
- Fill: `darken(accent, 0.45)`, Stroke: `accent`, strokeWidth: `1.4 * (size/96)`
- `useLottie: false` ist nun der Standard — kein Lottie-Asset mehr nötig
**Loader Preview** in den Appearance Settings (zwei 120px-Boxen: Custom Painter + Lottie-Fallback).
---
### 13. Chat-Scroll-Stabilisierung (`chat_view.dart`)
**Problem:** Der Chat zuckte und zitterte je weiter man nach oben scrollte, besonders
wenn neue History nachgeladen wurde.
**Ursache:** `DateDivider` und `MessageGroup` waren in einem gemeinsamen `Column`-Widget
mit einem einzigen Key zusammengefasst. Wenn beim Nachladen von History die `showDate`-
Eigenschaft einer Gruppe sich änderte (z.B. weil sie nicht mehr die älteste Gruppe war),
änderte sich die Höhe des Items — direkt in dem Bereich, den der User gerade betrachtete.
**Fix:**
- `DateDivider` und `MessageGroup` sind jetzt separate List-Items mit unabhängigen Keys:
- Gruppen: `ValueKey('g:<firstEventId>')`
- Datumstrennlinien: `ValueKey('d:<YYYY-MM-DD>')`
- `findChildIndexCallback` nutzt eine vorberechnete `Map<String, int>` für O(1)-Lookup
statt O(n)-`indexWhere`.
- 200ms Cooldown nach `requestHistory` verhindert sofortige Re-Trigger durch veraltetes
`maxScrollExtent`.
---
### 14. Startup-Flash Fix (`app.dart`)
**Problem:** Beim App-Start flackerte kurz die Server-Eingabe-Seite auf, bevor der
korrekte Redirect zu `/app` stattfand.
**Ursache:** `isLoggedInProvider` (StreamProvider) gibt beim ersten Build `AsyncLoading`
zurück. `valueOrNull ?? false` ergibt `false` → Router leitet sofort zu `/server` weiter.
Kurz danach resolvet der Stream und der Router erstellt sich neu mit `isLoggedIn = true`.
**Fix:** `PyramidApp` prüft `isLoggedInProvider` zuerst. Solange noch `isLoading`:
vollbild `PyramidLoader` statt Router — die Pyramiden-Animation überbrückt den kurzen
Moment, bis der Auth-State bekannt ist.
---
### 15. Eigenes Profilbild (`user_panel.dart`)
**Problem:** Das eigene Profilbild wurde unten links (UserPanel) nicht angezeigt —
nur ein Farbverlauf-Platzhalter mit Initial-Buchstabe.
**Fix:**
- `_ownProfileProvider` (FutureProvider): ruft einmalig `client.getProfileFromUserId(userID)`
auf und cached das Ergebnis.
- `UserPanel` zeigt nun `MxcAvatar` mit dem echten Profilbild.
- Anzeigename kommt aus `profile.displayName` statt nur aus der UserID.
- Farbverlauf-Initial-Avatar bleibt als Fallback (`_InitialAvatar`) während des Ladens
oder wenn kein Bild gesetzt ist.
---
### 16. QR-Code-Verifikation (`app_shell.dart`, `settings_modal.dart`)
**Problem:** Geräteverifikation unterstützte nur Emoji-SAS — kein QR-Code-Flow.
**Fix:**
- `KeyVerificationMethod.qrShow` zu `verificationMethods` in `matrix_client.dart` hinzugefügt.
- `pubspec.yaml`: `pretty_qr_code: ^3.6.0` und `qr: ^3.0.2` ergänzt.
- Eingehender Verifizierungsdialog (`app_shell.dart`) und ausgehender Dialog (`settings_modal.dart`)
unterstützen jetzt beide 3 neue States:
- `askChoice` → Methodenwahl (Emoji oder QR)
- `confirmQRScan` → QR-Code anzeigen (`QrCode.fromUint8List` + `PrettyQrView`) + "Gescannt bestätigen"-Button
- `showQRSuccess` → Erfolgsanzeige
**Technischer Hinweis:** Matrix QR-Codes sind Binärdaten (`qrDataRawBytes: Uint8Buffer`).
Rendering über `QrCode.fromUint8List(data: rawBytes, errorCorrectLevel: QrErrorCorrectLevel.L)`.
---
### 17. Wiederherstellungsschlüssel ändern (`settings_modal.dart`)
**Problem:** Kein Feature zum Wechseln des SSSS-Wiederherstellungsschlüssels vorhanden.
**Implementierung:** `_ChangeRecoveryKeyDialog` mit Bootstrap-State-Machine:
**Flow:**
1. **Laden** — Bootstrap startet, `wipeSsss(true)``newSsss()` generiert neuen Schlüssel
2. **Schlüssel anzeigen** — Schlüssel selektierbar + Kopier-Button; wartet auf Nutzer
3. **Bestätigen** — Nutzer tippt Schlüssel zur Bestätigung ein (`_ConfirmKeyBody`)
4. **Aktivieren** — wartet auf Bootstrap `done` (Cross-Signing + Key Backup im Hintergrund)
5. **Fertig** — Erfolgsmeldung
**Kritischer Fix (BootstrapBadStateException):**
- `newSsss()` ruft intern `checkCrossSigning()` auf → State geht sofort zu `askWipeCrossSigning`.
- `openExistingSsss()` ist nach `newSsss()` nicht mehr erreichbar — wurde entfernt.
- Alle Post-`newSsss`-States werden ohne `_step`-Guards auto-advanced.
- Bootstrap-`done` setzt `_step` nur wenn bereits in `applying` — verhindert vorzeitigen Dialog-Schluss.
---
## Offene Punkte
### ⚠️ Screenshare auf Windows (LiveKit)
`startScreenShare()` wählt auf Windows automatisch die erste verfügbare Screen-Source.
Der `ScreenSharePicker` ist implementiert, muss noch verifiziert werden.
### ️ Profilbilder in Chats Lazy Loading
Profilbilder in der Raumliste und im Chat werden erst beim Öffnen des jeweiligen
Raums geladen (kein Preloading). Ist akzeptables Verhalten, kein Fehler.
### 📋 Noch nicht implementiert (Settings)
~~Alle erledigt in Sitzung 3 (26.04.2026): Gerät umbenennen, Alle anderen Sitzungen abmelden, Ed25519-Fingerabdruck, Blockierte Nutzer, Passwort ändern, Konto löschen, Logout-Button~~
### 💡 Nice-to-have
- **QR-Express-Login** (MSC4108): Desktop zeigt QR → Handy scannt → eingeloggt + verifiziert.
Voraussetzung: Rendezvous-Server + SDK-Unterstützung (matrix-dart-sdk ≥ 7.x erwartet).
Aktuell blockiert durch fehlende SDK-API in 6.2.0.
---
## Geänderte Dateien (vollständig)
| Datei | Inhalt |
|-------|--------|
| `lib/core/livekit_call_manager.dart` | Quality-Bug-Fix, Track-Events, Optimistic Mute, 4K |
| `lib/core/voip_manager.dart` | Kamera-Fix, Optimistic Toggle, Speakerphone-Fix, 4K |
| `lib/features/call/voice_channel.dart` | Kompletter Rewrite: Unified Architecture, adaptive AR, Hero, Mirroring, Grid, Sortierung, Controls |
| `lib/features/call/mini_call_widget.dart` | Steuerungs-Reihenfolge |
| `lib/core/matrix_client.dart` | WAL-Modus, busy_timeout, qrShow in verificationMethods |
| `lib/widgets/pyramid_loader.dart` | 3D CustomPainter (JS-Port), useLottie=false Standard |
| `lib/core/app.dart` | Startup-Flash-Fix: PyramidLoader während isLoggedInProvider lädt |
| `lib/features/chat/chat_view.dart` | Scroll-Stabilisierung: flache Item-Liste, O(1)-Lookup, Cooldown |
| `lib/features/rooms/user_panel.dart` | Eigenes Profilbild via _ownProfileProvider + MxcAvatar |
| `lib/widgets/settings_modal.dart` | QR-Verifikation, Wiederherstellungsschlüssel ändern, Loader-Preview |
| `lib/layout/app_shell.dart` | QR-Verifikation im eingehenden Verifizierungsdialog |
| `pubspec.yaml` | pretty_qr_code, qr hinzugefügt |
| `flutter_launcher_icons.yaml` | Pyramid-Logo für alle Plattformen |
| `windows/runner/resources/app_icon.ico` | Multi-Size ICO (16/32/48/64/128/256px) via Pillow |
| `assets/logo.png` | Pyramid-Logo (1024px) |
| `assets/pyramid-loader.json` | Lottie-Fallback (aus Ordner mit Leerzeichen verschoben) |
---
## Backups
| Datum | Pfad |
|-------|------|
| 24.04.2026 (Sitzung 1) | `E:\pyramid_backup_20260424` |
| 24.04.2026 (Sitzung 2) | `E:\pyramid_backup_20260424_2` |
| 26.04.2026 | `E:\pyramid_backup_20260426` |