From 3a39f1c5ac2aecd61f4f8b6d95dee60616edccff Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Fri, 23 Oct 2026 12:12:20 -0400 Subject: [PATCH] more intuitive permissions in encrypted rooms --- lib/controllers/room_settings_sections.dart | 45 +++++++++------------ lib/models/power_level_permission.dart | 29 ++++++++----- 2 files changed, 39 insertions(+), 35 deletions(-) diff --git a/lib/controllers/room_settings_sections.dart b/lib/controllers/room_settings_sections.dart index 42da22c..8ce08f8 100644 --- a/lib/controllers/room_settings_sections.dart +++ b/lib/controllers/room_settings_sections.dart @@ -158,10 +158,8 @@ class RoomSettingsSectionsController(final String roomId) ClientStateController.provider.select((state) => state?.userId), ); final ownLevel = userId == null ? 0.0 : roomPowerLevels.levelOf(userId); - // Encrypted rooms send messages as m.room.encrypted, so that's what limits them - final messageType = stateContent(.encryption) is EncryptionContent - ? EventType.encrypted - : EventType.message; + final encrypted = stateContent(.encryption) is EncryptionContent; + final shownEvents = PowerLevelEvent.shownIn(encrypted: encrypted); Future setState(EventType type, Content content) async { final eventId = await client.setState( @@ -220,12 +218,7 @@ class RoomSettingsSectionsController(final String roomId) .run(() => setState(.powerLevels, content).onError(showError)); // Event types with their own level that don't have a setting of their own - final listedEvents = [ - messageType, - EventType.sticker, - EventType.reaction, - EventType.pinnedEvents, - ].map((type) => type.type).toISet(); + final listedEvents = shownEvents.map((event) => event.type.type).toISet(); final otherEvents = powerLevels.events.entries .where((entry) => !listedEvents.contains(entry.key)) .toIList() @@ -300,19 +293,16 @@ class RoomSettingsSectionsController(final String roomId) ); /// A setting for the level needed to send [event], which otherwise needs [fallback]. - Setting eventSetting(PowerLevelEvent event, {required int fallback}) { - final type = event == .message ? messageType : event.types.first; - - return powerLevelSetting( - title: event.title, - description: event.description, - icon: event.icon, - value: powerLevels.events[type.type] ?? fallback, - update: (level) => powerLevels.copyWith( - events: powerLevels.events.add(type.type, level), - ), - ); - } + Setting eventSetting(PowerLevelEvent event, {required int fallback}) => + powerLevelSetting( + title: event.title, + description: event.description, + icon: event.icon, + value: powerLevels.events[event.type.type] ?? fallback, + update: (level) => powerLevels.copyWith( + events: powerLevels.events.add(event.type.type, level), + ), + ); return .new({ "Room": .new([ @@ -683,9 +673,12 @@ class RoomSettingsSectionsController(final String roomId) "Power levels needed to send messages and other timeline events.", Icons.chat, ), - eventSetting(.message, fallback: powerLevels.eventsDefault), - eventSetting(.sticker, fallback: powerLevels.eventsDefault), - eventSetting(.reaction, fallback: powerLevels.eventsDefault), + ...shownEvents + .where((event) => event != .pinnedEvents) + .map( + (event) => + eventSetting(event, fallback: powerLevels.eventsDefault), + ), permissionSetting(.eventsDefault), header( "State Events", diff --git a/lib/models/power_level_permission.dart b/lib/models/power_level_permission.dart index 8abff82..355623b 100644 --- a/lib/models/power_level_permission.dart +++ b/lib/models/power_level_permission.dart @@ -77,39 +77,50 @@ enum PowerLevelPermission( } enum PowerLevelEvent( + final EventType type, final String title, final String description, final IconData icon, ) { message( + .message, "Send Messages", "The power level needed to send messages.", Icons.message, ), + // Everything a client encrypts is sent as this, so its level is what limits + // messages, stickers, and the like in encrypted rooms + encrypted( + .encrypted, + "Send Encrypted Events", + "The power level needed to send messages, stickers, files and anything else encrypted.", + Icons.lock, + ), sticker( + .sticker, "Send Stickers", "The power level needed to send stickers.", Icons.sticky_note_2, ), reaction( + .reaction, "Send Reactions", "The power level needed to react to messages.", Icons.add_reaction, ), pinnedEvents( + .pinnedEvents, "Pin Messages", "The power level needed to pin messages.", Icons.push_pin, ); - IList get types => .new(switch (this) { - .message => [.message, .encrypted], - .sticker => [.sticker], - .reaction => [.reaction], - .pinnedEvents => [.pinnedEvents], - }); + static IList shownIn({required bool encrypted}) => .new([ + if (encrypted) .encrypted else ...[.message, .sticker], + .reaction, + .pinnedEvents, + ]); - static PowerLevelEvent? of(String type) => values - .where((event) => event.types.any((eventType) => eventType.type == type)) - .firstOrNull; + static PowerLevelEvent? of(String type) => + values.where((event) => event.type.type == type).firstOrNull; }