add per-room notif overrides

Fixes #163
This commit is contained in:
Henry Hiles 2026-11-09 15:40:17 -05:00
commit 8aada9a895
Signed by: Henry-Hiles
SSH key fingerprint: SHA256:VKQUdS31Q90KvX7EkKMHMBpUspcmItAh86a+v7PGiIs
2 changed files with 137 additions and 3 deletions

View file

@ -15,18 +15,20 @@ class const PushRules({final PushRuleset global = const .new()})
_$PushRulesFromJson(json);
}
/// Push rules by kind, apart from room and sender rules, which Nexus doesn't
/// use. The first rule that matches decides what happens.
/// Push rules by kind, apart from sender rules, which Nexus doesn't use. The
/// first rule that matches decides what happens.
@Freezed(toJson: false, fromJson: false)
@JsonSerializable()
class const PushRuleset({
final IList<PushRule> override = const .empty(),
final IList<PushRule> content = const .empty(),
final IList<PushRule> room = const .empty(),
final IList<PushRule> underride = const .empty(),
}) with _$PushRuleset {
IList<PushRule> ofKind(PushRuleKind kind) => switch (kind) {
.override => override,
.content => content,
.room => room,
.underride => underride,
};
@ -46,7 +48,7 @@ class const PushRuleset({
_$PushRulesetFromJson(json);
}
enum PushRuleKind { override, content, underride }
enum PushRuleKind { override, content, room, underride }
@Freezed(toJson: false, fromJson: false)
@JsonSerializable()
@ -60,6 +62,9 @@ class const PushRule({
/// What content rules match in messages' bodies.
final String? pattern,
/// What override and underride rules match.
final IList<PushCondition> conditions = const .empty(),
}) with _$PushRule {
Map<String, Object?> toJson() => _$PushRuleToJson(this);
@ -67,6 +72,24 @@ class const PushRule({
_$PushRuleFromJson(json);
}
/// Only the fields of event_match conditions are kept.
@Freezed(toJson: false, fromJson: false)
@JsonSerializable()
class const PushCondition({
required final String kind,
final String? key,
final String? pattern,
}) with _$PushCondition {
/// Matches events in the room with [roomId].
factory PushCondition.inRoom(String roomId) =>
.new(kind: "event_match", key: "room_id", pattern: roomId);
Map<String, Object?> toJson() => _$PushConditionToJson(this);
factory PushCondition.fromJson(Map<String, Object?> json) =>
_$PushConditionFromJson(json);
}
/// What happens when a push rule matches. Actions Nexus doesn't support are
/// dropped, so they're replaced when these are changed.
@Freezed(toJson: false, fromJson: false)

View file

@ -0,0 +1,49 @@
import "package:material_ui/material_ui.dart";
import "package:nexus/models/push_rules.dart";
enum RoomNotificationLevel(
final String title,
final String description,
final IconData icon,
) {
global(
"Default",
"Follows your global notification rules.",
Icons.notifications,
),
allMessages(
"All Messages",
"Every message notifies you.",
Icons.notifications_active,
),
mentionsAndKeywords(
"Mentions & Keywords",
"Only messages that mention you or contain your keywords notify you.",
Icons.alternate_email,
),
mute("Mute", "Nothing in this room notifies you.", Icons.notifications_off);
static RoomNotificationLevel of(PushRuleset ruleset, String roomId) =>
switch ((ruleset.find(.override, roomId), ruleset.find(.room, roomId))) {
(PushRule(enabled: true, actions: PushActions(notify: false)), _) =>
.mute,
(_, PushRule(enabled: true, actions: PushActions(notify: true))) =>
.allMessages,
(_, PushRule(enabled: true)) => .mentionsAndKeywords,
_ => .global,
};
({PushRuleKind kind, PushRule rule})? ruleFor(String roomId) =>
switch (this) {
.global => null,
.allMessages => (
kind: .room,
rule: .new(ruleId: roomId, actions: .notifyWithSound),
),
.mentionsAndKeywords => (kind: .room, rule: .new(ruleId: roomId)),
.mute => (
kind: .override,
rule: .new(ruleId: roomId, conditions: .new([.inRoom(roomId)])),
),
};
}