Files
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

5.4 KiB

04 — Direct Messages (DMs)


Inhaltsverzeichnis


DM starten oder finden

// Existierenden DM finden oder neuen erstellen
// Gibt immer einen joined Room zurück
final room = await client.startDirectChat('@alice:server');
print(room.id); // !roomId:server

// Mit expliziten Optionen
final room = await client.startDirectChat(
  '@alice:server',
  // enableEncryption: true,  // Standard: true
);

// Navigieren zum DM-Raum
// (z.B. in Pyramid:)
ref.read(activeRoomIdProvider.notifier).state = room.id;

DM-Raum identifizieren

final room = client.getRoomById('!roomId:server')!;

// Ist es ein DM?
final isDm = room.isDirectChat;                      // bool

// Mit wem?
final partner = room.directChatMatrixID;             // '@alice:server' oder null

// DM zu einem bestimmten User finden
final existingDm = client.getDirectChatFromUserId('@alice:server');
// Gibt Room? zurück (null wenn kein DM vorhanden)

// Alle DMs des Users (Account-Data)
final dmMap = client.directChats;
// Map<String, List<String>>: { '@alice:server': ['!roomId:server'] }

DM-Liste

// Alle DM-Räume (joined)
final dms = client.rooms.where((r) =>
    r.isDirectChat &&
    r.membership == Membership.join,
).toList();

// Sortiert nach letzter Aktivität
dms.sort((a, b) =>
  (b.lastEvent?.originServerTs ?? DateTime(0))
      .compareTo(a.lastEvent?.originServerTs ?? DateTime(0)));

// DMs mit ungelesenen Nachrichten
final unreadDms = dms.where((r) => r.notificationCount > 0).toList();

// Display-Info für DM-Liste
for (final dm in dms) {
  final partner = client.getDirectChatFromUserId(dm.directChatMatrixID!);
  final user = dm.unsafeGetUserFromMemoryOrFallback(dm.directChatMatrixID!);
  print(user.displayName ?? dm.directChatMatrixID);
  print(user.avatarUrl);
  print(dm.lastEvent?.body);              // Vorschautext
  print(dm.lastEvent?.originServerTs);   // Zeitstempel
  print(dm.notificationCount);           // Ungelesen
}

Gruppen-DM

// Gruppen-DM mit mehreren Personen erstellen
final roomId = await client.createRoom(
  name: 'Gruppenunterhaltung',
  invite: ['@bob:server', '@charlie:server'],
  isDirect: true,                        // Als DM markieren
  preset: CreateRoomPreset.trustedPrivateChat,
  enableEncryption: true,
);

// Oder mit startDirectChat (für Einzel-DMs) und manuell Teilnehmer hinzufügen
final room = await client.startDirectChat('@bob:server');
await room.invite('@charlie:server');

// Gruppe als DM im Account-Data kennzeichnen
// (passiert automatisch bei isDirect: true)

DM-Raum schließen / vergessen

final room = client.getRoomById('!roomId:server')!;

// Verlassen (Gegenüber kann weiter chatten)
await room.leave();

// Aus Raumliste entfernen
await room.forget();

// DM aus Account-Daten entfernen
final dmMap = Map<String, dynamic>.from(client.directChats);
dmMap.remove('@alice:server');
// oder nur den spezifischen Raum entfernen:
final rooms = List<String>.from(dmMap['@alice:server'] ?? []);
rooms.remove(room.id);
if (rooms.isEmpty) {
  dmMap.remove('@alice:server');
} else {
  dmMap['@alice:server'] = rooms;
}
await client.setAccountData(
  client.userID!,
  'm.direct',
  dmMap,
);

User-Profil für DM laden

// Profil des DM-Partners laden
final partnerMxid = room.directChatMatrixID!;

// Aus Room-Membership (gecacht, schnell)
final user = room.unsafeGetUserFromMemoryOrFallback(partnerMxid);
print(user.displayName);
print(user.avatarUrl);

// Frisch vom Server (genauer, aber langsamer)
final profile = await client.getProfileFromUserId(partnerMxid);
print(profile.displayname);
print(profile.avatarUrl);

// Avatar-URL in Widget verwenden
// (mxc:// URI → HTTP-URL via SDK)
final httpUrl = user.avatarUrl?.getThumbnail(
  client,
  width: 56,
  height: 56,
  method: ThumbnailMethod.crop,
);

Verschlüsselung in DMs

// E2EE-Status prüfen
final isEncrypted = room.isEncrypted;

// E2EE aktivieren (einmalig, nicht mehr rückgängig!)
if (!room.isEncrypted) {
  await room.enableEncryption();
}

// Geräte des Partners verifizieren
final partnerDevices = await client.getUserDeviceKeys('@alice:server');
for (final device in partnerDevices?.deviceKeys.values ?? []) {
  print('${device.deviceId}: verified=${device.verified}');
}

// Fehlende Schlüssel für Entschlüsselung anfordern
await client.requestMissingSessionKeys();

// Schlechte Verschlüsselung erkennen
final badEvents = timeline.events
    .where((e) => e.isBadEncrypted)
    .toList();

Einladungen (DM-Invite-Ansicht)

// Eingehende DM-Einladungen
final invites = client.rooms.where((r) =>
    r.membership == Membership.invite &&
    r.isDirectChat).toList();

// Einladung anzeigen
for (final invite in invites) {
  final senderId = invite.getState(EventTypes.RoomMember, client.userID!)
      ?.senderId;              // Wer hat eingeladen?
  final sender = await client.getProfileFromUserId(senderId ?? '');
  print('Einladung von ${sender.displayname}');

  // Annehmen
  await invite.join();

  // Ablehnen
  await invite.leave();
  await invite.forget();
}