more intuitive permissions in encrypted rooms

This commit is contained in:
Henry Hiles 2026-10-23 12:12:20 -04:00
commit 3a39f1c5ac
Signed by: Henry-Hiles
SSH key fingerprint: SHA256:VKQUdS31Q90KvX7EkKMHMBpUspcmItAh86a+v7PGiIs
2 changed files with 39 additions and 35 deletions

View file

@ -158,10 +158,8 @@ class RoomSettingsSectionsController(final String roomId)
ClientStateController.provider.select((state) => state?.userId), ClientStateController.provider.select((state) => state?.userId),
); );
final ownLevel = userId == null ? 0.0 : roomPowerLevels.levelOf(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 encrypted = stateContent(.encryption) is EncryptionContent;
final messageType = stateContent(.encryption) is EncryptionContent final shownEvents = PowerLevelEvent.shownIn(encrypted: encrypted);
? EventType.encrypted
: EventType.message;
Future<void> setState(EventType type, Content content) async { Future<void> setState(EventType type, Content content) async {
final eventId = await client.setState( final eventId = await client.setState(
@ -220,12 +218,7 @@ class RoomSettingsSectionsController(final String roomId)
.run(() => setState(.powerLevels, content).onError(showError)); .run(() => setState(.powerLevels, content).onError(showError));
// Event types with their own level that don't have a setting of their own // Event types with their own level that don't have a setting of their own
final listedEvents = [ final listedEvents = shownEvents.map((event) => event.type.type).toISet();
messageType,
EventType.sticker,
EventType.reaction,
EventType.pinnedEvents,
].map((type) => type.type).toISet();
final otherEvents = powerLevels.events.entries final otherEvents = powerLevels.events.entries
.where((entry) => !listedEvents.contains(entry.key)) .where((entry) => !listedEvents.contains(entry.key))
.toIList() .toIList()
@ -300,19 +293,16 @@ class RoomSettingsSectionsController(final String roomId)
); );
/// A setting for the level needed to send [event], which otherwise needs [fallback]. /// A setting for the level needed to send [event], which otherwise needs [fallback].
Setting eventSetting(PowerLevelEvent event, {required int fallback}) { Setting eventSetting(PowerLevelEvent event, {required int fallback}) =>
final type = event == .message ? messageType : event.types.first; powerLevelSetting(
title: event.title,
return powerLevelSetting( description: event.description,
title: event.title, icon: event.icon,
description: event.description, value: powerLevels.events[event.type.type] ?? fallback,
icon: event.icon, update: (level) => powerLevels.copyWith(
value: powerLevels.events[type.type] ?? fallback, events: powerLevels.events.add(event.type.type, level),
update: (level) => powerLevels.copyWith( ),
events: powerLevels.events.add(type.type, level), );
),
);
}
return .new({ return .new({
"Room": .new([ "Room": .new([
@ -683,9 +673,12 @@ class RoomSettingsSectionsController(final String roomId)
"Power levels needed to send messages and other timeline events.", "Power levels needed to send messages and other timeline events.",
Icons.chat, Icons.chat,
), ),
eventSetting(.message, fallback: powerLevels.eventsDefault), ...shownEvents
eventSetting(.sticker, fallback: powerLevels.eventsDefault), .where((event) => event != .pinnedEvents)
eventSetting(.reaction, fallback: powerLevels.eventsDefault), .map(
(event) =>
eventSetting(event, fallback: powerLevels.eventsDefault),
),
permissionSetting(.eventsDefault), permissionSetting(.eventsDefault),
header( header(
"State Events", "State Events",

View file

@ -77,39 +77,50 @@ enum PowerLevelPermission(
} }
enum PowerLevelEvent( enum PowerLevelEvent(
final EventType type,
final String title, final String title,
final String description, final String description,
final IconData icon, final IconData icon,
) { ) {
message( message(
.message,
"Send Messages", "Send Messages",
"The power level needed to send messages.", "The power level needed to send messages.",
Icons.message, 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(
.sticker,
"Send Stickers", "Send Stickers",
"The power level needed to send stickers.", "The power level needed to send stickers.",
Icons.sticky_note_2, Icons.sticky_note_2,
), ),
reaction( reaction(
.reaction,
"Send Reactions", "Send Reactions",
"The power level needed to react to messages.", "The power level needed to react to messages.",
Icons.add_reaction, Icons.add_reaction,
), ),
pinnedEvents( pinnedEvents(
.pinnedEvents,
"Pin Messages", "Pin Messages",
"The power level needed to pin messages.", "The power level needed to pin messages.",
Icons.push_pin, Icons.push_pin,
); );
IList<EventType> get types => .new(switch (this) { static IList<PowerLevelEvent> shownIn({required bool encrypted}) => .new([
.message => [.message, .encrypted], if (encrypted) .encrypted else ...[.message, .sticker],
.sticker => [.sticker], .reaction,
.reaction => [.reaction], .pinnedEvents,
.pinnedEvents => [.pinnedEvents], ]);
});
static PowerLevelEvent? of(String type) => values static PowerLevelEvent? of(String type) =>
.where((event) => event.types.any((eventType) => eventType.type == type)) values.where((event) => event.type.type == type).firstOrNull;
.firstOrNull;
} }