parent
a5711d2ded
commit
8aada9a895
4 changed files with 137 additions and 3 deletions
|
|
@ -8,6 +8,7 @@ import "package:nexus/controllers/client.dart";
|
||||||
import "package:nexus/models/account_data.dart";
|
import "package:nexus/models/account_data.dart";
|
||||||
import "package:nexus/models/push_rules.dart";
|
import "package:nexus/models/push_rules.dart";
|
||||||
import "package:nexus/models/requests/update_push_rule.dart";
|
import "package:nexus/models/requests/update_push_rule.dart";
|
||||||
|
import "package:nexus/models/room_notification_level.dart";
|
||||||
import "package:nexus/models/sync_data.dart";
|
import "package:nexus/models/sync_data.dart";
|
||||||
|
|
||||||
class AccountDataController extends Notifier<AccountData> {
|
class AccountDataController extends Notifier<AccountData> {
|
||||||
|
|
@ -98,6 +99,27 @@ class AccountDataController extends Notifier<AccountData> {
|
||||||
(rules) => rules.find(kind, ruleId) == null,
|
(rules) => rules.find(kind, ruleId) == null,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// Replaces the push rules for the room with [roomId] with those [level]
|
||||||
|
/// needs, deleting them first so disabled ones don't stay disabled.
|
||||||
|
Future<void> setRoomNotificationLevel(
|
||||||
|
String roomId,
|
||||||
|
RoomNotificationLevel level,
|
||||||
|
) async {
|
||||||
|
final rules = state.pushRules.global;
|
||||||
|
if (RoomNotificationLevel.of(rules, roomId) == level) return;
|
||||||
|
|
||||||
|
return _updatePushRules(
|
||||||
|
.new([
|
||||||
|
...[PushRuleKind.override, PushRuleKind.room]
|
||||||
|
.where((kind) => rules.find(kind, roomId) != null)
|
||||||
|
.map((kind) => .new(kind: kind, ruleId: roomId, action: .delete)),
|
||||||
|
if (level.ruleFor(roomId) case (:final kind, :final rule))
|
||||||
|
.new(kind: kind, ruleId: roomId, action: .put, newContent: rule),
|
||||||
|
]),
|
||||||
|
(rules) => RoomNotificationLevel.of(rules, roomId) == level,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Sends [requests] in order, then waits until [arrived] is true for the
|
/// Sends [requests] in order, then waits until [arrived] is true for the
|
||||||
/// synced push rules.
|
/// synced push rules.
|
||||||
Future<void> _updatePushRules(
|
Future<void> _updatePushRules(
|
||||||
|
|
|
||||||
|
|
@ -15,18 +15,20 @@ class const PushRules({final PushRuleset global = const .new()})
|
||||||
_$PushRulesFromJson(json);
|
_$PushRulesFromJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Push rules by kind, apart from room and sender rules, which Nexus doesn't
|
/// Push rules by kind, apart from sender rules, which Nexus doesn't use. The
|
||||||
/// use. The first rule that matches decides what happens.
|
/// first rule that matches decides what happens.
|
||||||
@Freezed(toJson: false, fromJson: false)
|
@Freezed(toJson: false, fromJson: false)
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class const PushRuleset({
|
class const PushRuleset({
|
||||||
final IList<PushRule> override = const .empty(),
|
final IList<PushRule> override = const .empty(),
|
||||||
final IList<PushRule> content = const .empty(),
|
final IList<PushRule> content = const .empty(),
|
||||||
|
final IList<PushRule> room = const .empty(),
|
||||||
final IList<PushRule> underride = const .empty(),
|
final IList<PushRule> underride = const .empty(),
|
||||||
}) with _$PushRuleset {
|
}) with _$PushRuleset {
|
||||||
IList<PushRule> ofKind(PushRuleKind kind) => switch (kind) {
|
IList<PushRule> ofKind(PushRuleKind kind) => switch (kind) {
|
||||||
.override => override,
|
.override => override,
|
||||||
.content => content,
|
.content => content,
|
||||||
|
.room => room,
|
||||||
.underride => underride,
|
.underride => underride,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -46,7 +48,7 @@ class const PushRuleset({
|
||||||
_$PushRulesetFromJson(json);
|
_$PushRulesetFromJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
enum PushRuleKind { override, content, underride }
|
enum PushRuleKind { override, content, room, underride }
|
||||||
|
|
||||||
@Freezed(toJson: false, fromJson: false)
|
@Freezed(toJson: false, fromJson: false)
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
|
@ -60,6 +62,9 @@ class const PushRule({
|
||||||
|
|
||||||
/// What content rules match in messages' bodies.
|
/// What content rules match in messages' bodies.
|
||||||
final String? pattern,
|
final String? pattern,
|
||||||
|
|
||||||
|
/// What override and underride rules match.
|
||||||
|
final IList<PushCondition> conditions = const .empty(),
|
||||||
}) with _$PushRule {
|
}) with _$PushRule {
|
||||||
Map<String, Object?> toJson() => _$PushRuleToJson(this);
|
Map<String, Object?> toJson() => _$PushRuleToJson(this);
|
||||||
|
|
||||||
|
|
@ -67,6 +72,24 @@ class const PushRule({
|
||||||
_$PushRuleFromJson(json);
|
_$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
|
/// What happens when a push rule matches. Actions Nexus doesn't support are
|
||||||
/// dropped, so they're replaced when these are changed.
|
/// dropped, so they're replaced when these are changed.
|
||||||
@Freezed(toJson: false, fromJson: false)
|
@Freezed(toJson: false, fromJson: false)
|
||||||
|
|
|
||||||
49
lib/models/room_notification_level.dart
Normal file
49
lib/models/room_notification_level.dart
Normal 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)])),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -5,7 +5,9 @@ import "package:nexus/helpers/extensions/get_localpart.dart";
|
||||||
import "package:nexus/helpers/extensions/show_leave_room_sheet.dart";
|
import "package:nexus/helpers/extensions/show_leave_room_sheet.dart";
|
||||||
import "package:flutter/services.dart";
|
import "package:flutter/services.dart";
|
||||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
|
import "package:nexus/controllers/account_data.dart";
|
||||||
import "package:nexus/controllers/client.dart";
|
import "package:nexus/controllers/client.dart";
|
||||||
|
import "package:nexus/models/room_notification_level.dart";
|
||||||
import "package:nexus/controllers/room_settings_sections.dart";
|
import "package:nexus/controllers/room_settings_sections.dart";
|
||||||
import "package:nexus/widgets/settings/settings_layout.dart";
|
import "package:nexus/widgets/settings/settings_layout.dart";
|
||||||
import "package:nexus/controllers/via.dart";
|
import "package:nexus/controllers/via.dart";
|
||||||
|
|
@ -44,6 +46,44 @@ final class const RoomMenuButton(
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (room != null) ...[
|
if (room != null) ...[
|
||||||
|
PopupMenuItem(
|
||||||
|
onTap: () async {
|
||||||
|
final roomId = room!.metadata!.id;
|
||||||
|
final accountData = ref.read(
|
||||||
|
AccountDataController.provider.notifier,
|
||||||
|
);
|
||||||
|
final current = RoomNotificationLevel.of(
|
||||||
|
ref.read(AccountDataController.provider).pushRules.global,
|
||||||
|
roomId,
|
||||||
|
);
|
||||||
|
|
||||||
|
final level = await context.showAdaptiveMenu(
|
||||||
|
RoomNotificationLevel.values
|
||||||
|
.map(
|
||||||
|
(level) => PopupMenuItem(
|
||||||
|
value: level,
|
||||||
|
child: ListTile(
|
||||||
|
selected: level == current,
|
||||||
|
leading: Icon(level.icon),
|
||||||
|
title: Text(level.title),
|
||||||
|
subtitle: Text(level.description),
|
||||||
|
trailing: level == current ? Icon(Icons.check) : null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
if (level == null) return;
|
||||||
|
await accountData
|
||||||
|
.setRoomNotificationLevel(roomId, level)
|
||||||
|
.onError(showError);
|
||||||
|
},
|
||||||
|
child: ListTile(
|
||||||
|
leading: Icon(Icons.notifications),
|
||||||
|
title: Text("Notifications"),
|
||||||
|
trailing: Icon(Icons.expand_more),
|
||||||
|
),
|
||||||
|
),
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final vias = ref.read(ViaController.provider(room!)).viaQuery;
|
final vias = ref.read(ViaController.provider(room!)).viaQuery;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue