Fixes to power level controller

This commit is contained in:
Henry Hiles 2026-05-16 21:21:33 -04:00
commit ad14f2207e
Signed by: Henry-Hiles
SSH key fingerprint: SHA256:VKQUdS31Q90KvX7EkKMHMBpUspcmItAh86a+v7PGiIs
10 changed files with 69 additions and 101 deletions

View file

@ -26,37 +26,32 @@ class PowerLevelController extends Notifier<bool> {
content.users[userId] ?? content.usersDefault;
final userLevel = powerLevelOf(user);
final targetLevel = config.targetUser != null
? powerLevelOf(config.targetUser!)
: null;
if (config.action != null) {
return switch (config.action!) {
MembershipAction.invite => userLevel >= content.invite,
return switch (config) {
EventPowerLevelConfig(:final eventType) =>
userLevel > (content.events[eventType.type] ?? content.eventsDefault),
MembershipActionPowerLevelConfig(:final action, :final targetUser) =>
switch (action) {
MembershipAction.invite => userLevel >= content.invite,
MembershipAction.kick =>
targetLevel != null &&
userLevel >= content.kick &&
userLevel > targetLevel,
MembershipAction.kick =>
userLevel >= content.kick && userLevel > powerLevelOf(targetUser),
MembershipAction.ban =>
targetLevel != null &&
userLevel >= content.ban &&
userLevel > targetLevel,
MembershipAction.ban =>
userLevel >= content.ban && userLevel > powerLevelOf(targetUser),
MembershipAction.unban => userLevel >= content.ban,
};
}
MembershipAction.unban => userLevel >= content.ban,
},
if (config.eventType == "m.room.redaction") {
return userLevel >= content.redact;
}
final requiredLevel =
content.events[config.eventType] ??
(config.isStateEvent ? content.stateDefault : content.eventsDefault);
return userLevel >= requiredLevel;
StatePowerLevelConfig(:final eventType) =>
userLevel > (content.events[eventType.type] ?? content.stateDefault),
RedactPowerLevelConfig(:final targetUser) =>
userLevel >=
(targetUser == user
? (content.events[EventType.redaction.type] ??
content.eventsDefault)
: content.redact),
};
}
static final provider = NotifierProvider.autoDispose

View file

@ -1,28 +0,0 @@
import "package:freezed_annotation/freezed_annotation.dart";
import "package:nexus/models/event.dart";
import "package:nexus/models/room.dart";
part "message_config.freezed.dart";
part "message_config.g.dart";
@freezed
abstract class MessageConfig with _$MessageConfig {
const MessageConfig._();
const factory MessageConfig({
@Default(false) bool alwaysReturn,
@Default(false) bool includeEdits,
required Room room,
required Event event,
}) = _MessageConfig;
@override
bool operator ==(Object other) =>
other.runtimeType == runtimeType &&
other is MessageConfig &&
other.event == event;
@override
int get hashCode => Object.hash(runtimeType, event);
factory MessageConfig.fromJson(Map<String, Object?> json) =>
_$MessageConfigFromJson(json);
}

View file

@ -1,17 +0,0 @@
import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:freezed_annotation/freezed_annotation.dart";
import "package:nexus/models/event.dart";
import "package:nexus/models/room.dart";
part "messages_config.freezed.dart";
part "messages_config.g.dart";
@freezed
abstract class MessagesConfig with _$MessagesConfig {
const factory MessagesConfig({
required Room room,
required IList<Event> events,
}) = _MessagesConfig;
factory MessagesConfig.fromJson(Map<String, Object?> json) =>
_$MessagesConfigFromJson(json);
}

View file

@ -1,17 +1,21 @@
import "package:freezed_annotation/freezed_annotation.dart";
import "package:nexus/models/content/content.dart";
import "package:nexus/models/requests/membership_action.dart";
part "power_level_config.freezed.dart";
part "power_level_config.g.dart";
@freezed
abstract class PowerLevelConfig with _$PowerLevelConfig {
const factory PowerLevelConfig({
@Default(false) bool isStateEvent,
required String eventType,
MembershipAction? action,
String? targetUser,
}) = _PowerLevelConfig;
sealed class PowerLevelConfig with _$PowerLevelConfig {
const factory PowerLevelConfig({required EventType eventType}) =
EventPowerLevelConfig;
factory PowerLevelConfig.fromJson(Map<String, Object?> json) =>
_$PowerLevelConfigFromJson(json);
const factory PowerLevelConfig.membershipAction({
required MembershipAction action,
required String targetUser,
}) = MembershipActionPowerLevelConfig;
const factory PowerLevelConfig.state({required EventType eventType}) =
StatePowerLevelConfig;
const factory PowerLevelConfig.redact({required String targetUser}) =
RedactPowerLevelConfig;
}

View file

@ -11,6 +11,7 @@ import "package:nexus/models/content/name.dart";
import "package:nexus/models/content/pinned_events.dart";
import "package:nexus/models/content/power_levels.dart";
import "package:nexus/models/content/reaction.dart";
import "package:nexus/models/content/redaction.dart";
import "package:nexus/models/content/server_acl.dart";
import "package:nexus/models/content/topic.dart";
@ -30,6 +31,7 @@ class Content {
@JsonEnum(valueField: "type")
enum EventType {
encrypted("m.room.encrypted", Content.fromJson),
redaction("m.room.redaction", RedactionContent.fromJson),
encryption("m.room.encryption", EncryptionContent.fromJson),
membership("m.room.member", MembershipContent.fromJson),
create("m.room.create", CreateContent.fromJson),

View file

@ -14,7 +14,7 @@ abstract class MessageContent extends Content with _$MessageContent {
required String body,
String? format,
String? formattedBody,
}) = _TextMessageContent;
}) = TextMessageContent;
@FreezedUnionValue("m.image")
const factory MessageContent.image({
@ -25,7 +25,7 @@ abstract class MessageContent extends Content with _$MessageContent {
String? filename,
ImageInfo? info,
String? url,
}) = _ImageMessageContent;
}) = ImageMessageContent;
@FreezedUnionValue("m.file")
const factory MessageContent.file({
@ -36,7 +36,7 @@ abstract class MessageContent extends Content with _$MessageContent {
String? filename,
FileInfo? info,
String? url,
}) = _FileMessageContent;
}) = FileMessageContent;
@FreezedUnionValue("m.audio")
const factory MessageContent.audio({
@ -47,7 +47,7 @@ abstract class MessageContent extends Content with _$MessageContent {
String? filename,
AudioInfo? info,
String? url,
}) = _AudioMessageContent;
}) = AudioMessageContent;
@FreezedUnionValue("m.video")
const factory MessageContent.video({
@ -58,13 +58,13 @@ abstract class MessageContent extends Content with _$MessageContent {
String? filename,
AudioInfo? info,
String? url,
}) = _AudioMessageContent;
}) = AudioMessageContent;
@FreezedUnionValue("m.location")
const factory MessageContent.location({
required String body,
required Uri geoUri,
}) = _LocationMessageContent;
}) = LocationMessageContent;
factory MessageContent.fromJson(Map<String, Object?> json) =>
_$MessageContentFromJson(json);

View file

@ -0,0 +1,14 @@
import "package:freezed_annotation/freezed_annotation.dart";
import "package:nexus/models/content/content.dart";
part "redaction.freezed.dart";
part "redaction.g.dart";
@freezed
abstract class RedactionContent extends Content with _$RedactionContent {
RedactionContent._();
const factory RedactionContent({String? reason, String? redacts}) =
_RedactionContent;
factory RedactionContent.fromJson(Map<String, Object?> json) =>
_$RedactionContentFromJson(json);
}

View file

@ -5,6 +5,7 @@ import "package:fluttertagger/fluttertagger.dart";
import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:nexus/controllers/power_level_controller.dart";
import "package:nexus/models/configs/power_level_config.dart";
import "package:nexus/models/content/content.dart";
import "package:nexus/models/event.dart";
import "package:nexus/models/relation_type.dart";
import "package:nexus/widgets/chat_page/composer/mention_overlay.dart";
@ -87,7 +88,7 @@ class ChatBox extends HookConsumerWidget {
children:
ref.watch(
PowerLevelController.provider(
PowerLevelConfig(eventType: "m.room.message"),
PowerLevelConfig(eventType: EventType.message),
),
)
? [

View file

@ -13,6 +13,7 @@ import "package:nexus/controllers/via_controller.dart";
import "package:nexus/helpers/extensions/better_when.dart";
import "package:nexus/helpers/extensions/show_context_menu.dart";
import "package:nexus/models/configs/power_level_config.dart";
import "package:nexus/models/content/content.dart";
import "package:nexus/models/relation_type.dart";
import "package:nexus/models/requests/report_request.dart";
import "package:nexus/widgets/chat_page/composer/chat_box.dart";
@ -85,7 +86,7 @@ class RoomChat extends HookConsumerWidget {
return [
if (ref.watch(
PowerLevelController.provider(
PowerLevelConfig(eventType: "m.reaction"),
PowerLevelConfig(eventType: EventType.reaction),
),
))
PopupMenuItem(
@ -129,7 +130,7 @@ class RoomChat extends HookConsumerWidget {
),
if (ref.watch(
PowerLevelController.provider(
PowerLevelConfig(eventType: "m.room.message"),
PowerLevelConfig(eventType: EventType.message),
),
))
PopupMenuItem(
@ -167,7 +168,7 @@ class RoomChat extends HookConsumerWidget {
),
if (ref.watch(
PowerLevelController.provider(
PowerLevelConfig(eventType: "m.room.redaction"),
PowerLevelConfig.redact(targetUser: message.authorId),
),
))
PopupMenuItem(

View file

@ -152,10 +152,8 @@ class UserPopover extends ConsumerWidget {
if (ref.watch(
PowerLevelController.provider(
PowerLevelConfig(
eventType: "m.room.member",
PowerLevelConfig.membershipAction(
action: MembershipAction.kick,
isStateEvent: true,
targetUser: userId,
),
),
@ -176,10 +174,8 @@ class UserPopover extends ConsumerWidget {
),
if (ref.watch(
PowerLevelController.provider(
PowerLevelConfig(
eventType: "m.room.member",
PowerLevelConfig.membershipAction(
action: MembershipAction.ban,
isStateEvent: true,
targetUser: userId,
),
),