# 04 — Direct Messages (DMs)

---

## Inhaltsverzeichnis
- [DM starten oder finden](#dm-starten-oder-finden)
- [DM-Raum identifizieren](#dm-raum-identifizieren)
- [DM-Liste](#dm-liste)
- [Gruppen-DM](#gruppen-dm)
- [DM-Raum schließen / vergessen](#dm-raum-schließen--vergessen)
- [User-Profil für DM laden](#user-profil-für-dm-laden)
- [Verschlüsselung in DMs](#verschlüsselung-in-dms)

---

## DM starten oder finden

```dart
// 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

```dart
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

```dart
// 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

```dart
// 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

```dart
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

```dart
// 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

```dart
// 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)

```dart
// 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();
}
```
