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),
);
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<void> 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(
Setting eventSetting(PowerLevelEvent event, {required int fallback}) =>
powerLevelSetting(
title: event.title,
description: event.description,
icon: event.icon,
value: powerLevels.events[type.type] ?? fallback,
value: powerLevels.events[event.type.type] ?? fallback,
update: (level) => powerLevels.copyWith(
events: powerLevels.events.add(type.type, level),
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",

View file

@ -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<EventType> get types => .new(switch (this) {
.message => [.message, .encrypted],
.sticker => [.sticker],
.reaction => [.reaction],
.pinnedEvents => [.pinnedEvents],
});
static IList<PowerLevelEvent> 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;
}