Files
pyramid/PROTOKOLL.md
T
Bernd SteckmeisterandClaude Fable 5 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 <[email protected]>
Claude-Session: https://claude.ai/code/session_01CPrAGBxBT6GfPXzeWQ4AXb
2026-07-03 05:47:18 +02:00

14 KiB
Raw 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
  • 600–900px → 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