# 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('body'); final map = event.content.tryGetMap('info'); final list = event.content.tryGetList('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('m.relates_to'); final relType = relatesTo?.tryGet('rel_type'); final eventId = relatesTo?.tryGet('event_id'); // Verschlüsselte Medien final file = event.content.tryGetMap('file'); final url = file?.tryGet('url'); // mxc:// final key = file?.tryGetMap('key'); // Mentions (MSC3952) final mentions = event.content.tryGetMap('m.mentions'); final userIds = mentions?.tryGetList('user_ids') ?? []; final roomMention = mentions?.tryGet('room') ?? false; ``` --- ## Timeline-Widget Pattern (Flutter) ```dart class ChatView extends ConsumerStatefulWidget { final String roomId; const ChatView({required this.roomId, super.key}); @override ConsumerState createState() => _ChatViewState(); } class _ChatViewState extends ConsumerState { Timeline? _timeline; @override void initState() { super.initState(); _loadTimeline(); } Future _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>((ref) async* { final client = await ref.watch(matrixClientProvider.future); yield _sortedRooms(client); await for (final _ in client.onSync.stream) { yield _sortedRooms(client); } }); List _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 ```