parent
cb983701ca
commit
c9172e92eb
4 changed files with 596 additions and 15 deletions
|
|
@ -1,5 +1,6 @@
|
|||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||
import "package:freezed_annotation/freezed_annotation.dart";
|
||||
import "package:nexus/models/push_rules.dart";
|
||||
|
||||
part "account_data.freezed.dart";
|
||||
part "account_data.g.dart";
|
||||
|
|
@ -22,12 +23,15 @@ class const AccountData({
|
|||
final ImagePackRooms? imagePackRooms,
|
||||
@JsonKey(name: AccountData.legacyImagePackRoomsKey)
|
||||
final ImagePackRooms? legacyImagePackRooms,
|
||||
@JsonKey(name: AccountData.pushRulesKey)
|
||||
final PushRules pushRules = const .new(),
|
||||
}) with _$AccountData {
|
||||
static const invitePermissionConfigKey = "m.invite_permission_config";
|
||||
static const directKey = "m.direct";
|
||||
static const recentEmojiKey = "m.recent_emoji";
|
||||
static const imagePackRoomsKey = "m.image_pack.rooms";
|
||||
static const legacyImagePackRoomsKey = "im.ponies.emote_rooms";
|
||||
static const pushRulesKey = "m.push_rules";
|
||||
|
||||
ImagePackRooms get enabledImagePacks =>
|
||||
imagePackRooms ?? legacyImagePackRooms ?? const .new();
|
||||
|
|
|
|||
150
lib/models/push_rule_switch.dart
Normal file
150
lib/models/push_rule_switch.dart
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||
import "package:material_ui/material_ui.dart";
|
||||
import "package:nexus/models/push_rules.dart";
|
||||
|
||||
enum PushRuleSection(
|
||||
final String title,
|
||||
final String description,
|
||||
final IconData icon,
|
||||
) {
|
||||
messages("Messages", "Which messages notify you.", Icons.chat),
|
||||
otherEvents("Other Events", "Events other than messages.", Icons.more_horiz),
|
||||
}
|
||||
|
||||
/// Default push rules turned on or off together, as some match the same events
|
||||
/// in different ways, like encrypted ones, which the server can't read.
|
||||
enum PushRuleSwitch(
|
||||
final PushRuleSection section,
|
||||
final String title,
|
||||
final String description,
|
||||
final IconData icon,
|
||||
|
||||
/// What the rules do when on.
|
||||
final PushActions actions,
|
||||
|
||||
/// Whether turning it off disables the rules, so the next rule that matches
|
||||
/// decides, rather than not notifying.
|
||||
final bool disableWhenOff,
|
||||
) {
|
||||
mentions(
|
||||
.messages,
|
||||
"Mentions",
|
||||
"Messages that mention you, or contain your name.",
|
||||
Icons.alternate_email,
|
||||
.highlightWithSound,
|
||||
true,
|
||||
),
|
||||
roomMentions(
|
||||
.messages,
|
||||
"Room Mentions",
|
||||
"Messages that notify everyone in the room with @room.",
|
||||
Icons.campaign,
|
||||
.highlightWithSound,
|
||||
true,
|
||||
),
|
||||
directMessages(
|
||||
.messages,
|
||||
"Direct Messages",
|
||||
"Messages in rooms with just you and one other person.",
|
||||
Icons.person,
|
||||
.notifyWithSound,
|
||||
false,
|
||||
),
|
||||
groupMessages(
|
||||
.messages,
|
||||
"Group Messages",
|
||||
"Messages in rooms with more people, rather than only mentions and keywords.",
|
||||
Icons.group,
|
||||
.notifyWithSound,
|
||||
false,
|
||||
),
|
||||
invites(
|
||||
.otherEvents,
|
||||
"Invites",
|
||||
"Invites for you to join rooms.",
|
||||
Icons.mail,
|
||||
.notifyWithSound,
|
||||
false,
|
||||
),
|
||||
calls(
|
||||
.otherEvents,
|
||||
"Calls",
|
||||
"Incoming calls.",
|
||||
Icons.call,
|
||||
.new(notify: true, sound: "ring"),
|
||||
false,
|
||||
),
|
||||
tombstones(
|
||||
.otherEvents,
|
||||
"Room Tombstones",
|
||||
"Rooms being upgraded or locked.",
|
||||
Icons.lock,
|
||||
.notifyWithSound,
|
||||
false,
|
||||
);
|
||||
|
||||
IList<({PushRuleKind kind, String ruleId})> get rules => .new(switch (this) {
|
||||
.mentions => [
|
||||
(kind: .override, ruleId: ".m.rule.is_user_mention"),
|
||||
(kind: .override, ruleId: ".m.rule.contains_display_name"),
|
||||
(kind: .content, ruleId: ".m.rule.contains_user_name"),
|
||||
],
|
||||
.roomMentions => [
|
||||
(kind: .override, ruleId: ".m.rule.is_room_mention"),
|
||||
(kind: .override, ruleId: ".m.rule.roomnotif"),
|
||||
],
|
||||
.directMessages => [
|
||||
(kind: .underride, ruleId: ".m.rule.room_one_to_one"),
|
||||
(kind: .underride, ruleId: ".m.rule.encrypted_room_one_to_one"),
|
||||
],
|
||||
.groupMessages => [
|
||||
(kind: .underride, ruleId: ".m.rule.message"),
|
||||
(kind: .underride, ruleId: ".m.rule.encrypted"),
|
||||
],
|
||||
.invites => [(kind: .override, ruleId: ".m.rule.invite_for_me")],
|
||||
.calls => [(kind: .underride, ruleId: ".m.rule.call")],
|
||||
.tombstones => [(kind: .override, ruleId: ".m.rule.tombstone")],
|
||||
});
|
||||
|
||||
/// The rules in [ruleset], as servers may not have them all.
|
||||
IList<({PushRuleKind kind, PushRule rule})> rulesIn(PushRuleset ruleset) =>
|
||||
rules
|
||||
.map(
|
||||
(rule) => switch (ruleset.find(rule.kind, rule.ruleId)) {
|
||||
final found? => (kind: rule.kind, rule: found),
|
||||
null => null,
|
||||
},
|
||||
)
|
||||
.nonNulls
|
||||
.toIList();
|
||||
|
||||
/// Whether the rules in [ruleset] are on or off, or null if they're set up
|
||||
/// some other way.
|
||||
bool? valueIn(PushRuleset ruleset) => switch (rulesIn(ruleset)
|
||||
.map((entry) => entry.rule)) {
|
||||
final rules
|
||||
when rules.every((rule) => rule.enabled && rule.actions == actions) =>
|
||||
true,
|
||||
final rules
|
||||
when rules.every((rule) => !rule.enabled || !rule.actions.notify) =>
|
||||
false,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
/// The rules in [ruleset], turned on or off.
|
||||
IList<({PushRuleKind kind, PushRule rule})> changesIn(
|
||||
PushRuleset ruleset,
|
||||
bool value,
|
||||
) => rulesIn(ruleset)
|
||||
.map(
|
||||
(entry) => (
|
||||
kind: entry.kind,
|
||||
rule: value
|
||||
? entry.rule.copyWith(enabled: true, actions: actions)
|
||||
: disableWhenOff
|
||||
? entry.rule.copyWith(enabled: false)
|
||||
: entry.rule.copyWith(enabled: true, actions: const .new()),
|
||||
),
|
||||
)
|
||||
.toIList();
|
||||
}
|
||||
105
lib/models/push_rules.dart
Normal file
105
lib/models/push_rules.dart
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||
import "package:freezed_annotation/freezed_annotation.dart";
|
||||
|
||||
part "push_rules.freezed.dart";
|
||||
part "push_rules.g.dart";
|
||||
|
||||
/// The content of the m.push_rules account data.
|
||||
@Freezed(toJson: false, fromJson: false)
|
||||
@JsonSerializable()
|
||||
class const PushRules({final PushRuleset global = const .new()})
|
||||
with _$PushRules {
|
||||
Map<String, Object?> toJson() => _$PushRulesToJson(this);
|
||||
|
||||
factory PushRules.fromJson(Map<String, Object?> json) =>
|
||||
_$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.
|
||||
@Freezed(toJson: false, fromJson: false)
|
||||
@JsonSerializable()
|
||||
class const PushRuleset({
|
||||
final IList<PushRule> override = const .empty(),
|
||||
final IList<PushRule> content = const .empty(),
|
||||
final IList<PushRule> underride = const .empty(),
|
||||
}) with _$PushRuleset {
|
||||
IList<PushRule> ofKind(PushRuleKind kind) => switch (kind) {
|
||||
.override => override,
|
||||
.content => content,
|
||||
.underride => underride,
|
||||
};
|
||||
|
||||
PushRule? find(PushRuleKind kind, String ruleId) =>
|
||||
ofKind(kind).where((rule) => rule.ruleId == ruleId).firstOrNull;
|
||||
|
||||
/// Mutes everything when enabled, as it matches every event.
|
||||
PushRule? get master => find(.override, ".m.rule.master");
|
||||
|
||||
/// The content rules the user added, which match these words.
|
||||
IList<PushRule> get keywords =>
|
||||
content.where((rule) => !rule.isDefault).toIList();
|
||||
|
||||
Map<String, Object?> toJson() => _$PushRulesetToJson(this);
|
||||
|
||||
factory PushRuleset.fromJson(Map<String, Object?> json) =>
|
||||
_$PushRulesetFromJson(json);
|
||||
}
|
||||
|
||||
enum PushRuleKind { override, content, underride }
|
||||
|
||||
@Freezed(toJson: false, fromJson: false)
|
||||
@JsonSerializable()
|
||||
class const PushRule({
|
||||
required final String ruleId,
|
||||
final PushActions actions = const .new(),
|
||||
|
||||
/// Whether the server made the rule, so it can't be deleted.
|
||||
@JsonKey(name: "default") final bool isDefault = false,
|
||||
final bool enabled = true,
|
||||
|
||||
/// What content rules match in messages' bodies.
|
||||
final String? pattern,
|
||||
}) with _$PushRule {
|
||||
Map<String, Object?> toJson() => _$PushRuleToJson(this);
|
||||
|
||||
factory PushRule.fromJson(Map<String, Object?> json) =>
|
||||
_$PushRuleFromJson(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)
|
||||
class const PushActions({
|
||||
final bool notify = false,
|
||||
final bool highlight = false,
|
||||
final String? sound,
|
||||
}) with _$PushActions {
|
||||
static const defaultSound = "default";
|
||||
static const notifyWithSound = PushActions(notify: true, sound: defaultSound);
|
||||
static const highlightWithSound = PushActions(
|
||||
notify: true,
|
||||
highlight: true,
|
||||
sound: defaultSound,
|
||||
);
|
||||
|
||||
List<Object?> toJson() => [
|
||||
if (notify) "notify",
|
||||
if (highlight) {"set_tweak": "highlight"},
|
||||
if (sound != null) {"set_tweak": "sound", "value": sound},
|
||||
];
|
||||
|
||||
factory PushActions.fromJson(List<Object?> json) => json.fold(
|
||||
const .new(),
|
||||
(actions, action) => switch (action) {
|
||||
"notify" => actions.copyWith(notify: true),
|
||||
{"set_tweak": "highlight", "value": final bool highlight} =>
|
||||
actions.copyWith(highlight: highlight),
|
||||
{"set_tweak": "highlight"} => actions.copyWith(highlight: true),
|
||||
{"set_tweak": "sound", "value": final String sound} => actions.copyWith(
|
||||
sound: sound,
|
||||
),
|
||||
_ => actions,
|
||||
},
|
||||
);
|
||||
}
|
||||
27
lib/models/requests/update_push_rule.dart
Normal file
27
lib/models/requests/update_push_rule.dart
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import "package:freezed_annotation/freezed_annotation.dart";
|
||||
import "package:nexus/models/push_rules.dart";
|
||||
|
||||
part "update_push_rule.freezed.dart";
|
||||
part "update_push_rule.g.dart";
|
||||
|
||||
@Freezed(toJson: false, fromJson: false)
|
||||
@JsonSerializable()
|
||||
class const UpdatePushRuleRequest({
|
||||
required final PushRuleKind kind,
|
||||
required final String ruleId,
|
||||
required final UpdatePushRuleAction action,
|
||||
|
||||
/// The new rule, when putting it. Only its actions and pattern are used.
|
||||
final PushRule? newContent,
|
||||
|
||||
/// The new actions, when putting only them.
|
||||
final PushActions? actions,
|
||||
}) with _$UpdatePushRuleRequest {
|
||||
Map<String, Object?> toJson() => _$UpdatePushRuleRequestToJson(this);
|
||||
|
||||
factory UpdatePushRuleRequest.fromJson(Map<String, Object?> json) =>
|
||||
_$UpdatePushRuleRequestFromJson(json);
|
||||
}
|
||||
|
||||
@JsonEnum(fieldRename: .snake)
|
||||
enum UpdatePushRuleAction { enable, disable, put, putActions, delete }
|
||||
Loading…
Reference in a new issue