Initial commit
Some checks failed
Main Deploy Workflow / deploy_web (push) Has been cancelled
Main Deploy Workflow / deploy_playstore_internal (push) Has been cancelled
Close stale issues and PRs / stale (push) Has been cancelled

This commit is contained in:
2026-09-11 15:59:56 -03:00
commit 0037b0b9cf
651 changed files with 199705 additions and 0 deletions

View File

@@ -0,0 +1,78 @@
// SPDX-FileCopyrightText: 2019-Present Christian Kußowski
// SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat
//
// SPDX-License-Identifier: AGPL-3.0-or-later
import 'package:tatchat/l10n/l10n.dart';
import 'package:tatchat/pages/chat_permissions_settings/chat_permissions_settings_view.dart';
import 'package:tatchat/widgets/future_loading_dialog.dart';
import 'package:tatchat/widgets/matrix.dart';
import 'package:tatchat/widgets/permission_slider_dialog.dart';
import 'package:go_router/go_router.dart';
import 'package:material_ui/material_ui.dart';
import 'package:matrix/matrix.dart';
class ChatPermissionsSettings extends StatefulWidget {
const ChatPermissionsSettings({super.key});
@override
ChatPermissionsSettingsController createState() =>
ChatPermissionsSettingsController();
}
class ChatPermissionsSettingsController extends State<ChatPermissionsSettings> {
String? get roomId => GoRouterState.of(context).pathParameters['roomid'];
Future<void> editPowerLevel(
BuildContext context,
String key,
int currentLevel, {
int? newLevel,
String? category,
}) async {
final room = Matrix.of(context).client.getRoomById(roomId!)!;
if (!room.canSendEvent(EventTypes.RoomPowerLevels)) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(L10n.of(context).noPermission)));
return;
}
newLevel ??= await showPermissionChooser(
context,
currentLevel: currentLevel,
);
if (newLevel == null) return;
if (!context.mounted) return;
final content = Map<String, dynamic>.from(
room.getState(EventTypes.RoomPowerLevels)!.content,
);
if (category != null) {
if (!content.containsKey(category)) {
content[category] = <String, dynamic>{};
}
content[category][key] = newLevel;
} else {
content[key] = newLevel;
}
await showFutureLoadingDialog(
context: context,
future: () => room.client.setRoomStateWithKey(
room.id,
EventTypes.RoomPowerLevels,
'',
content,
),
);
}
Stream get onChanged => Matrix.of(context).client.onSync.stream.where(
(e) =>
(e.rooms?.join?.containsKey(roomId) ?? false) &&
(e.rooms!.join![roomId!]?.timeline?.events?.any(
(s) => s.type == EventTypes.RoomPowerLevels,
) ??
false),
);
@override
Widget build(BuildContext context) => ChatPermissionsSettingsView(this);
}

View File

@@ -0,0 +1,151 @@
// SPDX-FileCopyrightText: 2019-Present Christian Kußowski
// SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat
//
// SPDX-License-Identifier: AGPL-3.0-or-later
import 'package:tatchat/l10n/l10n.dart';
import 'package:tatchat/pages/chat_permissions_settings/chat_permissions_settings.dart';
import 'package:tatchat/pages/chat_permissions_settings/permission_list_tile.dart';
import 'package:tatchat/utils/matrix_live_kit_calls/matrix_live_kit_call_member.dart';
import 'package:tatchat/widgets/layouts/max_width_body.dart';
import 'package:tatchat/widgets/matrix.dart';
import 'package:material_ui/material_ui.dart';
import 'package:matrix/matrix.dart';
class ChatPermissionsSettingsView extends StatelessWidget {
final ChatPermissionsSettingsController controller;
const ChatPermissionsSettingsView(this.controller, {super.key});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Scaffold(
appBar: AppBar(
leading: const Center(child: BackButton()),
title: Text(L10n.of(context).chatPermissions),
),
body: MaxWidthBody(
child: StreamBuilder(
stream: controller.onChanged,
builder: (context, _) {
final roomId = controller.roomId;
final room = roomId == null
? null
: Matrix.of(context).client.getRoomById(roomId);
if (room == null) {
return Center(child: Text(L10n.of(context).noRoomsFound));
}
final powerLevelsContent = Map<String, Object?>.from(
room.getState(EventTypes.RoomPowerLevels)?.content ?? {},
);
final powerLevels = Map<String, dynamic>.from(powerLevelsContent)
..removeWhere((k, v) => v is! int);
final eventsPowerLevels = Map<String, int?>.from(
powerLevelsContent.tryGetMap<String, int?>('events') ?? {},
)..removeWhere((k, v) => v is! int);
eventsPowerLevels[MatrixRtcCallMember.eventType] ??=
powerLevelsContent['state_default'] as int? ?? 50;
return Column(
children: [
ListTile(
leading: const Icon(Icons.info_outlined),
subtitle: Text(L10n.of(context).chatPermissionsDescription),
),
Divider(color: theme.dividerColor),
ListTile(
title: Text(
L10n.of(context).chatPermissions,
style: TextStyle(
color: theme.colorScheme.primary,
fontWeight: FontWeight.bold,
),
),
),
Column(
mainAxisSize: .min,
children: [
for (final entry in powerLevels.entries)
PermissionsListTile(
permissionKey: entry.key,
permission: entry.value,
onChanged: (level) => controller.editPowerLevel(
context,
entry.key,
entry.value,
newLevel: level,
),
canEdit: room.canChangePowerLevel,
),
Divider(color: theme.dividerColor),
ListTile(
title: Text(
L10n.of(context).notifications,
style: TextStyle(
color: theme.colorScheme.primary,
fontWeight: FontWeight.bold,
),
),
),
Builder(
builder: (context) {
const key = 'rooms';
final value =
powerLevelsContent.containsKey('notifications')
? powerLevelsContent
.tryGetMap<String, Object?>(
'notifications',
)
?.tryGet<int>('rooms') ??
0
: 0;
return PermissionsListTile(
permissionKey: key,
permission: value,
category: 'notifications',
canEdit: room.canChangePowerLevel,
onChanged: (level) => controller.editPowerLevel(
context,
key,
value,
newLevel: level,
category: 'notifications',
),
);
},
),
Divider(color: theme.dividerColor),
ListTile(
title: Text(
L10n.of(context).configureChat,
style: TextStyle(
color: theme.colorScheme.primary,
fontWeight: FontWeight.bold,
),
),
),
for (final entry in eventsPowerLevels.entries)
PermissionsListTile(
permissionKey: entry.key,
category: 'events',
permission: entry.value ?? 0,
canEdit: room.canChangePowerLevel,
onChanged: (level) => controller.editPowerLevel(
context,
entry.key,
entry.value ?? 0,
newLevel: level,
category: 'events',
),
),
],
),
],
);
},
),
),
);
}
}

View File

@@ -0,0 +1,130 @@
// SPDX-FileCopyrightText: 2019-Present Christian Kußowski
// SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat
//
// SPDX-License-Identifier: AGPL-3.0-or-later
import 'package:tatchat/config/app_config.dart';
import 'package:tatchat/l10n/l10n.dart';
import 'package:tatchat/utils/matrix_live_kit_calls/matrix_live_kit_call_member.dart';
import 'package:material_ui/material_ui.dart';
import 'package:matrix/matrix.dart';
class PermissionsListTile extends StatelessWidget {
final String permissionKey;
final int permission;
final String? category;
final void Function(int? level)? onChanged;
final bool canEdit;
const PermissionsListTile({
super.key,
required this.permissionKey,
required this.permission,
this.category,
required this.onChanged,
required this.canEdit,
});
String getLocalizedPowerLevelString(BuildContext context) {
if (category == null) {
switch (permissionKey) {
case 'users_default':
return L10n.of(context).defaultPermissionLevel;
case 'events_default':
return L10n.of(context).sendMessages;
case 'state_default':
return L10n.of(context).changeGeneralChatSettings;
case 'ban':
return L10n.of(context).banFromChat;
case 'kick':
return L10n.of(context).kickFromChat;
case 'redact':
return L10n.of(context).deleteMessage;
case 'invite':
return L10n.of(context).inviteOtherUsers;
}
} else if (category == 'notifications') {
switch (permissionKey) {
case 'rooms':
return L10n.of(context).sendRoomNotifications;
}
} else if (category == 'events') {
switch (permissionKey) {
case EventTypes.RoomName:
return L10n.of(context).changeTheNameOfTheGroup;
case EventTypes.RoomTopic:
return L10n.of(context).changeTheDescriptionOfTheGroup;
case EventTypes.RoomPowerLevels:
return L10n.of(context).changeTheChatPermissions;
case EventTypes.HistoryVisibility:
return L10n.of(context).changeTheVisibilityOfChatHistory;
case EventTypes.RoomCanonicalAlias:
return L10n.of(context).changeTheCanonicalRoomAlias;
case EventTypes.RoomAvatar:
return L10n.of(context).editRoomAvatar;
case EventTypes.RoomTombstone:
return L10n.of(context).replaceRoomWithNewerVersion;
case EventTypes.Encryption:
return L10n.of(context).enableEncryption;
case 'm.room.server_acl':
return L10n.of(context).editBlockedServers;
case MatrixRtcCallMember.eventType:
return 'Create or join a group call';
}
}
return permissionKey;
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final color = permission >= 100
? Colors.orangeAccent
: permission >= 50
? Colors.blueAccent
: Colors.greenAccent;
return ListTile(
title: Text(
getLocalizedPowerLevelString(context),
style: theme.textTheme.titleSmall,
),
trailing: Material(
color: color.withAlpha(32),
borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2),
child: DropdownButton<int>(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2),
underline: const SizedBox.shrink(),
onChanged: canEdit ? onChanged : null,
value: permission,
items: [
DropdownMenuItem(
value: permission < 50 ? permission : 0,
child: Text(
L10n.of(context).userLevel(permission < 50 ? permission : 0),
),
),
DropdownMenuItem(
value: permission < 100 && permission >= 50 ? permission : 50,
child: Text(
L10n.of(context).moderatorLevel(
permission < 100 && permission >= 50 ? permission : 50,
),
),
),
DropdownMenuItem(
value: permission >= 100 ? permission : 100,
child: Text(
L10n.of(
context,
).adminLevel(permission >= 100 ? permission : 100),
),
),
DropdownMenuItem(value: null, child: Text(L10n.of(context).custom)),
],
),
),
);
}
}