# 02 — Räume & Spaces --- ## Inhaltsverzeichnis - [Raum erstellen](#raum-erstellen) - [Raum beitreten](#raum-beitreten) - [Raum verlassen](#raum-verlassen) - [Raumliste](#raumliste) - [Raum-Metadaten lesen](#raum-metadaten-lesen) - [Raum-Einstellungen ändern](#raum-einstellungen-ändern) - [Mitglieder](#mitglieder) - [Einladen & Kick & Ban](#einladen--kick--ban) - [Öffentliches Raumverzeichnis](#öffentliches-raumverzeichnis) - [Spaces](#spaces) - [Room Upgrades](#room-upgrades) --- ## Raum erstellen ```dart // Einfacher privater Raum final roomId = await client.createRoom( name: 'Mein Raum', topic: 'Willkommen!', visibility: Visibility.private, // oder .public preset: CreateRoomPreset.privateChat, // oder .publicChat, .trustedPrivateChat ); // Raum mit Einladungen final roomId = await client.createRoom( name: 'Projekt Alpha', invite: ['@bob:example.org', '@charlie:example.org'], enableEncryption: true, // E2EE aktivieren ); // Raum mit Alias final roomId = await client.createRoom( name: 'Öffentlicher Kanal', roomAliasName: 'kanal', // → #kanal:example.org visibility: Visibility.public, preset: CreateRoomPreset.publicChat, ); // Raum mit Custom Power Levels final roomId = await client.createRoom( name: 'Moderiert', powerLevelContentOverride: PowerLevelsContent( eventsDefault: 50, // Nur Moderatoren senden usersDefault: 0, users: {'@admin:example.org': 100}, ), ); // Raum mit History-Visibility final roomId = await client.createRoom( initialState: [ StateEvent( type: EventTypes.HistoryVisibility, content: {'history_visibility': 'invited'}, // shared|invited|joined|world_readable ), ], ); ``` --- ## Raum beitreten ```dart // Per Raum-ID await client.joinRoom('!roomId:server'); // Per Alias await client.joinRoomOrAlias('#kanal:example.org'); // Per Invite-Link / Matrix-URI // matrix:r/kanal:example.org // https://matrix.to/#/#kanal:example.org final uri = MatrixUri.parse('https://matrix.to/#/#kanal:example.org'); if (uri != null) { await client.joinRoomOrAlias(uri.primaryIdentifier); } // Mit Via-Servern (für föderierte Räume) await client.joinRoom( '!roomId:remoteserver.org', serverName: ['remoteserver.org', 'matrix.org'], ); ``` --- ## Raum verlassen ```dart // Verlassen (bleibt in Left-State) final room = client.getRoomById('!roomId:server')!; await room.leave(); // Vergessen (entfernt aus Raumliste) await room.forget(); // Verlassen + Vergessen await room.leave(); await room.forget(); ``` --- ## Raumliste ```dart // Alle Räume (joined + invited) final allRooms = client.rooms; // Nur beigetretene final joinedRooms = client.rooms .where((r) => r.membership == Membership.join) .toList(); // Nur Einladungen final invites = client.rooms .where((r) => r.membership == Membership.invite) .toList(); // Sortiert nach letzter Aktivität final sorted = [...client.rooms] ..sort((a, b) => (b.lastEvent?.originServerTs ?? DateTime(0)) .compareTo(a.lastEvent?.originServerTs ?? DateTime(0))); // Raum per ID final room = client.getRoomById('!roomId:server'); // Raum per Alias auflösen final resolved = await client.getRoomIdByAlias('#alias:server'); // resolved.roomId // Ungelesene Nachrichten final unread = client.rooms.fold( 0, (sum, r) => sum + r.notificationCount); ``` --- ## Raum-Metadaten lesen ```dart final room = client.getRoomById('!roomId:server')!; // Basis-Info print(room.id); // !roomId:server print(room.name); // Anzeigename (aufgelöst) print(room.topic); // Thema print(room.avatar); // mxc://… URI print(room.canonicalAlias); // #alias:server print(room.membership); // Membership.join/invite/leave/ban print(room.isDirectChat); // DM? print(room.directChatMatrixID); // Gegenüber-MXID bei DM print(room.isEncrypted); // E2EE aktiv? print(room.joinRules); // JoinRules.public/invite/knock/restricted print(room.guestAccess); // GuestAccess.forbidden/canJoin print(room.historyVisibility); // HistoryVisibility.* print(room.notificationCount); // Ungelesene Notifications print(room.highlightCount); // Mentions print(room.summary.mJoinedMemberCount); // Mitgliederzahl // Letztes Event final last = room.lastEvent; print(last?.body); print(last?.originServerTs); // Display-Name (lokalisiert, wie FluffyChat) final displayName = room.getLocalizedDisplayname(); ``` --- ## Raum-Einstellungen ändern ```dart final room = client.getRoomById('!roomId:server')!; // Name await room.setName('Neuer Name'); // Thema await room.setDescription('Neues Thema'); // Avatar (mxc:// URI nach Upload) final uri = await client.uploadContent(imageBytes, filename: 'avatar.png'); await room.setAvatar(uri); // Alias setzen / entfernen await client.setRoomAlias('#neu:example.org', room.id); await client.deleteRoomAlias('#alt:example.org'); await room.setCanonicalAlias('#neu:example.org'); // Kanonischen Alias setzen // Join-Rules await room.setJoinRules(JoinRules.invite); // private await room.setJoinRules(JoinRules.public); // öffentlich await room.setJoinRules(JoinRules.knock); // Beitrittsanfragen await room.setJoinRules( JoinRules.restricted, allow: [SpaceRoomJoinRulesAllowInner( roomId: '!spaceId:server', type: SpaceRoomJoinRulesAllowInnerType.mRoomMembership, )], ); // History-Visibility await room.setHistoryVisibility(HistoryVisibility.shared); // .shared | .invited | .joined | .worldReadable // Guest Access await room.setGuestAccess(GuestAccess.canJoin); await room.setGuestAccess(GuestAccess.forbidden); // E2EE aktivieren (nicht mehr deaktivierbar!) await room.enableEncryption(); // Generischer State-Event senden await room.client.sendState( room.id, 'm.room.custom_event', {'key': 'value'}, stateKey: '', ); ``` --- ## Mitglieder ```dart final room = client.getRoomById('!roomId:server')!; // Alle Mitglieder (gecacht) final members = room.getParticipants(); // Vollständige Mitgliederliste vom Server laden final allMembers = await room.requestParticipants(); // Einzelnen User holen final user = room.unsafeGetUserFromMemoryOrFallback('@alice:server'); print(user.displayName); print(user.avatarUrl); print(user.powerLevel); print(user.membership); // Membership.join/invite/leave/ban/knock // Aktuell tippende User final typingUsers = room.typingUsers; // List // Power Level eines Users final pl = room.getPowerLevelByUserId('@alice:server'); // Eigener Power Level final myPl = room.ownPowerLevel; ``` --- ## Einladen, Kick & Ban ```dart final room = client.getRoomById('!roomId:server')!; // Einladen await room.invite('@alice:server'); // Kick (entfernt, kann wieder beitreten) await room.kick('@alice:server', reason: 'Regeln verletzt'); // Ban (dauerhaft gesperrt) await room.ban('@alice:server', reason: 'Spam'); // Unban await room.unban('@alice:server'); // Eigene Berechtigungen prüfen room.canBan; // bool room.canKick; // bool room.canInvite; // bool room.canSendEvent(EventTypes.Message); // bool ``` --- ## Power Levels ```dart final room = client.getRoomById('!roomId:server')!; // Power Level eines Users setzen await room.setPower('@alice:server', 50); // 0=User, 50=Mod, 100=Admin // Power Level Content auslesen final plContent = room.getState(EventTypes.RoomPowerLevels) ?.content .tryGet('users'); // Vollständig setzen await room.client.sendState( room.id, EventTypes.RoomPowerLevels, { 'ban': 50, 'events': { 'm.room.name': 50, 'm.room.power_levels': 100, }, 'events_default': 0, 'invite': 50, 'kick': 50, 'redact': 50, 'state_default': 50, 'users': { '@alice:server': 100, '@mod:server': 50, }, 'users_default': 0, }, stateKey: '', ); // Berechtigungen prüfen room.canSendDefaultMessages; // Nachrichten senden room.canChangeStateEvent(EventTypes.RoomName); // State-Events ändern ``` --- ## Öffentliches Raumverzeichnis ```dart // Öffentliche Räume suchen final result = await client.queryPublicRooms( limit: 20, filter: PublicRoomQueryFilter(genericSearchTerm: 'flutter'), server: 'matrix.org', // optionaler anderer Server ); for (final room in result.chunk) { print(room.name); print(room.roomId); print(room.numJoinedMembers); print(room.worldReadable); } // Nächste Seite final next = await client.queryPublicRooms( since: result.nextBatch, limit: 20, ); ``` --- ## Spaces ```dart // Space erstellen final spaceId = await client.createRoom( name: 'Mein Space', creationContent: {'type': 'm.space'}, // markiert als Space visibility: Visibility.private, ); // Kind-Raum zu Space hinzufügen final space = client.getRoomById(spaceId)!; await space.client.sendState( spaceId, EventTypes.SpaceChild, { 'via': ['example.org'], 'suggested': true, 'auto_join': false, }, stateKey: '!childRoomId:server', ); // Space-Kinder auflisten (vom Server) final children = await client.getSpaceHierarchy( spaceId, maxDepth: 2, suggestedOnly: false, ); for (final room in children.rooms) { print(room.name); print(room.roomType); // 'm.space' oder null print(room.childrenState); // Sub-Spaces } // Kind-Raum entfernen await space.client.sendState( spaceId, EventTypes.SpaceChild, {}, // leeres Content → entfernt stateKey: '!childRoomId:server', ); // Space-Parent setzen (vom Kind-Raum aus) await client.sendState( '!childRoomId:server', EventTypes.SpaceParent, { 'via': ['example.org'], 'canonical': true, }, stateKey: spaceId, ); // Alle Spaces des Users final spaces = client.rooms.where((r) => r.membership == Membership.join && r.getState('m.room.create')?.content['type'] == 'm.space'); ``` --- ## Room Upgrades ```dart // Raum auf neuere Version upgraden (erstellt Nachfolger-Raum) final newRoomId = await client.upgradeRoom( '!alterRaumId:server', newVersion: '11', // Aktuelle empfohlene Version ); // Alle Mitglieder werden automatisch in den neuen Raum eingeladen // tombstone-Event wird im alten Raum gesetzt // Tombstone lesen final tombstone = room.getState(EventTypes.RoomTombstone); final replacement = tombstone?.content['replacement_room'] as String?; ```