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

10 KiB

12 — Schnellreferenz: Typen, Konstanten & Patterns


Wichtige Enum-Werte

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

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

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

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

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

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

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

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

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