chore: Adjust power level UX

This commit is contained in:
Christian Kußowski 2026-02-25 13:12:47 +01:00
parent 1ea607f633
commit 47934a3378
No known key found for this signature in database
GPG key ID: E067ECD60F1A0652
4 changed files with 187 additions and 107 deletions

View file

@ -3126,5 +3126,12 @@
"theProcessWasCanceled": "The process was canceled.",
"join": "Join",
"searchOrEnterHomeserverAddress": "Search or enter homeserver address",
"matrixId": "Matrix ID"
"matrixId": "Matrix ID",
"setPowerLevel": "Set power level",
"makeModerator": "Make moderator",
"makeAdmin": "Make admin",
"removeModeratorRights": "Remove moderator rights",
"removeAdminRights": "Remove admin rights",
"powerLevel": "Power level",
"setPowerLevelDescription": "Power levels define what a member is allowed to do in this room and usually range between 0 and 100."
}

View file

@ -210,6 +210,7 @@ class Message extends StatelessWidget {
singleSelected && event.room.canSendDefaultMessages;
final enterThread = this.enterThread;
final sender = event.senderFromMemoryOrFallback;
return Center(
child: Swipeable(
@ -358,9 +359,7 @@ class Message extends StatelessWidget {
FutureBuilder<User?>(
future: event.fetchSenderUser(),
builder: (context, snapshot) {
final user =
snapshot.data ??
event.senderFromMemoryOrFallback;
final user = snapshot.data ?? sender;
return Avatar(
mxContent: user.avatarUrl,
name: user.calcDisplayname(),
@ -392,52 +391,78 @@ class Message extends StatelessWidget {
ownMessage ||
event.room.isDirectChat
? const SizedBox(height: 12)
: FutureBuilder<User?>(
future: event
.fetchSenderUser(),
builder: (context, snapshot) {
final displayname =
snapshot.data
?.calcDisplayname() ??
event
.senderFromMemoryOrFallback
.calcDisplayname();
return Text(
displayname,
style: TextStyle(
fontSize: 11,
fontWeight:
FontWeight.bold,
color:
(theme.brightness ==
Brightness
.light
? displayname
.color
: displayname
.lightColorText),
shadows:
!wallpaperMode
? null
: [
const Shadow(
offset:
Offset(
0.0,
0.0,
),
blurRadius:
3,
color: Colors
.black,
),
],
: Row(
children: [
if (sender.powerLevel >=
50)
Padding(
padding:
const EdgeInsets.only(
right: 2.0,
),
child: Icon(
sender.powerLevel >=
100
? Icons
.admin_panel_settings
: Icons
.add_moderator_outlined,
size: 14,
color: theme
.colorScheme
.onPrimaryContainer,
),
),
maxLines: 1,
overflow: TextOverflow
.ellipsis,
);
},
Expanded(
child: FutureBuilder<User?>(
future: event
.fetchSenderUser(),
builder: (context, snapshot) {
final displayname =
snapshot.data
?.calcDisplayname() ??
sender
.calcDisplayname();
return Text(
displayname,
style: TextStyle(
fontSize: 11,
fontWeight:
FontWeight
.bold,
color:
(theme.brightness ==
Brightness
.light
? displayname
.color
: displayname
.lightColorText),
shadows:
!wallpaperMode
? null
: [
const Shadow(
offset: Offset(
0.0,
0.0,
),
blurRadius:
3,
color:
Colors.black,
),
],
),
maxLines: 1,
overflow:
TextOverflow
.ellipsis,
);
},
),
),
],
),
),
Container(

View file

@ -43,13 +43,7 @@ Future<void> showMemberActionsPopupMenu({
child: Row(
spacing: 12.0,
children: [
Avatar(
name: displayname,
size: 30,
mxContent: user.avatarUrl,
presenceUserId: user.id,
presenceBackgroundColor: theme.colorScheme.surfaceContainer,
),
Avatar(name: displayname, size: 30, mxContent: user.avatarUrl),
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 200),
child: Text(
@ -83,31 +77,71 @@ Future<void> showMemberActionsPopupMenu({
],
),
),
PopupMenuItem(
enabled: user.room.canChangePowerLevel && user.canChangeUserPowerLevel,
value: _MemberActions.setRole,
child: Row(
children: [
const Icon(Icons.admin_panel_settings_outlined),
const SizedBox(width: 18),
Column(
mainAxisSize: .min,
crossAxisAlignment: .start,
if (user.canChangeUserPowerLevel) ...[
if (user.powerLevel < 100)
PopupMenuItem(
value: _MemberActions.makeAdmin,
child: Row(
children: [
Text(L10n.of(context).chatPermissions),
Text(
user.powerLevel < 50
? L10n.of(context).userLevel(user.powerLevel)
: user.powerLevel < 100
? L10n.of(context).moderatorLevel(user.powerLevel)
: L10n.of(context).adminLevel(user.powerLevel),
style: const TextStyle(fontSize: 10),
),
const Icon(Icons.admin_panel_settings_outlined),
const SizedBox(width: 18),
Text(L10n.of(context).makeAdmin),
],
),
],
),
if (user.powerLevel < 50)
PopupMenuItem(
value: _MemberActions.makeModerator,
child: Row(
children: [
const Icon(Icons.add_moderator_outlined),
const SizedBox(width: 18),
Text(L10n.of(context).makeModerator),
],
),
),
if (user.powerLevel >= 100)
PopupMenuItem(
value: _MemberActions.removeAdmin,
child: Row(
children: [
const Icon(Icons.remove_moderator_outlined),
const SizedBox(width: 18),
Text(L10n.of(context).removeAdminRights),
],
),
)
else if (user.powerLevel >= 50)
PopupMenuItem(
value: _MemberActions.removeModerator,
child: Row(
children: [
const Icon(Icons.remove_moderator_outlined),
const SizedBox(width: 18),
Text(L10n.of(context).removeModeratorRights),
],
),
),
],
if (user.canChangeUserPowerLevel ||
!{0, 50, 100}.contains(user.powerLevel))
PopupMenuItem(
value: _MemberActions.setPowerLevel,
enabled: user.canChangeUserPowerLevel,
child: Row(
children: [
const Icon(Icons.manage_accounts_outlined),
const SizedBox(width: 18),
Text(
user.canChangeUserPowerLevel
? L10n.of(context).setPowerLevel
: L10n.of(context).powerLevel,
),
if (!{0, 50, 100}.contains(user.powerLevel))
Text(' (${user.powerLevel})'),
],
),
),
),
if (user.canKick)
PopupMenuItem(
value: _MemberActions.kick,
@ -179,7 +213,7 @@ Future<void> showMemberActionsPopupMenu({
case _MemberActions.mention:
onMention?.call();
return;
case _MemberActions.setRole:
case _MemberActions.setPowerLevel:
final power = await showPermissionChooser(
context,
currentLevel: user.powerLevel,
@ -280,13 +314,48 @@ Future<void> showMemberActionsPopupMenu({
future: () => user.unban(),
);
}
case _MemberActions.makeAdmin:
if (user.room.ownPowerLevel <= 100) {
final consent = await showOkCancelAlertDialog(
context: context,
title: L10n.of(context).areYouSure,
message: L10n.of(context).makeAdminDescription,
);
if (consent != OkCancelResult.ok) return;
if (!context.mounted) return;
}
await showFutureLoadingDialog(
context: context,
future: () => user.setPower(100),
);
case _MemberActions.makeModerator:
await showFutureLoadingDialog(
context: context,
future: () => user.setPower(50),
);
case _MemberActions.removeAdmin:
case _MemberActions.removeModerator:
final defaultUserLevel =
user.room
.getState(EventTypes.RoomPowerLevels)
?.content
.tryGet<int>('users_default') ??
0;
await showFutureLoadingDialog(
context: context,
future: () => user.setPower(defaultUserLevel),
);
}
}
enum _MemberActions {
info,
mention,
setRole,
setPowerLevel,
makeAdmin,
makeModerator,
removeAdmin,
removeModerator,
kick,
ban,
approve,

View file

@ -9,7 +9,7 @@ Future<int?> showPermissionChooser(
int currentLevel = 0,
int maxLevel = 100,
}) async {
final controller = TextEditingController();
final controller = TextEditingController(text: currentLevel.toString());
final error = ValueNotifier<String?>(null);
return await showAdaptiveDialog<int>(
context: context,
@ -22,7 +22,7 @@ Future<int?> showPermissionChooser(
crossAxisAlignment: .stretch,
spacing: 12.0,
children: [
Text(L10n.of(context).setPermissionsLevelDescription),
Text(L10n.of(context).setPowerLevelDescription),
ValueListenableBuilder(
valueListenable: error,
builder: (context, errorText, _) => DialogTextField(
@ -38,8 +38,6 @@ Future<int?> showPermissionChooser(
),
actions: [
AdaptiveDialogAction(
bigButtons: true,
borderRadius: AdaptiveDialogAction.topRadius,
onPressed: () {
final level = int.tryParse(controller.text.trim());
if (level == null) {
@ -52,31 +50,12 @@ Future<int?> showPermissionChooser(
}
Navigator.of(context).pop<int>(level);
},
child: Text(L10n.of(context).setCustomPermissionLevel),
child: Text(L10n.of(context).setPowerLevel),
),
AdaptiveDialogAction(
onPressed: () => Navigator.of(context).pop<int>(null),
child: Text(L10n.of(context).cancel),
),
if (maxLevel >= 100 && currentLevel != 100)
AdaptiveDialogAction(
borderRadius: AdaptiveDialogAction.centerRadius,
bigButtons: true,
onPressed: () => Navigator.of(context).pop<int>(100),
child: Text(L10n.of(context).admin),
),
if (maxLevel >= 50 && currentLevel != 50)
AdaptiveDialogAction(
borderRadius: maxLevel != 0
? AdaptiveDialogAction.centerRadius
: AdaptiveDialogAction.bottomRadius,
bigButtons: true,
onPressed: () => Navigator.of(context).pop<int>(50),
child: Text(L10n.of(context).moderator),
),
if (currentLevel != 0)
AdaptiveDialogAction(
borderRadius: AdaptiveDialogAction.bottomRadius,
bigButtons: true,
onPressed: () => Navigator.of(context).pop<int>(0),
child: Text(L10n.of(context).normalUser),
),
],
),
);