Files
pyramid/docs/matrix-sdk/12-quick-reference.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

367 lines
10 KiB
Markdown

# 12 — Schnellreferenz: Typen, Konstanten & Patterns
---
## Wichtige Enum-Werte
```dart
// Membership
Membership.join // Mitglied
Membership.invite // Eingeladen
Membership.leave // Verlassen
Membership.ban // Gesperrt
Membership.knock // Klopf-Anfrage
// JoinRules
JoinRules.public // Offen
JoinRules.invite // Nur per Einladung
JoinRules.knock // Beitrittsanfragen erlaubt
JoinRules.restricted // Nur Mitglieder bestimmter Spaces/Räume
JoinRules.knockRestricted
// HistoryVisibility
HistoryVisibility.worldReadable // Ohne Login lesbar
HistoryVisibility.shared // Alle Mitglieder sehen History
HistoryVisibility.invited // Ab Einladungszeitpunkt
HistoryVisibility.joined // Ab Beitrittszeitpunkt
// GuestAccess
GuestAccess.canJoin // Gäste erlaubt
GuestAccess.forbidden // Keine Gäste
// Visibility
Visibility.public // Im Verzeichnis
Visibility.private // Nicht im Verzeichnis
// CreateRoomPreset
CreateRoomPreset.privateChat // invite + joined History
CreateRoomPreset.publicChat // public + shared History
CreateRoomPreset.trustedPrivateChat // wie private + alle Mitglieder sind Admins
// EventStatus
EventStatus.sending // Wird übertragen
EventStatus.sent // Server-ACK
EventStatus.synced // Im Sync empfangen
EventStatus.error // Fehlgeschlagen
EventStatus.removed // Gelöscht
// PresenceType
PresenceType.online
PresenceType.unavailable
PresenceType.offline
// ThumbnailMethod
ThumbnailMethod.crop // Ausschneiden auf exakte Größe
ThumbnailMethod.scale // Skalieren ohne Abschneiden
// LoginType
LoginType.mLoginPassword
LoginType.mLoginToken
LoginType.mLoginSso
// PushRuleKind
PushRuleKind.override
PushRuleKind.content
PushRuleKind.room
PushRuleKind.sender
PushRuleKind.underride
```
---
## EventTypes Konstanten
```dart
EventTypes.Message // m.room.message
EventTypes.Sticker // m.sticker
EventTypes.Reaction // m.reaction
EventTypes.RoomRedaction // m.room.redaction
EventTypes.Encrypted // m.room.encrypted
EventTypes.RoomCreate // m.room.create
EventTypes.RoomName // m.room.name
EventTypes.RoomTopic // m.room.topic
EventTypes.RoomAvatar // m.room.avatar
EventTypes.RoomMember // m.room.member
EventTypes.RoomPowerLevels // m.room.power_levels
EventTypes.RoomCanonicalAlias // m.room.canonical_alias
EventTypes.RoomJoinRules // m.room.join_rules
EventTypes.RoomGuestAccess // m.room.guest_access
EventTypes.HistoryVisibility // m.room.history_visibility
EventTypes.RoomPinnedEvents // m.room.pinned_events
EventTypes.RoomTombstone // m.room.tombstone
EventTypes.Encryption // m.room.encryption
EventTypes.SpaceChild // m.space.child
EventTypes.SpaceParent // m.space.parent
EventTypes.CallInvite // m.call.invite
EventTypes.CallAnswer // m.call.answer
EventTypes.CallHangup // m.call.hangup
EventTypes.CallCandidates // m.call.candidates
EventTypes.CallReject // m.call.reject
EventTypes.CallSelectAnswer // m.call.select_answer
EventTypes.CallNegotiate // m.call.negotiate
```
---
## Häufige Content-Zugriffe
```dart
// Sicher auf Map-Inhalte zugreifen (Matrix SDK Extension)
final text = event.content.tryGet<String>('body');
final map = event.content.tryGetMap<String, dynamic>('info');
final list = event.content.tryGetList<String>('aliases');
// Event-Inhalt strukturiert
final msgtype = event.content['msgtype'] as String?;
final body = event.content['body'] as String?;
final formatted = event.content['formatted_body'] as String?;
final format = event.content['format'] as String?; // 'org.matrix.custom.html'
// Relation
final relatesTo = event.content
.tryGetMap<String, dynamic>('m.relates_to');
final relType = relatesTo?.tryGet<String>('rel_type');
final eventId = relatesTo?.tryGet<String>('event_id');
// Verschlüsselte Medien
final file = event.content.tryGetMap<String, dynamic>('file');
final url = file?.tryGet<String>('url'); // mxc://
final key = file?.tryGetMap<String, dynamic>('key');
// Mentions (MSC3952)
final mentions = event.content.tryGetMap<String, dynamic>('m.mentions');
final userIds = mentions?.tryGetList<String>('user_ids') ?? [];
final roomMention = mentions?.tryGet<bool>('room') ?? false;
```
---
## Timeline-Widget Pattern (Flutter)
```dart
class ChatView extends ConsumerStatefulWidget {
final String roomId;
const ChatView({required this.roomId, super.key});
@override
ConsumerState<ChatView> createState() => _ChatViewState();
}
class _ChatViewState extends ConsumerState<ChatView> {
Timeline? _timeline;
@override
void initState() {
super.initState();
_loadTimeline();
}
Future<void> _loadTimeline() async {
final client = await ref.read(matrixClientProvider.future);
final room = client.getRoomById(widget.roomId)!;
_timeline = await room.getTimeline(
onUpdate: () => setState(() {}),
);
setState(() {});
}
@override
void dispose() {
_timeline?.cancelSubscriptions(); // WICHTIG!
super.dispose();
}
@override
Widget build(BuildContext context) {
final events = _timeline?.events ?? [];
return ListView.builder(
reverse: true, // Neueste unten
itemCount: events.length,
itemBuilder: (ctx, i) {
final event = events[i];
if (event.type != EventTypes.Message) return const SizedBox();
return ListTile(
title: Text(event.body),
subtitle: Text(event.senderId),
);
},
);
}
}
```
---
## Raum-Liste Widget Pattern
```dart
// StreamProvider für reaktive Raumliste
final roomsProvider = StreamProvider<List<Room>>((ref) async* {
final client = await ref.watch(matrixClientProvider.future);
yield _sortedRooms(client);
await for (final _ in client.onSync.stream) {
yield _sortedRooms(client);
}
});
List<Room> _sortedRooms(Client client) => client.rooms
.where((r) => r.membership == Membership.join)
.toList()
..sort((a, b) =>
(b.lastEvent?.originServerTs ?? DateTime(0))
.compareTo(a.lastEvent?.originServerTs ?? DateTime(0)));
// In Widget:
final rooms = ref.watch(roomsProvider);
rooms.when(
data: (list) => ListView.builder(
itemCount: list.length,
itemBuilder: (ctx, i) {
final room = list[i];
return ListTile(
leading: CircleAvatar(child: Text(room.name[0])),
title: Text(room.getLocalizedDisplayname()),
subtitle: Text(room.lastEvent?.body ?? ''),
trailing: room.notificationCount > 0
? Badge(label: Text('${room.notificationCount}'))
: null,
);
},
),
loading: () => const CircularProgressIndicator(),
error: (e, s) => Text('Fehler: $e'),
);
```
---
## Nachrichten senden mit optimistischem UI
```dart
// Das SDK fügt das Event sofort mit Status 'sending' in die Timeline ein.
// UI kann es sofort anzeigen, ohne auf Server zu warten.
class MessageInput extends ConsumerWidget {
final Room room;
const MessageInput({required this.room, super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final controller = TextEditingController();
return Row(
children: [
Expanded(child: TextField(controller: controller)),
IconButton(
icon: const Icon(Icons.send),
onPressed: () async {
final text = controller.text.trim();
if (text.isEmpty) return;
controller.clear();
try {
await room.sendTextEvent(text);
} catch (e) {
// Event bleibt mit Status 'error' in Timeline
// User kann auf "Erneut senden" tippen
}
},
),
],
);
}
}
```
---
## Nützliche Client-Extensions & Helpers
```dart
// User-Erwähnung formatieren
'@${userId.split(':').first.replaceFirst('@', '')}' // kurzform ohne Server
// Raum-Display-Name (FluffyChat-Stil)
// Für DMs: Name des Gegenübers
// Für Räume mit Name: Name
// Für Räume ohne Name: kommagetrennte Mitgliedernamen
room.getLocalizedDisplayname()
// Avatar-URL sicher holen
user.avatarUrl?.getThumbnail(client, width: 56, height: 56)
// Event-Zeitstempel formatieren
event.originServerTs.toLocal().toString()
// oder mit intl:
DateFormat.Hm().format(event.originServerTs.toLocal())
// Stable Notification-ID aus Raum-ID (deterministisch)
int stableId(String roomId) {
var h = 0;
for (final c in roomId.codeUnits) {
h = ((h * 31) + c) & 0x7FFFFFFF;
}
return h == 0 ? 1 : h;
}
// Matrix-Größenangabe
// event.sizeString → '2.3 MB', '512 KB'
// mxc:// URI in HTTP umwandeln
mxcUri.getDownloadUri(client)
mxcUri.getThumbnail(client, width: 300, height: 300)
```
---
## Fehler-Handling
```dart
// MatrixException — Fehler vom Server
try {
await client.joinRoom('!room:server');
} on MatrixException catch (e) {
switch (e.error) {
case MatrixError.M_FORBIDDEN: // Kein Zugang
case MatrixError.M_NOT_FOUND: // Raum existiert nicht
case MatrixError.M_LIMIT_EXCEEDED: // Rate-Limit
final retryAfter = e.retryAfterMs; // Wann wieder versuchen
case MatrixError.M_UNKNOWN:
default:
print(e.errorMessage);
}
}
// TimeoutException — Keine Antwort vom Server
// SocketException — Keine Netzwerkverbindung
// FormatException — Ungültige Server-Antwort
// Verbindungsstatus überwachen
client.onConnectionStateChanged.stream.listen((state) {
switch (state) {
case ConnectionState.connected: // Sync läuft
case ConnectionState.connecting: // Verbindet
case ConnectionState.reconnecting: // Verbindung wiederhergestellt
}
});
```
---
## Wichtige Pakete für Pyramid
```yaml
dependencies:
matrix: ^6.x.x # Matrix SDK (Haupt-SDK)
flutter_vodozemac: ^x.x.x # E2EE (Rust-basiert, empfohlen)
firebase_messaging: ^x.x.x # FCM Push (Android)
firebase_core: ^x.x.x
flutter_local_notifications: ^x.x.x # Lokale Notifications
flutter_riverpod: ^x.x.x # State Management
sqflite: ^x.x.x # Datenbank
path_provider: ^x.x.x # App-Verzeichnisse
shared_preferences: ^x.x.x # Einfache KV-Speicherung
livekit_client: ^x.x.x # LiveKit für VoIP
flutter_webrtc: ^x.x.x # WebRTC
go_router: ^x.x.x # Navigation
local_notifier: ^x.x.x # Windows Notifications
windows_taskbar: ^x.x.x # Windows Badge
```