mirror of
https://github.com/krille-chan/fluffychat
synced 2024-09-17 08:55:12 +00:00
design: Improve user permission settings
This commit is contained in:
parent
ac7e424b7b
commit
216d3bd403
5 changed files with 312 additions and 325 deletions
|
@ -2525,6 +2525,7 @@
|
||||||
"@thisDevice": {},
|
"@thisDevice": {},
|
||||||
"initAppError": "An error occured while init the app",
|
"initAppError": "An error occured while init the app",
|
||||||
"@initAppError": {},
|
"@initAppError": {},
|
||||||
|
"userRole": "User role",
|
||||||
"minimumPowerLevel": "{level} is the minimum power level.",
|
"minimumPowerLevel": "{level} is the minimum power level.",
|
||||||
"@minimumPowerLevel": {
|
"@minimumPowerLevel": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
|
|
@ -2,7 +2,6 @@ import 'dart:developer';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
|
||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
|
import 'package:future_loading_dialog/future_loading_dialog.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
@ -10,6 +9,7 @@ import 'package:matrix/matrix.dart';
|
||||||
|
|
||||||
import 'package:fluffychat/pages/chat_permissions_settings/chat_permissions_settings_view.dart';
|
import 'package:fluffychat/pages/chat_permissions_settings/chat_permissions_settings_view.dart';
|
||||||
import 'package:fluffychat/widgets/matrix.dart';
|
import 'package:fluffychat/widgets/matrix.dart';
|
||||||
|
import 'package:fluffychat/widgets/permission_slider_dialog.dart';
|
||||||
|
|
||||||
class ChatPermissionsSettings extends StatefulWidget {
|
class ChatPermissionsSettings extends StatefulWidget {
|
||||||
const ChatPermissionsSettings({super.key});
|
const ChatPermissionsSettings({super.key});
|
||||||
|
@ -35,30 +35,9 @@ class ChatPermissionsSettingsController extends State<ChatPermissionsSettings> {
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
newLevel ??= int.tryParse(
|
newLevel ??= await showPermissionChooser(
|
||||||
(await showTextInputDialog(
|
context,
|
||||||
context: context,
|
currentLevel: currentLevel,
|
||||||
title: L10n.of(context)!.setPermissionsLevel,
|
|
||||||
textFields: [
|
|
||||||
DialogTextField(
|
|
||||||
initialText: currentLevel.toString(),
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
autocorrect: false,
|
|
||||||
validator: (text) {
|
|
||||||
if (text == null) {
|
|
||||||
return L10n.of(context)!.pleaseEnterANumber;
|
|
||||||
}
|
|
||||||
final level = int.tryParse(text);
|
|
||||||
if (level == null || level < 0) {
|
|
||||||
return L10n.of(context)!.pleaseEnterANumber;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
))
|
|
||||||
?.singleOrNull ??
|
|
||||||
'',
|
|
||||||
);
|
);
|
||||||
if (newLevel == null) return;
|
if (newLevel == null) return;
|
||||||
final content = Map<String, dynamic>.from(
|
final content = Map<String, dynamic>.from(
|
||||||
|
|
|
@ -17,7 +17,6 @@ enum UserBottomSheetAction {
|
||||||
ban,
|
ban,
|
||||||
kick,
|
kick,
|
||||||
unban,
|
unban,
|
||||||
permission,
|
|
||||||
message,
|
message,
|
||||||
ignore,
|
ignore,
|
||||||
}
|
}
|
||||||
|
@ -201,30 +200,6 @@ class UserBottomSheetController extends State<UserBottomSheet> {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case UserBottomSheetAction.permission:
|
|
||||||
if (user == null) throw ('User must not be null for this action!');
|
|
||||||
final newPermission = await showPermissionChooser(
|
|
||||||
context,
|
|
||||||
currentLevel: user.powerLevel,
|
|
||||||
);
|
|
||||||
if (newPermission != null) {
|
|
||||||
if (newPermission == 100 &&
|
|
||||||
await showOkCancelAlertDialog(
|
|
||||||
useRootNavigator: false,
|
|
||||||
context: context,
|
|
||||||
title: L10n.of(context)!.areYouSure,
|
|
||||||
okLabel: L10n.of(context)!.yes,
|
|
||||||
cancelLabel: L10n.of(context)!.no,
|
|
||||||
message: L10n.of(context)!.makeAdminDescription,
|
|
||||||
) !=
|
|
||||||
OkCancelResult.ok) break;
|
|
||||||
await showFutureLoadingDialog(
|
|
||||||
context: context,
|
|
||||||
future: () => user.setPower(newPermission),
|
|
||||||
);
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case UserBottomSheetAction.message:
|
case UserBottomSheetAction.message:
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
// Workaround for https://github.com/flutter/flutter/issues/27495
|
// Workaround for https://github.com/flutter/flutter/issues/27495
|
||||||
|
@ -270,6 +245,35 @@ class UserBottomSheetController extends State<UserBottomSheet> {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setPowerLevel(int? newLevel) async {
|
||||||
|
final user = widget.user;
|
||||||
|
if (user == null) throw ('User must not be null for this action!');
|
||||||
|
|
||||||
|
final level = newLevel ??
|
||||||
|
await showPermissionChooser(
|
||||||
|
context,
|
||||||
|
currentLevel: user.powerLevel,
|
||||||
|
);
|
||||||
|
if (level == null) return;
|
||||||
|
|
||||||
|
if (level == 100) {
|
||||||
|
final consent = await showOkCancelAlertDialog(
|
||||||
|
useRootNavigator: false,
|
||||||
|
context: context,
|
||||||
|
title: L10n.of(context)!.areYouSure,
|
||||||
|
okLabel: L10n.of(context)!.yes,
|
||||||
|
cancelLabel: L10n.of(context)!.no,
|
||||||
|
message: L10n.of(context)!.makeAdminDescription,
|
||||||
|
);
|
||||||
|
if (consent != OkCancelResult.ok) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await showFutureLoadingDialog(
|
||||||
|
context: context,
|
||||||
|
future: () => user.setPower(level),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => UserBottomSheetView(this);
|
Widget build(BuildContext context) => UserBottomSheetView(this);
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,14 +104,24 @@ class UserBottomSheetView extends StatelessWidget {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: ListView(
|
body: StreamBuilder<Object>(
|
||||||
|
stream: user?.room.client.onSync.stream.where(
|
||||||
|
(syncUpdate) =>
|
||||||
|
syncUpdate.rooms?.join?[user.room.id]?.timeline?.events?.any(
|
||||||
|
(state) => state.type == EventTypes.RoomPowerLevels,
|
||||||
|
) ??
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
return ListView(
|
||||||
children: [
|
children: [
|
||||||
if (user?.membership == Membership.knock)
|
if (user?.membership == Membership.knock)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(12.0),
|
padding: const EdgeInsets.all(12.0),
|
||||||
child: Material(
|
child: Material(
|
||||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
borderRadius:
|
||||||
|
BorderRadius.circular(AppConfig.borderRadius),
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
minVerticalPadding: 16,
|
minVerticalPadding: 16,
|
||||||
title: Padding(
|
title: Padding(
|
||||||
|
@ -137,10 +147,12 @@ class UserBottomSheetView extends StatelessWidget {
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
backgroundColor:
|
backgroundColor: Theme.of(context)
|
||||||
Theme.of(context).colorScheme.errorContainer,
|
.colorScheme
|
||||||
foregroundColor:
|
.errorContainer,
|
||||||
Theme.of(context).colorScheme.onErrorContainer,
|
foregroundColor: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onErrorContainer,
|
||||||
),
|
),
|
||||||
onPressed: controller.knockDecline,
|
onPressed: controller.knockDecline,
|
||||||
icon: const Icon(Icons.cancel_outlined),
|
icon: const Icon(Icons.cancel_outlined),
|
||||||
|
@ -156,8 +168,9 @@ class UserBottomSheetView extends StatelessWidget {
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Material(
|
child: Material(
|
||||||
elevation:
|
elevation: Theme.of(context)
|
||||||
Theme.of(context).appBarTheme.scrolledUnderElevation ??
|
.appBarTheme
|
||||||
|
.scrolledUnderElevation ??
|
||||||
4,
|
4,
|
||||||
shadowColor: Theme.of(context).appBarTheme.shadowColor,
|
shadowColor: Theme.of(context).appBarTheme.shadowColor,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
|
@ -261,7 +274,8 @@ class UserBottomSheetView extends StatelessWidget {
|
||||||
color: Colors.blueAccent,
|
color: Colors.blueAccent,
|
||||||
decorationColor: Colors.blueAccent,
|
decorationColor: Colors.blueAccent,
|
||||||
),
|
),
|
||||||
onOpen: (url) => UrlLauncher(context, url.url).launchUrl(),
|
onOpen: (url) =>
|
||||||
|
UrlLauncher(context, url.url).launchUrl(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -270,27 +284,60 @@ class UserBottomSheetView extends StatelessWidget {
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.alternate_email_outlined),
|
leading: const Icon(Icons.alternate_email_outlined),
|
||||||
title: Text(L10n.of(context)!.mention),
|
title: Text(L10n.of(context)!.mention),
|
||||||
onTap: () =>
|
|
||||||
controller.participantAction(UserBottomSheetAction.mention),
|
|
||||||
),
|
|
||||||
if (user != null && user.canChangePowerLevel)
|
|
||||||
ListTile(
|
|
||||||
title: Text(L10n.of(context)!.setPermissionsLevel),
|
|
||||||
leading: const Icon(Icons.edit_attributes_outlined),
|
|
||||||
onTap: () => controller
|
onTap: () => controller
|
||||||
.participantAction(UserBottomSheetAction.permission),
|
.participantAction(UserBottomSheetAction.mention),
|
||||||
),
|
),
|
||||||
|
if (user != null) ...[
|
||||||
|
Divider(height: 1, color: Theme.of(context).dividerColor),
|
||||||
|
ListTile(
|
||||||
|
title: Text(
|
||||||
|
'${L10n.of(context)!.userRole} (${user.powerLevel})',
|
||||||
|
),
|
||||||
|
leading: const Icon(Icons.person_outlined),
|
||||||
|
trailing: DropdownButton<int>(
|
||||||
|
onChanged: user.canChangePowerLevel
|
||||||
|
? controller.setPowerLevel
|
||||||
|
: null,
|
||||||
|
value: {0, 50, 100}.contains(user.powerLevel)
|
||||||
|
? user.powerLevel
|
||||||
|
: null,
|
||||||
|
items: [
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: 0,
|
||||||
|
child: Text(L10n.of(context)!.user),
|
||||||
|
),
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: 50,
|
||||||
|
child: Text(L10n.of(context)!.moderator),
|
||||||
|
),
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: 100,
|
||||||
|
child: Text(L10n.of(context)!.admin),
|
||||||
|
),
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: null,
|
||||||
|
child: Text(L10n.of(context)!.custom),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Divider(height: 1, color: Theme.of(context).dividerColor),
|
||||||
|
],
|
||||||
if (user != null && user.canKick)
|
if (user != null && user.canKick)
|
||||||
ListTile(
|
ListTile(
|
||||||
|
textColor: Theme.of(context).colorScheme.error,
|
||||||
|
iconColor: Theme.of(context).colorScheme.error,
|
||||||
title: Text(L10n.of(context)!.kickFromChat),
|
title: Text(L10n.of(context)!.kickFromChat),
|
||||||
leading: const Icon(Icons.exit_to_app_outlined),
|
leading: const Icon(Icons.exit_to_app_outlined),
|
||||||
onTap: () =>
|
onTap: () => controller
|
||||||
controller.participantAction(UserBottomSheetAction.kick),
|
.participantAction(UserBottomSheetAction.kick),
|
||||||
),
|
),
|
||||||
if (user != null &&
|
if (user != null &&
|
||||||
user.canBan &&
|
user.canBan &&
|
||||||
user.membership != Membership.ban)
|
user.membership != Membership.ban)
|
||||||
ListTile(
|
ListTile(
|
||||||
|
textColor: Theme.of(context).colorScheme.onErrorContainer,
|
||||||
|
iconColor: Theme.of(context).colorScheme.onErrorContainer,
|
||||||
title: Text(L10n.of(context)!.banFromChat),
|
title: Text(L10n.of(context)!.banFromChat),
|
||||||
leading: const Icon(Icons.warning_sharp),
|
leading: const Icon(Icons.warning_sharp),
|
||||||
onTap: () =>
|
onTap: () =>
|
||||||
|
@ -302,8 +349,8 @@ class UserBottomSheetView extends StatelessWidget {
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(L10n.of(context)!.unbanFromChat),
|
title: Text(L10n.of(context)!.unbanFromChat),
|
||||||
leading: const Icon(Icons.warning_outlined),
|
leading: const Icon(Icons.warning_outlined),
|
||||||
onTap: () =>
|
onTap: () => controller
|
||||||
controller.participantAction(UserBottomSheetAction.unban),
|
.participantAction(UserBottomSheetAction.unban),
|
||||||
),
|
),
|
||||||
if (user != null && user.id != client.userID)
|
if (user != null && user.id != client.userID)
|
||||||
ListTile(
|
ListTile(
|
||||||
|
@ -311,8 +358,8 @@ class UserBottomSheetView extends StatelessWidget {
|
||||||
iconColor: Theme.of(context).colorScheme.onErrorContainer,
|
iconColor: Theme.of(context).colorScheme.onErrorContainer,
|
||||||
title: Text(L10n.of(context)!.reportUser),
|
title: Text(L10n.of(context)!.reportUser),
|
||||||
leading: const Icon(Icons.report_outlined),
|
leading: const Icon(Icons.report_outlined),
|
||||||
onTap: () =>
|
onTap: () => controller
|
||||||
controller.participantAction(UserBottomSheetAction.report),
|
.participantAction(UserBottomSheetAction.report),
|
||||||
),
|
),
|
||||||
if (profileSearchError != null)
|
if (profileSearchError != null)
|
||||||
ListTile(
|
ListTile(
|
||||||
|
@ -326,6 +373,8 @@ class UserBottomSheetView extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,55 +4,10 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||||
|
|
||||||
enum PermissionLevel {
|
|
||||||
user,
|
|
||||||
moderator,
|
|
||||||
admin,
|
|
||||||
custom,
|
|
||||||
}
|
|
||||||
|
|
||||||
extension on PermissionLevel {
|
|
||||||
String toLocalizedString(BuildContext context) {
|
|
||||||
switch (this) {
|
|
||||||
case PermissionLevel.user:
|
|
||||||
return L10n.of(context)!.user;
|
|
||||||
case PermissionLevel.moderator:
|
|
||||||
return L10n.of(context)!.moderator;
|
|
||||||
case PermissionLevel.admin:
|
|
||||||
return L10n.of(context)!.admin;
|
|
||||||
case PermissionLevel.custom:
|
|
||||||
default:
|
|
||||||
return L10n.of(context)!.custom;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<int?> showPermissionChooser(
|
Future<int?> showPermissionChooser(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
int currentLevel = 0,
|
int currentLevel = 0,
|
||||||
}) async {
|
}) async {
|
||||||
final permissionLevel = await showConfirmationDialog(
|
|
||||||
context: context,
|
|
||||||
title: L10n.of(context)!.setPermissionsLevel,
|
|
||||||
actions: PermissionLevel.values
|
|
||||||
.map(
|
|
||||||
(level) => AlertDialogAction(
|
|
||||||
key: level,
|
|
||||||
label: level.toLocalizedString(context),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
);
|
|
||||||
if (permissionLevel == null) return null;
|
|
||||||
|
|
||||||
switch (permissionLevel) {
|
|
||||||
case PermissionLevel.user:
|
|
||||||
return 0;
|
|
||||||
case PermissionLevel.moderator:
|
|
||||||
return 50;
|
|
||||||
case PermissionLevel.admin:
|
|
||||||
return 100;
|
|
||||||
case PermissionLevel.custom:
|
|
||||||
final customLevel = await showTextInputDialog(
|
final customLevel = await showTextInputDialog(
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context)!.setPermissionsLevel,
|
title: L10n.of(context)!.setPermissionsLevel,
|
||||||
|
@ -76,5 +31,4 @@ Future<int?> showPermissionChooser(
|
||||||
);
|
);
|
||||||
if (customLevel == null) return null;
|
if (customLevel == null) return null;
|
||||||
return int.tryParse(customLevel.first);
|
return int.tryParse(customLevel.first);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue