add power level groups
This commit is contained in:
parent
3a39f1c5ac
commit
6336764da0
4 changed files with 216 additions and 11 deletions
|
|
@ -218,7 +218,10 @@ 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 = shownEvents.map((event) => event.type.type).toISet();
|
||||
final listedEvents = shownEvents
|
||||
.addAll(PowerLevelGroup.values.expand((group) => group.events))
|
||||
.map((event) => event.type.type)
|
||||
.toISet();
|
||||
final otherEvents = powerLevels.events.entries
|
||||
.where((entry) => !listedEvents.contains(entry.key))
|
||||
.toIList()
|
||||
|
|
@ -304,6 +307,76 @@ class RoomSettingsSectionsController(final String roomId)
|
|||
),
|
||||
);
|
||||
|
||||
Widget tileOf(Setting setting) =>
|
||||
setting.builder(setting.title, setting.description, setting.icon);
|
||||
|
||||
/// A setting for all of [group]'s levels at once, which expands to set each.
|
||||
Setting groupSetting(PowerLevelGroup group) => .new(
|
||||
title: group.title,
|
||||
description: group.description,
|
||||
icon: group.icon,
|
||||
builder: (title, description, icon) => HookBuilder(
|
||||
builder: (context) {
|
||||
final expanded = useState(false);
|
||||
|
||||
return Column(
|
||||
mainAxisSize: .min,
|
||||
children: [
|
||||
PowerLevelTile(
|
||||
leading: Icon(icon),
|
||||
title: title,
|
||||
description: description,
|
||||
value: group.levelIn(powerLevels),
|
||||
ownLevel: ownLevel,
|
||||
// Changing it would change levels above the user's own
|
||||
onChanged:
|
||||
canSetPowerLevels &&
|
||||
group.highestIn(powerLevels) <= ownLevel
|
||||
? (level) =>
|
||||
setPowerLevels(group.withLevel(powerLevels, level))
|
||||
: null,
|
||||
trailing: IconButton(
|
||||
tooltip: expanded.value ? "Collapse" : "Set each",
|
||||
icon: AnimatedRotation(
|
||||
turns: expanded.value ? 0.5 : 0,
|
||||
duration: Durations.medium2,
|
||||
child: Icon(Icons.expand_more),
|
||||
),
|
||||
onPressed: () => expanded.value = !expanded.value,
|
||||
),
|
||||
),
|
||||
AnimatedSize(
|
||||
duration: Durations.medium2,
|
||||
curve: Curves.easeInOut,
|
||||
alignment: .topCenter,
|
||||
child: expanded.value
|
||||
? Padding(
|
||||
padding: .only(left: 16),
|
||||
child: Column(
|
||||
mainAxisSize: .min,
|
||||
children: [
|
||||
...group.permissions
|
||||
.map(permissionSetting)
|
||||
.map(tileOf),
|
||||
...group.events
|
||||
.map(
|
||||
(event) => eventSetting(
|
||||
event,
|
||||
fallback: powerLevels.stateDefault,
|
||||
),
|
||||
)
|
||||
.map(tileOf),
|
||||
],
|
||||
),
|
||||
)
|
||||
: const SizedBox(width: .infinity),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
return .new({
|
||||
"Room": .new([
|
||||
.new(
|
||||
|
|
@ -668,6 +741,12 @@ class RoomSettingsSectionsController(final String roomId)
|
|||
),
|
||||
),
|
||||
),
|
||||
header(
|
||||
"Groups",
|
||||
"Power levels set together, which can each be set on their own.",
|
||||
Icons.workspaces,
|
||||
),
|
||||
...PowerLevelGroup.values.map(groupSetting),
|
||||
header(
|
||||
"Regular Events",
|
||||
"Power levels needed to send messages and other timeline events.",
|
||||
|
|
@ -682,14 +761,10 @@ class RoomSettingsSectionsController(final String roomId)
|
|||
permissionSetting(.eventsDefault),
|
||||
header(
|
||||
"State Events",
|
||||
"Power levels needed to manage members and change the room's state.",
|
||||
"Power levels needed to change the room's state.",
|
||||
Icons.settings,
|
||||
),
|
||||
eventSetting(.pinnedEvents, fallback: powerLevels.stateDefault),
|
||||
permissionSetting(.invite),
|
||||
permissionSetting(.kick),
|
||||
permissionSetting(.ban),
|
||||
permissionSetting(.redact),
|
||||
permissionSetting(.stateDefault),
|
||||
header(
|
||||
"Other Events",
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ enum EventType(
|
|||
avatar("m.room.avatar", AvatarContent.fromJson),
|
||||
topic("m.room.topic", TopicContent.fromJson),
|
||||
name("m.room.name", NameContent.fromJson),
|
||||
tombstone("m.room.tombstone", Content.fromJson),
|
||||
reaction("m.reaction", ReactionContent.fromJson),
|
||||
pinnedEvents("m.room.pinned_events", PinnedEventsContent.fromJson),
|
||||
message("m.room.message", MessageContent.fromJson),
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import "dart:math";
|
||||
|
||||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||
import "package:material_ui/material_ui.dart";
|
||||
import "package:nexus/models/content/content.dart";
|
||||
|
|
@ -113,14 +115,133 @@ enum PowerLevelEvent(
|
|||
"Pin Messages",
|
||||
"The power level needed to pin messages.",
|
||||
Icons.push_pin,
|
||||
),
|
||||
name(
|
||||
.name,
|
||||
"Change Name",
|
||||
"The power level needed to change the room's name.",
|
||||
Icons.title,
|
||||
),
|
||||
topic(
|
||||
.topic,
|
||||
"Change Topic",
|
||||
"The power level needed to change the room's topic.",
|
||||
Icons.description,
|
||||
),
|
||||
avatar(
|
||||
.avatar,
|
||||
"Change Avatar",
|
||||
"The power level needed to change the room's avatar.",
|
||||
Icons.image,
|
||||
),
|
||||
canonicalAlias(
|
||||
.canonicalAlias,
|
||||
"Change Aliases",
|
||||
"The power level needed to change the room's canonical alias and published aliases.",
|
||||
Icons.numbers,
|
||||
),
|
||||
powerLevels(
|
||||
.powerLevels,
|
||||
"Change Power Levels",
|
||||
"The power level needed to change power levels, including these.",
|
||||
Icons.admin_panel_settings,
|
||||
),
|
||||
joinRules(
|
||||
.joinRules,
|
||||
"Change Join Rule",
|
||||
"The power level needed to change who can join the room.",
|
||||
Icons.login,
|
||||
),
|
||||
historyVisibility(
|
||||
.historyVisibility,
|
||||
"Change History Visibility",
|
||||
"The power level needed to change who can read the room's history.",
|
||||
Icons.history,
|
||||
),
|
||||
serverACL(
|
||||
.serverACL,
|
||||
"Change Server ACL",
|
||||
"The power level needed to change which servers can take part in the room.",
|
||||
Icons.dns,
|
||||
),
|
||||
encryption(
|
||||
.encryption,
|
||||
"Enable Encryption",
|
||||
"The power level needed to enable encryption, which can't be turned off.",
|
||||
Icons.lock,
|
||||
),
|
||||
tombstone(
|
||||
.tombstone,
|
||||
"Upgrade Room",
|
||||
"The power level needed to replace the room with an upgraded one.",
|
||||
Icons.upgrade,
|
||||
);
|
||||
|
||||
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.type.type == type).firstOrNull;
|
||||
}
|
||||
|
||||
/// Power levels set together, like everything moderators need, to save
|
||||
/// setting each one. Each can still be set on its own, by expanding it.
|
||||
enum PowerLevelGroup(final String title, final IconData icon) {
|
||||
moderation("Moderation", Icons.shield),
|
||||
roomDetails("Room Details", Icons.edit),
|
||||
administration("Administration", Icons.admin_panel_settings);
|
||||
|
||||
IList<PowerLevelPermission> get permissions => .new(switch (this) {
|
||||
.moderation => [.kick, .ban, .redact, .notifyRoom],
|
||||
.roomDetails || .administration => [],
|
||||
});
|
||||
|
||||
IList<PowerLevelEvent> get events => .new(switch (this) {
|
||||
.moderation => [],
|
||||
.roomDetails => [.name, .topic, .avatar, .canonicalAlias, .pinnedEvents],
|
||||
.administration => [
|
||||
.powerLevels,
|
||||
.joinRules,
|
||||
.historyVisibility,
|
||||
.serverACL,
|
||||
.encryption,
|
||||
.tombstone,
|
||||
],
|
||||
});
|
||||
|
||||
String get description => [
|
||||
...permissions.map((permission) => permission.title),
|
||||
...events.map((event) => event.title),
|
||||
].join(", ");
|
||||
|
||||
int? levelIn(PowerLevelsContent content) {
|
||||
final levels = {
|
||||
...permissions.map((permission) => permission.levelIn(content)),
|
||||
...events.map(
|
||||
(event) => content.events[event.type.type] ?? content.stateDefault,
|
||||
),
|
||||
};
|
||||
return levels.length == 1 ? levels.single : null;
|
||||
}
|
||||
|
||||
int highestIn(PowerLevelsContent content) => [
|
||||
...permissions.map((permission) => permission.levelIn(content)),
|
||||
...events.map(
|
||||
(event) => content.events[event.type.type] ?? content.stateDefault,
|
||||
),
|
||||
].reduce(max);
|
||||
|
||||
PowerLevelsContent withLevel(PowerLevelsContent content, int level) =>
|
||||
permissions
|
||||
.fold(
|
||||
content,
|
||||
(content, permission) => permission.withLevel(content, level),
|
||||
)
|
||||
.copyWith(
|
||||
events: content.events.addEntries(
|
||||
events.map((event) => MapEntry(event.type.type, level)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@ class const PowerLevelTile({
|
|||
required final Widget leading,
|
||||
required final String title,
|
||||
required final String? description,
|
||||
required final int value,
|
||||
|
||||
/// Null for several levels that differ, shown as custom.
|
||||
required final int? value,
|
||||
|
||||
/// The user's own power level, which they can't set or change levels above.
|
||||
required final double ownLevel,
|
||||
|
|
@ -22,11 +24,14 @@ class const PowerLevelTile({
|
|||
|
||||
/// If given, asked before removing the level.
|
||||
final String? removeConfirmation,
|
||||
|
||||
/// Shown after the level, like a button to expand what it sets.
|
||||
final Widget? trailing,
|
||||
super.key,
|
||||
}) extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final enabled = onChanged != null && value <= ownLevel;
|
||||
final enabled = onChanged != null && (value ?? 0) <= ownLevel;
|
||||
|
||||
Future<void> remove() async {
|
||||
if (removeConfirmation case final confirmation?) {
|
||||
|
|
@ -65,7 +70,7 @@ class const PowerLevelTile({
|
|||
children: [
|
||||
Chip(
|
||||
label: Text(
|
||||
"$value",
|
||||
value?.toString() ?? "Custom",
|
||||
style: enabled ? null : .new(color: Colors.grey),
|
||||
),
|
||||
),
|
||||
|
|
@ -75,12 +80,15 @@ class const PowerLevelTile({
|
|||
icon: Icon(Icons.delete),
|
||||
onPressed: enabled ? remove : null,
|
||||
),
|
||||
?trailing,
|
||||
],
|
||||
),
|
||||
onTap: () => context.showAdaptiveSheet(
|
||||
(context) => HookBuilder(
|
||||
builder: (context) {
|
||||
final controller = useTextEditingController(text: "$value");
|
||||
final controller = useTextEditingController(
|
||||
text: value?.toString(),
|
||||
);
|
||||
final level = PowerLevelField.levelFrom(
|
||||
useValueListenable(controller).text,
|
||||
ownLevel,
|
||||
|
|
|
|||
Loading…
Reference in a new issue