fix(calls): Bildschirm teilen sichtbar, Verbindung trotz SDK-Wettlauf, Sprech-Ring; Trenner in Gruppen

Bernds Test 2026-10-08 (Streamen ging weder am PC noch am Handy, Ton kam
nicht an, Trenner blieb manchmal stehen) – mit neuem Live-Anruftest
(zwei Testkonten im selben Prozess) nachgestellt und behoben:

- Bildschirm teilen: Die Stromzuordnung rief ein SDK-getFeeds() auf, das es
  nicht gibt; der Fehler wurde geschluckt → Übertragungen wurden nie
  angezeigt und beim Sender nie als aktiv erkannt. Jetzt typisierte
  Zugriffe (remote/localScreenSharingStream, 'stopped' beachtet).
- Verbindungsaufbau: matrix-SDK verwirft Kandidaten, die vor der fertig
  verarbeiteten Einladung bzw. während des Annehmens eintreffen; unter
  Windows wirft getRemoteDescription zudem statt null zu liefern. Pyramid
  puffert alle Kandidaten der Gegenseite und reicht sie direkt an die
  Verbindung nach. Test: vorher nie verbunden, jetzt ~0,5 s (angerufen)
  bzw. ~2,7 s (anrufend), Ton beide Richtungen, Teilen beide Richtungen.
- SDK-Aufrufe in eigener Zone: harmlose Plugin-Fehler (Lautsprecher unter
  Windows) werden protokolliert statt durchzuschlagen; Ergebnisse/Fehler
  per Completer über die Zonengrenze (sonst ewiges Warten).
- Sprech-Ring auch bei 1:1-Anrufen (Pegel aus WebRTC-Statistiken) – zeigt,
  ob das eigene Mikrofon Ton liefert und ob die Gegenseite ankommt; großes
  Bild springt nicht mehr bei jedem Sprechwechsel.
- „Neue Nachrichten“ mitten in einer Gruppe (mehrere Nachrichten desselben
  Absenders) blieb bis zum erneuten Öffnen stehen: Inline-Trenner jetzt nur
  mit Ungelesenem, blendet aus und trägt den Sprungschlüssel. Live-Test.
- „Anruf läuft“-Leiste nicht schon beim Klingeln.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Bernd Steckmeister
2026-10-08 11:05:46 +02:00
co-authored by Claude Opus 5.5
parent 5485b6d449
commit 8074928505
12 changed files with 793 additions and 97 deletions
@@ -103,7 +103,7 @@ extension _BubbleBuilder on _MessageGroupState {
rows.add(_ReadMarker(pt: pt));
}
if (widget.fullyReadId == event.eventId && !isLast) {
rows.add(_InlineUnreadDivider(pt: pt));
rows.add(_InlineUnreadDivider(key: widget.unreadDividerKey, pt: pt, fading: widget.unreadFading));
}
}
@@ -32,6 +32,9 @@ class MessageGroup extends StatefulWidget {
/// When non-null and pointing to a non-last event in this group, an inline
/// "Neue Nachrichten" divider is rendered after that event's row.
final String? fullyReadId;
/// Ausblenden des Inline-Trenners + Schlüssel für den Sprung zu ihm.
final bool unreadFading;
final GlobalKey? unreadDividerKey;
/// Jump-to-message (from search): the row matching [jumpEventId] gets
/// [jumpKey] attached (for ensureVisible) and [highlightEventId] is tinted.
final String? jumpEventId;
@@ -55,6 +58,8 @@ class MessageGroup extends StatefulWidget {
this.showStatusIndicator = false,
this.readEventIds = const {},
this.fullyReadId,
this.unreadFading = false,
this.unreadDividerKey,
this.jumpEventId,
this.jumpKey,
this.highlightEventId,
@@ -155,7 +160,7 @@ class _MessageGroupState extends State<MessageGroup> {
// Inline unread divider after the first event when it's the fullyRead
// marker and there are more events in this group.
if (widget.fullyReadId == firstEvent.eventId && widget.events.length > 1) {
rows.add(_InlineUnreadDivider(pt: pt));
rows.add(_InlineUnreadDivider(key: widget.unreadDividerKey, pt: pt, fading: widget.unreadFading));
}
// ── Continuation event rows (siblings, same indentation) ──
@@ -174,7 +179,7 @@ class _MessageGroupState extends State<MessageGroup> {
if (widget.readEventIds.contains(event.eventId)) rows.add(_ReadMarker(pt: pt));
// Inline divider after this event if it's the fullyRead and not the last.
if (widget.fullyReadId == event.eventId && j < widget.events.length - 1) {
rows.add(_InlineUnreadDivider(pt: pt));
rows.add(_InlineUnreadDivider(key: widget.unreadDividerKey, pt: pt, fading: widget.unreadFading));
}
}
+11 -2
View File
@@ -93,11 +93,19 @@ class _InitialBox extends StatelessWidget {
// fullyRead event is in the middle of the group (same sender, <5 min window).
class _InlineUnreadDivider extends StatelessWidget {
final PyramidTheme pt;
const _InlineUnreadDivider({required this.pt});
final bool fading;
const _InlineUnreadDivider({super.key, required this.pt, this.fading = false});
@override
Widget build(BuildContext context) {
return Padding(
// Blendet aus wie der Gruppen-Trenner (vorher blieb dieser „Neue
// Nachrichten“-Trenner mitten in einer Gruppe bis zum erneuten Öffnen
// stehen – Bernd 2026-10-08).
return AnimatedOpacity(
opacity: fading ? 0.0 : 1.0,
duration: const Duration(milliseconds: 700),
curve: Curves.easeOut,
child: Padding(
padding: const EdgeInsets.fromLTRB(0, 8, 0, 4),
child: Row(
children: [
@@ -112,6 +120,7 @@ class _InlineUnreadDivider extends StatelessWidget {
Expanded(child: Divider(color: pt.accent.withAlpha(160), height: 1)),
],
),
),
);
}
}
@@ -216,10 +216,11 @@ class _MessageList extends StatelessWidget {
// Pass fullyReadId to the group only when the event is mid-group
// (not the last event) so MessageGroup can render an inline divider.
// groupContainsFullyRead already guarantees fullyReadId != null.
final passedFullyReadId =
(groupContainsFullyRead && group.last.eventId != fullyReadId)
? fullyReadId
: null;
final passedFullyReadId = (hasUnread &&
groupContainsFullyRead &&
group.last.eventId != fullyReadId)
? fullyReadId
: null;
itemFullyReadIds.add(passedFullyReadId);
if (isLastOfDay) {
@@ -292,6 +293,9 @@ class _MessageList extends StatelessWidget {
showStatusIndicator: showStatus,
readEventIds: readEventIds,
fullyReadId: itemFullyReadIds[i],
unreadFading: unreadFading,
unreadDividerKey:
itemFullyReadIds[i] != null ? unreadDividerKey : null,
jumpEventId: jumpEventId,
jumpKey: jumpKey,
highlightEventId: highlightEventId,
+4 -1
View File
@@ -305,7 +305,10 @@ class _ActiveCallBar extends ConsumerWidget {
final lk = ref.watch(voiceChannelProvider);
final voipCall = voip.currentCall;
final String? name;
if (voipCall != null && voipCall.state != CallState.kEnded) {
// Klingeln zählt nicht als „läuft“ – dafür gibt es das Annehmen-Fenster.
if (voipCall != null &&
voipCall.state != CallState.kEnded &&
voipCall.state != CallState.kRinging) {
name = voipCall.room.getLocalizedDisplayname();
} else if (lk.isActive || lk.isConnecting) {
name = lk.displayName;