Add capacity field, fix details page permissions

This commit is contained in:
Kelrap 2024-06-04 14:59:17 -04:00
parent 7a43706b79
commit 215686f4a4
12 changed files with 255 additions and 79 deletions

View file

@ -3963,5 +3963,9 @@
"studentAnalyticsNotAvailable": "Student data not currently available",
"roomDataMissing": "Some data may be missing from rooms in which you are not a member.",
"updatePhoneOS": "You may need to update your device's OS version.",
"wordsPerMinute": "Words per minute"
"wordsPerMinute": "Words per minute",
"roomCapacity": "Room Capacity",
"roomFull": "This room is already at capacity.",
"topicNotSet": "The topic has not been set.",
"capacityNotSet": "This room has no capacity limit."
}

View file

@ -3,8 +3,9 @@ import 'package:collection/collection.dart';
import 'package:file_picker/file_picker.dart';
import 'package:fluffychat/pages/chat_details/chat_details_view.dart';
import 'package:fluffychat/pages/settings/settings.dart';
import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/class_description_button.dart';
import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/room_capacity_button.dart';
import 'package:fluffychat/pangea/utils/set_class_name.dart';
import 'package:fluffychat/pangea/utils/set_class_topic.dart';
import 'package:fluffychat/pangea/widgets/class/add_space_toggles.dart';
import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_settings.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
@ -232,6 +233,13 @@ class ChatDetailsController extends State<ChatDetails> {
// Pangea#
}
// #Pangea
void setCapacityAction() async {
final room = Matrix.of(context).client.getRoomById(roomId!)!;
setClassCapacity(room, context);
}
// Pangea#
void setGuestAccess() async {
final room = Matrix.of(context).client.getRoomById(roomId!)!;
final currentGuestAccess = room.guestAccess;

View file

@ -8,6 +8,7 @@ import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/class_des
import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/class_details_toggle_add_students_tile.dart';
import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/class_invitation_buttons.dart';
import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/class_name_button.dart';
import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/room_capacity_button.dart';
import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/room_rules_editor.dart';
import 'package:fluffychat/pangea/utils/archive_space.dart';
import 'package:fluffychat/pangea/utils/lock_room.dart';
@ -237,8 +238,9 @@ class ChatDetailsView extends StatelessWidget {
height: 1,
color: Theme.of(context).dividerColor,
),
// #Pangea
if (room.canSendEvent('m.room.name'))
// if (room.canSendEvent('m.room.name'))
if (room.isRoomAdmin)
// #Pangea
ClassNameButton(
room: room,
controller: controller,
@ -248,6 +250,12 @@ class ChatDetailsView extends StatelessWidget {
room: room,
controller: controller,
),
// #Pangea
RoomCapacityButton(
room: room,
controller: controller,
),
// Pangea#
if ((room.isPangeaClass || room.isExchange) &&
room.isRoomAdmin)
ListTile(
@ -436,7 +444,9 @@ class ChatDetailsView extends StatelessWidget {
// : null,
// ),
// if (!room.isDirectChat)
if (!room.isDirectChat && !room.isSpace)
if (!room.isDirectChat &&
!room.isSpace &&
room.isRoomAdmin)
// Pangea#
ListTile(
// #Pangea
@ -511,7 +521,9 @@ class ChatDetailsView extends StatelessWidget {
room: room,
),
const Divider(height: 1),
if (!room.isPangeaClass && !room.isDirectChat)
if (!room.isPangeaClass &&
!room.isDirectChat &&
room.isRoomAdmin)
AddToSpaceToggles(
roomId: room.id,
key: controller.addToSpaceKey,

View file

@ -18,6 +18,7 @@ class PangeaEventTypes {
static const audio = "p.audio";
static const botOptions = "pangea.bot_options";
static const capacity = "pangea.capacity";
static const userAge = "pangea.user_age";

View file

@ -132,8 +132,9 @@ class ClassController extends BaseController {
ClassCodeUtil.messageSnack(context, L10n.of(context)!.alreadyInClass);
return;
}
await _pangeaController.matrixState.client.joinRoom(classChunk.roomId);
setActiveSpaceIdInChatListController(classChunk.roomId);
if (_pangeaController.matrixState.client.getRoomById(classChunk.roomId) ==
null) {
await _pangeaController.matrixState.client.waitForRoomInSync(
@ -142,6 +143,14 @@ class ClassController extends BaseController {
);
}
final room =
_pangeaController.matrixState.client.getRoomById(classChunk.roomId);
if (room != null && (await room.leaveIfFull(context))) {
return;
}
setActiveSpaceIdInChatListController(classChunk.roomId);
// add the user's analytics room to this joined space
// so their teachers can join them via the space hierarchy
final Room? joinedSpace =

View file

@ -1,6 +1,20 @@
part of "pangea_room_extension.dart";
extension EventsRoomExtension on Room {
Future<bool> _leaveIfFull(BuildContext context) async {
if (!isRoomAdmin &&
(_capacity != null) &&
(await _numNonAdmins) >= (int.parse(_capacity!))) {
ClassCodeUtil.messageSnack(context, L10n.of(context)!.roomFull);
if (!isSpace) {
markUnread(false);
}
await leave();
return true;
}
return false;
}
Future<Event?> _sendPangeaEvent({
required Map<String, dynamic> content,
required String parentEventId,

View file

@ -10,9 +10,11 @@ import 'package:fluffychat/pangea/models/bot_options_model.dart';
import 'package:fluffychat/pangea/models/class_model.dart';
import 'package:fluffychat/pangea/models/tokens_event_content_model.dart';
import 'package:fluffychat/pangea/utils/bot_name.dart';
import 'package:fluffychat/pangea/utils/class_code.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
// import markdown.dart
import 'package:html_unescape/html_unescape.dart';
import 'package:matrix/matrix.dart';
@ -138,6 +140,9 @@ extension PangeaRoom on Room {
// events
Future<bool> leaveIfFull(BuildContext context) async =>
await _leaveIfFull(context);
Future<Event?> sendPangeaEvent({
required Map<String, dynamic> content,
required String parentEventId,
@ -212,6 +217,8 @@ extension PangeaRoom on Room {
// room_information
Future<int> get numNonAdmins async => await _numNonAdmins;
DateTime? get creationTime => _creationTime;
String? get creatorId => _creatorId;
@ -242,6 +249,11 @@ extension PangeaRoom on Room {
// room_settings
Future<String> updateRoomCapacity(String newCapacity) =>
_updateRoomCapacity(newCapacity);
String? get capacity => _capacity;
PangeaRoomRules? get pangeaRoomRules => _pangeaRoomRules;
PangeaRoomRules? get firstRules => _firstRules;

View file

@ -1,6 +1,17 @@
part of "pangea_room_extension.dart";
extension RoomInformationRoomExtension on Room {
Future<int> get _numNonAdmins async {
return (await requestParticipants())
.where(
(e) =>
e.powerLevel < ClassDefaultValues.powerLevelOfAdmin &&
e.id != BotName.byEnvironment,
)
.toList()
.length;
}
DateTime? get _creationTime =>
getState(EventTypes.RoomCreate)?.originServerTs;

View file

@ -1,6 +1,19 @@
part of "pangea_room_extension.dart";
extension RoomSettingsRoomExtension on Room {
Future<String> _updateRoomCapacity(String newCapacity) =>
client.setRoomStateWithKey(
id,
PangeaEventTypes.capacity,
'',
{'capacity': newCapacity},
);
String? get _capacity {
final t = getState(PangeaEventTypes.capacity)?.content['capacity'];
return t is String ? t : null;
}
PangeaRoomRules? get _pangeaRoomRules {
try {
final Map<String, dynamic>? content = pangeaRoomRulesStateEvent?.content;

View file

@ -1,9 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/pages/chat_details/chat_details.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:matrix/matrix.dart';
class ClassDescriptionButton extends StatelessWidget {
final Room room;
@ -20,21 +20,19 @@ class ClassDescriptionButton extends StatelessWidget {
return Column(
children: [
ListTile(
onTap: room.canSendEvent(EventTypes.RoomTopic)
? controller.setTopicAction
: null,
leading: room.canSendEvent(EventTypes.RoomTopic)
? CircleAvatar(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
foregroundColor: iconColor,
child: const Icon(Icons.topic_outlined),
)
: null,
onTap: room.isRoomAdmin ? controller.setTopicAction : null,
leading: CircleAvatar(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
foregroundColor: iconColor,
child: const Icon(Icons.topic_outlined),
),
subtitle: Text(
room.topic.isEmpty
? (room.isSpace
? L10n.of(context)!.classDescriptionDesc
: L10n.of(context)!.chatTopicDesc)
? (room.isRoomAdmin
? (room.isSpace
? L10n.of(context)!.classDescriptionDesc
: L10n.of(context)!.chatTopicDesc)
: L10n.of(context)!.topicNotSet)
: room.topic,
),
title: Text(
@ -51,3 +49,53 @@ class ClassDescriptionButton extends StatelessWidget {
);
}
}
void setClassTopic(Room room, BuildContext context) {
final TextEditingController textFieldController =
TextEditingController(text: room.topic);
showDialog(
context: context,
useRootNavigator: false,
builder: (BuildContext context) => AlertDialog(
title: Text(
room.isSpace
? L10n.of(context)!.classDescription
: L10n.of(context)!.chatTopic,
),
content: TextField(
controller: textFieldController,
keyboardType: TextInputType.multiline,
minLines: 1,
maxLines: 10,
maxLength: 2000,
),
actions: [
TextButton(
child: Text(L10n.of(context)!.cancel),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: Text(L10n.of(context)!.ok),
onPressed: () async {
if (textFieldController.text == "") return;
final success = await showFutureLoadingDialog(
context: context,
future: () => room.setDescription(textFieldController.text),
);
if (success.error == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content:
Text(L10n.of(context)!.groupDescriptionHasBeenChanged),
),
);
Navigator.of(context).pop();
}
},
),
],
),
);
}

View file

@ -0,0 +1,98 @@
import 'package:fluffychat/pages/chat_details/chat_details.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:matrix/matrix.dart';
class RoomCapacityButton extends StatelessWidget {
final Room room;
final ChatDetailsController? controller;
const RoomCapacityButton({
super.key,
required this.room,
this.controller,
});
@override
Widget build(BuildContext context) {
final iconColor = Theme.of(context).textTheme.bodyLarge!.color;
// Edit - use FutureBuilder to allow async call
// String nonAdmins = (await room.numNonAdmins).toString;
return Column(
children: [
ListTile(
onTap: room.isRoomAdmin ? controller!.setCapacityAction : null,
leading: CircleAvatar(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
foregroundColor: iconColor,
child: const Icon(Icons.reduce_capacity),
),
subtitle: Text(
// Edit
// '$nonAdmins/${room.capacity}',
(room.capacity ?? L10n.of(context)!.capacityNotSet),
),
title: Text(
L10n.of(context)!.roomCapacity,
style: TextStyle(
color: Theme.of(context).colorScheme.secondary,
fontWeight: FontWeight.bold,
),
),
),
],
);
}
}
void setClassCapacity(Room room, BuildContext context) {
final TextEditingController myTextFieldController =
TextEditingController(text: (room.capacity ?? ''));
showDialog(
context: context,
useRootNavigator: false,
builder: (BuildContext context) => AlertDialog(
title: Text(
L10n.of(context)!.roomCapacity,
),
content: TextFormField(
controller: myTextFieldController,
keyboardType: TextInputType.number,
maxLength: 2,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly,
],
),
actions: [
TextButton(
child: Text(L10n.of(context)!.cancel),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: Text(L10n.of(context)!.ok),
onPressed: () async {
if (myTextFieldController.text == "") return;
final success = await showFutureLoadingDialog(
context: context,
future: () => room.updateRoomCapacity(myTextFieldController.text),
);
if (success.error == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
L10n.of(context)!.groupDescriptionHasBeenChanged,
), // Edit
),
);
Navigator.of(context).pop();
}
},
),
],
),
);
}

View file

@ -1,54 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:matrix/matrix.dart';
void setClassTopic(Room room, BuildContext context) {
final TextEditingController textFieldController =
TextEditingController(text: room.topic);
showDialog(
context: context,
useRootNavigator: false,
builder: (BuildContext context) => AlertDialog(
title: Text(
room.isSpace
? L10n.of(context)!.classDescription
: L10n.of(context)!.chatTopic,
),
content: TextField(
controller: textFieldController,
keyboardType: TextInputType.multiline,
minLines: 1,
maxLines: 10,
maxLength: 2000,
),
actions: [
TextButton(
child: Text(L10n.of(context)!.cancel),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: Text(L10n.of(context)!.ok),
onPressed: () async {
if (textFieldController.text == "") return;
final success = await showFutureLoadingDialog(
context: context,
future: () => room.setDescription(textFieldController.text),
);
if (success.error == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content:
Text(L10n.of(context)!.groupDescriptionHasBeenChanged),
),
);
Navigator.of(context).pop();
}
},
),
],
),
);
}