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

14 KiB
Raw Permalink Blame History

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:

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:

..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:

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:

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

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