diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 303cf6390..5fbbb2f83 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -3128,7 +3128,7 @@ "maybeLater": "Maybe Later", "mainMenu": "Main Menu", "toggleImmersionMode": "Immersion Mode", - "toggleImmersionModeDesc": "When enabled, all messages are displayed in your target language and you can click the message to access definitions and translations.", + "toggleImmersionModeDesc": "When enabled, all messages are displayed in your target language. This setting is most useful in language exchanges.", "itToggleDescription": "This language learning tool will identify words in your base language and help you translate them to your target language. Though rare, the AI can make translation errors.", "igcToggleDescription": "This language learning tool will identify common spelling, grammar and punctuation errors in your message and suggest corrections. Though rare, the AI can make correction errors.", "sendOnEnterDescription": "Turn this off to be able to add line spaces in messages. When the toggle is off on the browser app, you can press Shift + Enter to start a new line. When the toggle is off on mobile apps, just Enter will start a new line.", @@ -3609,7 +3609,7 @@ "zmCountryDisplayName": "Zambia", "zwCountryDisplayName": "Zimbabwe", "pay": "Pay", - "allPrivateChats": "All private chats in space (including with Pangea Bot)", + "allPrivateChats": "Direct chats", "unknownPrivateChat": "Unknown private chat", "copyClassCodeDesc": "Students who are already in the app can 'Join class or exchange' via the main menu.", "addToClass": "Add exchange to class", @@ -3681,24 +3681,8 @@ "lockSpace": "Lock Space", "lockChat": "Lock Chat", "archiveSpace": "Archive Space", - "suggestTo": "Suggest to {spaceName}", - "@suggestTo": { - "placeholders": { - "spaceName": {} - } - }, - "suggestChatDesc": "Suggested chats will appear in the chat list for {spaceName}", - "@suggestToDesc": { - "placeholders": { - "spaceName": {} - } - }, - "suggestExchangeDesc": "Suggested exchanges will appear in the chat list for {spaceName}", - "@suggestToExchangeDesc": { - "placeholders": { - "spaceName": {} - } - }, + "suggestToChat": "Suggest this chat", + "suggestToChatDesc": "Suggested chats will appear in chat lists", "acceptSelection": "Accept Correction", "acceptSelectionAnyway": "Use this anyway", "makingActivity": "Making activity", @@ -3724,7 +3708,7 @@ }, "noTeachersFound": "No teachers found to report to", "pleaseEnterANumber": "Please enter a number greater than 0", - "archiveRoomDescription": "The chat will be moved to the archive. Other users will be able to see that you have left the chat.", + "archiveRoomDescription": "The chat will be moved to the archive for yourself and other non-admin users.", "roomUpgradeDescription": "The chat will then be recreated with the new room version. All participants will be notified that they need to switch to the new chat. You can find out more about room versions at https://spec.matrix.org/latest/rooms/", "removeDevicesDescription": "You will be logged out of this device and will no longer be able to receive messages.", "banUserDescription": "The user will be banned from the chat and will not be able to enter the chat again until they are unbanned.", @@ -3972,5 +3956,17 @@ "roomExceedsCapacity": "Room exceeds capacity. Consider removing students from the room, or raising the capacity.", "capacitySetTooLow": "Room capacity cannot be set below the current number of non-admins.", "roomCapacityExplanation": "Room capacity limits the number of non-admins allowed in a room.", - "enterNumber": "Please enter a whole number value." + "enterNumber": "Please enter a whole number value.", + "autoIGCToolName": "Run Language Assistance Automatically", + "autoIGCToolDescription": "Automatically run language assistance after typing messages", + "runGrammarCorrection": "Run grammar correction", + "grammarCorrectionFailed": "Issues to address", + "grammarCorrectionComplete": "Grammar correction complete", + "leaveRoomDescription": "The chat will be moved to the archive. Other users will be able to see that you have left the chat.", + "archiveSpaceDescription": "All chats within this space will be moved to the archive for yourself and other non-admin users.", + "leaveSpaceDescription": "All chats within this space will be moved to the archive. Other users will be able to see that you have left the space.", + "onlyAdminDescription": "Since there are no other admins, all other participants will also be removed.", + "tooltipInstructionsTitle": "Not sure what that does?", + "tooltipInstructionsMobileBody": "Press and hold items to view tooltips.", + "tooltipInstructionsBrowserBody": "Hover over items to view tooltips." } \ No newline at end of file diff --git a/assets/l10n/intl_es.arb b/assets/l10n/intl_es.arb index d8f54304a..fa097d393 100644 --- a/assets/l10n/intl_es.arb +++ b/assets/l10n/intl_es.arb @@ -3281,7 +3281,7 @@ "generateVocabulary": "Generar vocabulario basado en el título y la descripción", "generatePrompts": "Generar preguntas basado en el título y la descripción", "toggleImmersionMode": "Modo de inmersión", - "toggleImmersionModeDesc": "Cuando está habilitado, todos los mensajes se muestran en su idioma de destino y puede hacer clic en el mensaje para acceder a definiciones y traducciones.", + "toggleImmersionModeDesc": "Cuando está habilitado, todos los mensajes se muestran en su idioma de destino. Esta configuración es más útil en intercambios de idiomas.", "subscribe": "Subscríbase", "getAccess": "Activar herramientas", "subscriptionDesc": "¡Enviar y recibir mensajes es gratis! Suscríbase para aceder a la traducción interactiva, la revisión gramatical y el análisis de aprendizaje.", @@ -3698,7 +3698,7 @@ "@optionalRedactReason": {}, "dehydrate": "Exportar sesión y borrar dispositivo", "@dehydrate": {}, - "archiveRoomDescription": "", + "archiveRoomDescription": "El chat se moverá al archivo para ti y para otros usuarios que no sean administradores", "@archiveRoomDescription": {}, "pleaseEnterRecoveryKeyDescription": "Para desbloquear sus mensajes antiguos, ingrese su clave de recuperación que se generó en una sesión anterior. Su clave de recuperación NO es su contraseña.", "@pleaseEnterRecoveryKeyDescription": {}, @@ -4274,7 +4274,7 @@ "zwCountryDisplayName": "Zimbabue", "downloadXLSXFile": "Descargar archivo Excel", "unknownPrivateChat": "Chat Privado Desconocido", - "allPrivateChats": "Todos los chats privados (incluso con bots) en clase", + "allPrivateChats": "Chats privado", "chatHasBeenAddedToThisSpace": "Se ha añadido el chat a este espacio", "classes": "Clases", "spaceIsPublic": "El espacio es público", diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 500aa5f1e..4f363ac4a 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -231,6 +231,17 @@ class ChatController extends State context.go('/rooms'); } + // #Pangea + void archiveChat() async { + final success = await showFutureLoadingDialog( + context: context, + future: room.archive, + ); + if (success.error != null) return; + context.go('/rooms'); + } + // Pangea# + EmojiPickerType emojiPickerType = EmojiPickerType.keyboard; // #Pangea @@ -1319,9 +1330,18 @@ class ChatController extends State } // Pangea# if (!event.redacted) { - if (selectedEvents.contains(event)) { + // #Pangea + // If previous selectedEvent has same eventId, delete previous selectedEvent + final matches = + selectedEvents.where((e) => e.eventId == event.eventId).toList(); + if (matches.isNotEmpty) { + // if (selectedEvents.contains(event)) { + // Pangea# setState( - () => selectedEvents.remove(event), + // #Pangea + () => selectedEvents.remove(matches.first), + // () => selectedEvents.remove(event), + // Pangea# ); } else { setState( diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index 2f9610ec9..ff6da5099 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -9,6 +9,7 @@ import 'package:fluffychat/pages/chat/reactions_picker.dart'; import 'package:fluffychat/pages/chat/reply_display.dart'; import 'package:fluffychat/pangea/choreographer/widgets/has_error_button.dart'; import 'package:fluffychat/pangea/choreographer/widgets/language_permissions_warning_buttons.dart'; +import 'package:fluffychat/pangea/choreographer/widgets/start_igc_button.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; import 'package:fluffychat/pangea/pages/class_analytics/measure_able.dart'; import 'package:fluffychat/utils/account_config.dart'; @@ -373,6 +374,29 @@ class ChatView extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ + // #Pangea + if (controller.room.isRoomAdmin) + TextButton.icon( + style: TextButton.styleFrom( + padding: + const EdgeInsets.all( + 16, + ), + foregroundColor: + Theme.of(context) + .colorScheme + .error, + ), + icon: const Icon( + Icons.archive_outlined, + ), + onPressed: + controller.archiveChat, + label: Text( + L10n.of(context)!.archive, + ), + ), + // Pangea# TextButton.icon( style: TextButton.styleFrom( padding: const EdgeInsets.all( @@ -384,7 +408,10 @@ class ChatView extends StatelessWidget { .error, ), icon: const Icon( - Icons.archive_outlined, + // #Pangea + // Icons.archive_outlined, + Icons.arrow_forward, + // Pangea# ), onPressed: controller.leaveChat, label: Text( @@ -429,8 +456,8 @@ class ChatView extends StatelessWidget { // #Pangea // if (controller.dragging) // Container( - // color: Theme.of(context) - // .scaffoldBackgroundColor + // color: Theme.of(context) + // .scaffoldBackgroundColor // .withOpacity(0.9), // alignment: Alignment.center, // child: const Icon( @@ -438,6 +465,11 @@ class ChatView extends StatelessWidget { // size: 100, // ), // ), + Positioned( + left: 20, + bottom: 75, + child: StartIGCButton(controller: controller), + ), // Pangea# ], ), diff --git a/lib/pages/chat/events/message_content.dart b/lib/pages/chat/events/message_content.dart index 41997afea..2d27807b8 100644 --- a/lib/pages/chat/events/message_content.dart +++ b/lib/pages/chat/events/message_content.dart @@ -199,18 +199,8 @@ class MessageContent extends StatelessWidget { case MessageTypes.Notice: case MessageTypes.Emote: if (AppConfig.renderHtml && - !event.redacted && - event.isRichMessage - // #Pangea - && - !(pangeaMessageEvent?.showRichText( - selected, - isOverlay: isOverlay, - highlighted: toolbarController?.highlighted ?? false, - ) ?? - false) - // Pangea# - ) { + !event.redacted && + event.isRichMessage) { var html = event.formattedText; if (event.messageType == MessageTypes.Emote) { html = '* $html'; @@ -306,18 +296,17 @@ class MessageContent extends StatelessWidget { decoration: event.redacted ? TextDecoration.lineThrough : null, height: 1.3, ); - if (pangeaMessageEvent?.showRichText( - selected, - isOverlay: isOverlay, - highlighted: toolbarController?.highlighted ?? false, - ) ?? - false) { + if (immersionMode && pangeaMessageEvent != null) { return PangeaRichText( style: messageTextStyle, pangeaMessageEvent: pangeaMessageEvent!, immersionMode: immersionMode, toolbarController: toolbarController, ); + } else if (pangeaMessageEvent != null) { + toolbarController?.toolbar?.textSelection.setMessageText( + pangeaMessageEvent!.body, + ); } // Pangea# return FutureBuilder( diff --git a/lib/pages/chat_details/chat_details_view.dart b/lib/pages/chat_details/chat_details_view.dart index e4fb40013..3159af329 100644 --- a/lib/pages/chat_details/chat_details_view.dart +++ b/lib/pages/chat_details/chat_details_view.dart @@ -10,7 +10,6 @@ import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/class_inv 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'; import 'package:fluffychat/pangea/widgets/class/add_class_and_invite.dart'; import 'package:fluffychat/pangea/widgets/class/add_space_toggles.dart'; @@ -537,52 +536,126 @@ class ChatDetailsView extends StatelessWidget { ), const Divider(height: 1), if (!room.isDirectChat) - ListTile( - title: Text( - room.isSpace - ? L10n.of(context)!.archiveSpace - : L10n.of(context)!.archive, - style: TextStyle( - color: Theme.of(context).colorScheme.secondary, - fontWeight: FontWeight.bold, + if (room.isRoomAdmin) + ListTile( + title: Text( + room.isSpace + ? L10n.of(context)!.archiveSpace + : L10n.of(context)!.archive, + style: TextStyle( + color: + Theme.of(context).colorScheme.secondary, + fontWeight: FontWeight.bold, + ), ), - ), - leading: CircleAvatar( - backgroundColor: - Theme.of(context).scaffoldBackgroundColor, - foregroundColor: iconColor, - child: const Icon( - Icons.archive_outlined, + leading: CircleAvatar( + backgroundColor: + Theme.of(context).scaffoldBackgroundColor, + foregroundColor: iconColor, + child: const Icon( + Icons.archive_outlined, + ), ), + onTap: () async { + OkCancelResult confirmed = OkCancelResult.ok; + bool shouldGo = false; + // archiveSpace has its own popup; only show if not space + if (!room.isSpace) { + confirmed = await showOkCancelAlertDialog( + useRootNavigator: false, + context: context, + title: L10n.of(context)!.areYouSure, + okLabel: L10n.of(context)!.ok, + cancelLabel: L10n.of(context)!.cancel, + message: L10n.of(context)! + .archiveRoomDescription, + ); + } + if (confirmed == OkCancelResult.ok) { + if (room.isSpace) { + shouldGo = await room.archiveSpace( + context, + Matrix.of(context).client, + ); + } else { + final success = + await showFutureLoadingDialog( + context: context, + future: () async { + await room.archive(); + }, + ); + shouldGo = (success.error == null); + } + if (shouldGo) { + context.go('/rooms'); + } + } + }, ), - onTap: () async { - final confirmed = await showOkCancelAlertDialog( + ListTile( + title: Text( + L10n.of(context)!.leave, + style: TextStyle( + color: Theme.of(context).colorScheme.secondary, + fontWeight: FontWeight.bold, + ), + ), + leading: CircleAvatar( + backgroundColor: + Theme.of(context).scaffoldBackgroundColor, + foregroundColor: iconColor, + child: const Icon( + Icons.arrow_forward, + ), + ), + onTap: () async { + OkCancelResult confirmed = OkCancelResult.ok; + bool shouldGo = false; + // If user is only admin, room will be archived + final bool onlyAdmin = await room.isOnlyAdmin(); + // archiveSpace has its own popup; only show if not space + if (!room.isSpace) { + confirmed = await showOkCancelAlertDialog( useRootNavigator: false, context: context, title: L10n.of(context)!.areYouSure, okLabel: L10n.of(context)!.ok, cancelLabel: L10n.of(context)!.cancel, - message: - L10n.of(context)!.archiveRoomDescription, + message: onlyAdmin + ? L10n.of(context)!.onlyAdminDescription + : L10n.of(context)!.leaveRoomDescription, ); - if (confirmed == OkCancelResult.ok) { + } + if (confirmed == OkCancelResult.ok) { + if (room.isSpace) { + shouldGo = onlyAdmin + ? await room.archiveSpace( + context, + Matrix.of(context).client, + onlyAdmin: true, + ) + : await room.leaveSpace( + context, + Matrix.of(context).client, + ); + } else { final success = await showFutureLoadingDialog( context: context, future: () async { - room.isSpace - ? await archiveSpace( - room, - Matrix.of(context).client, - ) + onlyAdmin + ? await room.archive() : await room.leave(); }, ); - if (success.error == null) { - context.go('/rooms'); - } + shouldGo = (success.error == null); } - }, - ), + if (shouldGo) { + context.go('/rooms'); + } + } + }, + ), if (room.isRoomAdmin && !room.isDirectChat) SwitchListTile.adaptive( activeColor: AppConfig.activeToggleColor, diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 7d1370295..f7294e571 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -105,6 +105,12 @@ class ChatListController extends State selectedRoomIds.clear(); activeSpaceId = spaceId; activeFilter = ActiveFilter.spaces; + // #Pangea + // don't show all spaces view if in column mode + if (spaceId == null && FluffyThemes.isColumnMode(context)) { + activeFilter = ActiveFilter.allChats; + } + // Pangea# }); } @@ -515,7 +521,8 @@ class ChatListController extends State //#Pangea classStream = pangeaController.classController.stateStream.listen((event) { - if (event["activeSpaceId"] != null && mounted) { + // if (event["activeSpaceId"] != null && mounted) { + if (mounted) { setActiveSpace(event["activeSpaceId"]); } }); @@ -679,6 +686,38 @@ class ChatListController extends State // Pangea# } + // #Pangea + Future leaveAction() async { + final bool onlyAdmin = await Matrix.of(context) + .client + .getRoomById(selectedRoomIds.first) + ?.isOnlyAdmin() ?? + false; + final confirmed = await showOkCancelAlertDialog( + useRootNavigator: false, + context: context, + title: L10n.of(context)!.areYouSure, + okLabel: L10n.of(context)!.yes, + cancelLabel: L10n.of(context)!.cancel, + message: onlyAdmin && selectedRoomIds.length == 1 + ? L10n.of(context)!.onlyAdminDescription + : L10n.of(context)!.leaveRoomDescription, + ) == + OkCancelResult.ok; + if (!confirmed) return; + final bool leftActiveRoom = + selectedRoomIds.contains(Matrix.of(context).activeRoomId); + await showFutureLoadingDialog( + context: context, + future: () => _leaveSelectedRooms(onlyAdmin), + ); + setState(() {}); + if (leftActiveRoom) { + context.go('/rooms'); + } + } + // Pangea# + void dismissStatusList() async { final result = await showOkCancelAlertDialog( title: L10n.of(context)!.hidePresences, @@ -729,17 +768,35 @@ class ChatListController extends State final roomId = selectedRoomIds.first; try { // #Pangea - if (client.getRoomById(roomId)!.isUnread) { - await client.getRoomById(roomId)!.markUnread(false); - } + // await client.getRoomById(roomId)!.leave(); + await client.getRoomById(roomId)!.archive(); // Pangea# - await client.getRoomById(roomId)!.leave(); } finally { toggleSelection(roomId); } } } + // #Pangea + Future _leaveSelectedRooms(bool onlyAdmin) async { + final client = Matrix.of(context).client; + while (selectedRoomIds.isNotEmpty) { + final roomId = selectedRoomIds.first; + try { + final room = client.getRoomById(roomId); + if (!room!.isSpace && + room.membership == Membership.join && + room.isUnread) { + await room.markUnread(false); + } + onlyAdmin ? await room.archive() : await room.leave(); + } finally { + toggleSelection(roomId); + } + } + } + // Pangea# + Future addToSpace() async { final selectedSpace = await showConfirmationDialog( context: context, diff --git a/lib/pages/chat_list/chat_list_header.dart b/lib/pages/chat_list/chat_list_header.dart index c5973d8bb..20f41ea08 100644 --- a/lib/pages/chat_list/chat_list_header.dart +++ b/lib/pages/chat_list/chat_list_header.dart @@ -1,6 +1,8 @@ import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat_list/chat_list.dart'; import 'package:fluffychat/pages/chat_list/client_chooser_button.dart'; +import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; +import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; @@ -168,14 +170,39 @@ class ChatListHeader extends StatelessWidget implements PreferredSizeWidget { tooltip: L10n.of(context)!.toggleMuted, onPressed: controller.toggleMuted, ), - IconButton( - // #Pangea - // icon: const Icon(Icons.delete_outlined), - icon: const Icon(Icons.archive_outlined), + // #Pangea + if (controller.selectedRoomIds.length > 1) + IconButton( + icon: const Icon(Icons.arrow_forward), + tooltip: L10n.of(context)!.leave, + onPressed: controller.leaveAction, + ), + if (controller.selectedRoomIds.length == 1 && + !(Matrix.of(context) + .client + .getRoomById(controller.selectedRoomIds.single) + ?.isRoomAdmin ?? + false)) + IconButton( + icon: const Icon(Icons.arrow_forward), + tooltip: L10n.of(context)!.leave, + onPressed: controller.leaveAction, + ), + if (controller.selectedRoomIds.length == 1 && + (Matrix.of(context) + .client + .getRoomById(controller.selectedRoomIds.single) + ?.isRoomAdmin ?? + false)) // Pangea# - tooltip: L10n.of(context)!.archive, - onPressed: controller.archiveAction, - ), + IconButton( + // #Pangea + // icon: const Icon(Icons.delete_outlined), + icon: const Icon(Icons.archive_outlined), + // Pangea# + tooltip: L10n.of(context)!.archive, + onPressed: controller.archiveAction, + ), ] : null, ); diff --git a/lib/pages/chat_list/chat_list_item.dart b/lib/pages/chat_list/chat_list_item.dart index c917c8eff..927b97a79 100644 --- a/lib/pages/chat_list/chat_list_item.dart +++ b/lib/pages/chat_list/chat_list_item.dart @@ -53,14 +53,12 @@ class ChatListItem extends StatelessWidget { message: L10n.of(context)!.archiveRoomDescription, ); if (confirmed == OkCancelResult.cancel) return; - // #Pangea - if (room.isUnread) { - await room.markUnread(false); - } - // Pangea# await showFutureLoadingDialog( context: context, - future: () => room.leave(), + // #Pangea + // future: () => room.leave(), + future: () => room.archive(), + // Pangea# ); return; } diff --git a/lib/pages/chat_list/space_view.dart b/lib/pages/chat_list/space_view.dart index ff6582e94..2cdfed64a 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -11,7 +11,6 @@ import 'package:fluffychat/pangea/constants/class_default_values.dart'; import 'package:fluffychat/pangea/constants/pangea_room_types.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; import 'package:fluffychat/pangea/extensions/sync_update_extension.dart'; -import 'package:fluffychat/pangea/utils/archive_space.dart'; import 'package:fluffychat/pangea/utils/chat_list_handle_space_tap.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:fluffychat/widgets/avatar.dart'; @@ -148,7 +147,10 @@ class _SpaceViewState extends State { if (activeSpace != null) { await setChatCount( activeSpace, - _lastResponse[activeSpaceId], + _lastResponse[activeSpaceId] ?? + GetSpaceHierarchyResponse( + rooms: [], + ), ); } // Pangea# @@ -267,13 +269,16 @@ class _SpaceViewState extends State { label: L10n.of(context)!.addToSpace, icon: Icons.workspaces_outlined, ), - if (room != null && room.isRoomAdmin) + if (room != null && + room.isRoomAdmin && + room.membership != Membership.leave) SheetAction( key: SpaceChildContextAction.archive, label: room.isSpace ? L10n.of(context)!.archiveSpace : L10n.of(context)!.archive, icon: Icons.architecture_outlined, + isDestructiveAction: true, ), // if (room != null) if (room != null && room.membership != Membership.leave) @@ -296,22 +301,32 @@ class _SpaceViewState extends State { _onJoinSpaceChild(spaceChild!); break; case SpaceChildContextAction.leave: - await showFutureLoadingDialog( - context: context, - // #Pangea - // future: room!.leave, - future: () async { - if (room!.isUnread) { - await room.markUnread(false); - } - await room.leave(); - if (Matrix.of(context).activeRoomId == room.id) { - context.go('/rooms'); - } - }, - // Pangea# - ); + // #Pangea + widget.controller.cancelAction(); + if (room == null) return; + if (room.isSpace) { + await room.isOnlyAdmin() + ? await room.archiveSpace( + context, + Matrix.of(context).client, + onlyAdmin: true, + ) + : await room.leaveSpace( + context, + Matrix.of(context).client, + ); + } else { + widget.controller.toggleSelection(room.id); + await widget.controller.leaveAction(); + } + _refresh(); break; + // await showFutureLoadingDialog( + // context: context, + // future: room!.leave, + // ); + // break; + // Pangea# case SpaceChildContextAction.removeFromSpace: await showFutureLoadingDialog( context: context, @@ -324,19 +339,6 @@ class _SpaceViewState extends State { // #Pangea if (room == null || room.membership == Membership.leave) return; // Pangea# - widget.controller.toggleSelection(room.id); - room.isSpace - ? await showFutureLoadingDialog( - context: context, - future: () async { - await archiveSpace( - room, - Matrix.of(context).client, - ); - widget.controller.selectedRoomIds.clear(); - }, - ) - : await widget.controller.archiveAction(); _refresh(); break; case SpaceChildContextAction.addToSpace: @@ -346,8 +348,10 @@ class _SpaceViewState extends State { // Pangea# widget.controller.toggleSelection(room.id); await widget.controller.addToSpace(); + // #Pangea + setState(() => widget.controller.selectedRoomIds.clear()); + // Pangea# break; - // Pangea# } } @@ -598,7 +602,7 @@ class _SpaceViewState extends State { MatrixLocals(L10n.of(context)!), ); return Material( - color: Theme.of(context).colorScheme.background, + color: Theme.of(context).colorScheme.surface, child: ListTile( leading: Avatar( mxContent: rootSpace.avatar, @@ -933,7 +937,7 @@ class _SpaceViewState extends State { : L10n.of(context)!.enterRoom), maxLines: 1, style: TextStyle( - color: Theme.of(context).colorScheme.onBackground, + color: Theme.of(context).colorScheme.onSurface, ), ), trailing: isSpace diff --git a/lib/pages/chat_list/utils/on_chat_tap.dart b/lib/pages/chat_list/utils/on_chat_tap.dart index a54da9c89..f7daa248e 100644 --- a/lib/pages/chat_list/utils/on_chat_tap.dart +++ b/lib/pages/chat_list/utils/on_chat_tap.dart @@ -47,7 +47,9 @@ void onChatTap(Room room, BuildContext context) async { } if (inviteAction == InviteActions.decline) { // #Pangea - if (room.isUnread) { + if (!room.isSpace && + room.membership == Membership.join && + room.isUnread) { await room.markUnread(false); } // Pangea# diff --git a/lib/pages/invitation_selection/invitation_selection.dart b/lib/pages/invitation_selection/invitation_selection.dart index 368bf0584..5b69e7049 100644 --- a/lib/pages/invitation_selection/invitation_selection.dart +++ b/lib/pages/invitation_selection/invitation_selection.dart @@ -159,6 +159,8 @@ class InvitationSelectionController extends State { future: () async { if (mode == InvitationSelectionMode.admin) { await inviteTeacherAction(room, id); + } else { + await room.invite(id); } }, // Pangea# diff --git a/lib/pages/new_group/new_group.dart b/lib/pages/new_group/new_group.dart index 47b07b09d..b3a1703af 100644 --- a/lib/pages/new_group/new_group.dart +++ b/lib/pages/new_group/new_group.dart @@ -134,9 +134,7 @@ class NewGroupController extends State { powerLevelContentOverride: await ClassChatPowerLevels.powerLevelOverrideForClassChat( context, - addToSpaceKey.currentState!.parents - .map((suggestionStatus) => suggestionStatus.room) - .toList(), + addToSpaceKey.currentState!.parents, ), invite: [ if (addConversationBotKey.currentState?.addBot ?? false) diff --git a/lib/pages/new_space/new_space.dart b/lib/pages/new_space/new_space.dart index eebc0074b..1842a1695 100644 --- a/lib/pages/new_space/new_space.dart +++ b/lib/pages/new_space/new_space.dart @@ -179,9 +179,7 @@ class NewSpaceController extends State { powerLevelContentOverride: addToSpaceKey.currentState != null ? await ClassChatPowerLevels.powerLevelOverrideForClassChat( context, - addToSpaceKey.currentState!.parents - .map((suggestionStatus) => suggestionStatus.room) - .toList(), + addToSpaceKey.currentState!.parents, ) : null, // initialState: [ diff --git a/lib/pages/settings/settings.dart b/lib/pages/settings/settings.dart index 0e32fe478..55b39fa3d 100644 --- a/lib/pages/settings/settings.dart +++ b/lib/pages/settings/settings.dart @@ -1,18 +1,17 @@ import 'dart:async'; -import 'package:flutter/material.dart'; - import 'package:adaptive_dialog/adaptive_dialog.dart'; import 'package:collection/collection.dart'; import 'package:file_picker/file_picker.dart'; +import 'package:fluffychat/pangea/utils/logout.dart'; +import 'package:fluffychat/utils/platform_infos.dart'; +import 'package:fluffychat/widgets/app_lock.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:image_picker/image_picker.dart'; import 'package:matrix/matrix.dart'; -import 'package:fluffychat/pangea/utils/logout.dart'; -import 'package:fluffychat/utils/platform_infos.dart'; -import 'package:fluffychat/widgets/app_lock.dart'; import '../../widgets/matrix.dart'; import 'settings_view.dart'; @@ -171,6 +170,10 @@ class SettingsController extends State { // Pangea# super.initState(); + // #Pangea + profileUpdated = true; + profileFuture = null; + // Pangea# } void checkBootstrap() async { diff --git a/lib/pangea/choreographer/controllers/choreographer.dart b/lib/pangea/choreographer/controllers/choreographer.dart index 973ca6984..3d84b0e1d 100644 --- a/lib/pangea/choreographer/controllers/choreographer.dart +++ b/lib/pangea/choreographer/controllers/choreographer.dart @@ -13,6 +13,7 @@ import 'package:fluffychat/pangea/enum/edit_type.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; import 'package:fluffychat/pangea/models/class_model.dart'; import 'package:fluffychat/pangea/models/it_step.dart'; +import 'package:fluffychat/pangea/models/language_detection_model.dart'; import 'package:fluffychat/pangea/models/representation_content_model.dart'; import 'package:fluffychat/pangea/models/tokens_event_content_model.dart'; import 'package:fluffychat/pangea/utils/any_state_holder.dart'; @@ -51,7 +52,7 @@ class Choreographer { // last checked by IGC or translation String? _lastChecked; ChoreoMode choreoMode = ChoreoMode.igc; - final StreamController stateListener = StreamController(); + final StreamController stateListener = StreamController.broadcast(); StreamSubscription? trialStream; Choreographer(this.pangeaController, this.chatController) { @@ -93,7 +94,7 @@ class Choreographer { } } - void _sendWithIGC(BuildContext context) { + Future _sendWithIGC(BuildContext context) async { if (igc.canSendMessage) { final PangeaRepresentation? originalWritten = choreoRecord.includedIT && itController.sourceText != null @@ -105,7 +106,6 @@ class Choreographer { ) : null; - // PTODO - just put this in original message event final PangeaRepresentation originalSent = PangeaRepresentation( langCode: langCodeOfCurrentText ?? LanguageKeys.unknownLanguage, text: currentText, @@ -115,6 +115,22 @@ class Choreographer { final ChoreoRecord? applicableChoreo = isITandIGCEnabled && igc.igcTextData != null ? choreoRecord : null; + // if the message has not been processed to determine its language + // then run it through the language detection endpoint. If the detection + // confidence is high enough, use that language code as the message's language + // to save that pangea representation + if (applicableChoreo == null) { + final resp = await pangeaController.languageDetection.detectLanguage( + currentText, + pangeaController.languageController.userL2?.langCode, + pangeaController.languageController.userL1?.langCode, + ); + final LanguageDetection? bestDetection = resp.bestDetection(); + if (bestDetection != null) { + originalSent.langCode = bestDetection.langCode; + } + } + final UseType useType = useTypeCalculator(applicableChoreo); debugPrint("use type in choreographer $useType"); @@ -205,14 +221,18 @@ class Choreographer { textController.editType = EditType.keyboard; } - Future getLanguageHelp([bool tokensOnly = false]) async { + Future getLanguageHelp([ + bool tokensOnly = false, + bool manual = false, + ]) async { try { if (errorService.isError) return; final CanSendStatus canSendStatus = pangeaController.subscriptionController.canSendStatus; if (canSendStatus != CanSendStatus.subscribed || - (!igcEnabled && !itEnabled)) { + (!igcEnabled && !itEnabled) || + (!isAutoIGCEnabled && !manual && choreoMode != ChoreoMode.it)) { return; } @@ -525,14 +545,50 @@ class Choreographer { chatController.room, ); - bool get translationEnabled => - pangeaController.permissionsController.isToolEnabled( - ToolSetting.translations, - chatController.room, - ); + // bool get translationEnabled => + // pangeaController.permissionsController.isToolEnabled( + // ToolSetting.translations, + // chatController.room, + // ); bool get isITandIGCEnabled => pangeaController.permissionsController.isWritingAssistanceEnabled( chatController.room, ); + + bool get isAutoIGCEnabled => + pangeaController.permissionsController.isToolEnabled( + ToolSetting.autoIGC, + chatController.room, + ); + + AssistanceState get assistanceState { + if (currentText.isEmpty && itController.sourceText == null) { + return AssistanceState.noMessage; + } + + if (igc.igcTextData?.matches.isNotEmpty ?? false) { + return AssistanceState.fetched; + } + + if (isFetching) { + return AssistanceState.fetching; + } + + if (igc.igcTextData == null) { + return AssistanceState.notFetched; + } + + return AssistanceState.complete; + } +} + +// assistance state is, user has not typed a message, user has typed a message and IGC has not run, +// IGC is running, IGC has run and there are remaining steps (either IT or IGC), or all steps are done +enum AssistanceState { + noMessage, + notFetched, + fetching, + fetched, + complete, } diff --git a/lib/pangea/choreographer/controllers/igc_controller.dart b/lib/pangea/choreographer/controllers/igc_controller.dart index 646543f22..b0c8dd462 100644 --- a/lib/pangea/choreographer/controllers/igc_controller.dart +++ b/lib/pangea/choreographer/controllers/igc_controller.dart @@ -19,14 +19,37 @@ import '../../repo/tokens_repo.dart'; import '../../utils/error_handler.dart'; import '../../utils/overlay.dart'; +class _SpanDetailsCacheItem { + SpanDetailsRepoReqAndRes data; + + _SpanDetailsCacheItem({required this.data}); +} + class IgcController { Choreographer choreographer; IGCTextData? igcTextData; Object? igcError; Completer igcCompleter = Completer(); + final Map _cache = {}; + Timer? _cacheClearTimer; - IgcController(this.choreographer); + IgcController(this.choreographer) { + _initializeCacheClearing(); + } + + void _initializeCacheClearing() { + const duration = Duration(minutes: 2); + _cacheClearTimer = Timer.periodic(duration, (Timer t) => _clearCache()); + } + + void _clearCache() { + _cache.clear(); + } + + void dispose() { + _cacheClearTimer?.cancel(); + } Future getIGCTextData({required bool tokensOnly}) async { try { @@ -80,6 +103,14 @@ class IgcController { igcTextData = igcTextDataResponse; + // After fetching igc data, pre-call span details for each match optimistically. + // This will make the loading of span details faster for the user + if (igcTextData?.matches.isNotEmpty ?? false) { + for (int i = 0; i < igcTextData!.matches.length; i++) { + getSpanDetails(i); + } + } + debugPrint("igc text ${igcTextData.toString()}"); } catch (err, stack) { debugger(when: kDebugMode); @@ -99,18 +130,38 @@ class IgcController { debugger(when: kDebugMode); return; } - final SpanData span = igcTextData!.matches[matchIndex].match; - final SpanDetailsRepoReqAndRes response = await SpanDataRepo.getSpanDetails( - await choreographer.accessToken, - request: SpanDetailsRepoReqAndRes( - userL1: choreographer.l1LangCode!, - userL2: choreographer.l2LangCode!, - enableIGC: choreographer.igcEnabled, - enableIT: choreographer.itEnabled, - span: span, - ), + /// Retrieves the span data from the `igcTextData` matches at the specified `matchIndex`. + /// Creates a `SpanDetailsRepoReqAndRes` object with the retrieved span data and other parameters. + /// Generates a cache key based on the created `SpanDetailsRepoReqAndRes` object. + final SpanData span = igcTextData!.matches[matchIndex].match; + final req = SpanDetailsRepoReqAndRes( + userL1: choreographer.l1LangCode!, + userL2: choreographer.l2LangCode!, + enableIGC: choreographer.igcEnabled, + enableIT: choreographer.itEnabled, + span: span, ); + final int cacheKey = req.hashCode; + + /// Retrieves the [SpanDetailsRepoReqAndRes] response from the cache if it exists, + /// otherwise makes an API call to get the response and stores it in the cache. + SpanDetailsRepoReqAndRes response; + if (_cache.containsKey(cacheKey)) { + response = _cache[cacheKey]!.data; + } else { + response = await SpanDataRepo.getSpanDetails( + await choreographer.accessToken, + request: SpanDetailsRepoReqAndRes( + userL1: choreographer.l1LangCode!, + userL2: choreographer.l2LangCode!, + enableIGC: choreographer.igcEnabled, + enableIT: choreographer.itEnabled, + span: span, + ), + ); + _cache[cacheKey] = _SpanDetailsCacheItem(data: response); + } try { igcTextData!.matches[matchIndex].match = response.span; diff --git a/lib/pangea/choreographer/controllers/it_controller.dart b/lib/pangea/choreographer/controllers/it_controller.dart index 6d5f8e347..61c8c2312 100644 --- a/lib/pangea/choreographer/controllers/it_controller.dart +++ b/lib/pangea/choreographer/controllers/it_controller.dart @@ -183,7 +183,7 @@ class ITController { } } - FuturegetNextTranslationData() async { + Future getNextTranslationData() async { try { if (completedITSteps.length < goldRouteTracker.continuances.length) { final String currentText = choreographer.currentText; @@ -478,5 +478,5 @@ class CurrentITStep { // get continuance with highest level Continuance get best => - continuances.reduce((a, b) => a.level > b.level ? a : b); + continuances.reduce((a, b) => a.level < b.level ? a : b); } diff --git a/lib/pangea/choreographer/widgets/language_display_toggle.dart b/lib/pangea/choreographer/widgets/language_display_toggle.dart index 9478b4220..bc0efd492 100644 --- a/lib/pangea/choreographer/widgets/language_display_toggle.dart +++ b/lib/pangea/choreographer/widgets/language_display_toggle.dart @@ -17,9 +17,9 @@ class LanguageDisplayToggle extends StatelessWidget { @override Widget build(BuildContext context) { - if (!controller.choreographer.translationEnabled) { - return const SizedBox(); - } + // if (!controller.choreographer.translationEnabled) { + // return const SizedBox(); + // } return Container( decoration: BoxDecoration( shape: BoxShape.circle, diff --git a/lib/pangea/choreographer/widgets/send_button.dart b/lib/pangea/choreographer/widgets/send_button.dart index 045f0c516..fa3189df5 100644 --- a/lib/pangea/choreographer/widgets/send_button.dart +++ b/lib/pangea/choreographer/widgets/send_button.dart @@ -1,8 +1,7 @@ +import 'package:fluffychat/pangea/constants/colors.dart'; import 'package:flutter/material.dart'; - import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:fluffychat/pangea/constants/colors.dart'; import '../../../pages/chat/chat.dart'; class ChoreographerSendButton extends StatelessWidget { @@ -16,7 +15,8 @@ class ChoreographerSendButton extends StatelessWidget { @override Widget build(BuildContext context) { // commit for cicd - return controller.choreographer.isFetching + return controller.choreographer.isFetching && + controller.choreographer.isAutoIGCEnabled ? Container( height: 56, width: 56, @@ -28,7 +28,8 @@ class ChoreographerSendButton extends StatelessWidget { alignment: Alignment.center, child: IconButton( icon: const Icon(Icons.send_outlined), - color: controller.choreographer.igc.canSendMessage + color: controller.choreographer.igc.canSendMessage || + !controller.choreographer.isAutoIGCEnabled ? null : PangeaColors.igcError, onPressed: () { diff --git a/lib/pangea/choreographer/widgets/start_igc_button.dart b/lib/pangea/choreographer/widgets/start_igc_button.dart new file mode 100644 index 000000000..5f786306f --- /dev/null +++ b/lib/pangea/choreographer/widgets/start_igc_button.dart @@ -0,0 +1,159 @@ +import 'dart:async'; +import 'dart:math' as math; + +import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart'; +import 'package:fluffychat/pangea/constants/colors.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; + +import '../../../pages/chat/chat.dart'; + +class StartIGCButton extends StatefulWidget { + const StartIGCButton({ + super.key, + required this.controller, + }); + + final ChatController controller; + + @override + State createState() => StartIGCButtonState(); +} + +class StartIGCButtonState extends State + with SingleTickerProviderStateMixin { + AssistanceState get assistanceState => + widget.controller.choreographer.assistanceState; + AnimationController? _controller; + StreamSubscription? choreoListener; + AssistanceState? prevState; + + @override + void initState() { + _controller = AnimationController( + vsync: this, + duration: const Duration(seconds: 2), + ); + choreoListener = widget.controller.choreographer.stateListener.stream + .listen(updateSpinnerState); + super.initState(); + } + + void updateSpinnerState(_) { + if (prevState != AssistanceState.fetching && + assistanceState == AssistanceState.fetching) { + _controller?.repeat(); + } else if (prevState == AssistanceState.fetching && + assistanceState != AssistanceState.fetching) { + _controller?.stop(); + _controller?.reverse(); + } + setState(() => prevState = assistanceState); + } + + @override + Widget build(BuildContext context) { + if (widget.controller.choreographer.isAutoIGCEnabled || + widget.controller.choreographer.choreoMode == ChoreoMode.it) { + return const SizedBox.shrink(); + } + + final Widget icon = Icon( + Icons.autorenew_rounded, + size: 46, + color: assistanceState.stateColor(context), + ); + + return SizedBox( + height: 50, + width: 50, + child: FloatingActionButton( + tooltip: assistanceState.tooltip( + L10n.of(context)!, + ), + backgroundColor: Theme.of(context).scaffoldBackgroundColor, + disabledElevation: 0, + shape: const CircleBorder(), + onPressed: () { + if (assistanceState != AssistanceState.complete) { + widget.controller.choreographer + .getLanguageHelp( + false, + true, + ) + .then((_) { + if (widget.controller.choreographer.igc.igcTextData != null && + widget.controller.choreographer.igc.igcTextData!.matches + .isNotEmpty) { + widget.controller.choreographer.igc.showFirstMatch(context); + } + }); + } + }, + child: Stack( + alignment: Alignment.center, + children: [ + _controller != null + ? RotationTransition( + turns: Tween(begin: 0.0, end: math.pi * 2) + .animate(_controller!), + child: icon, + ) + : icon, + Container( + width: 26, + height: 26, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Theme.of(context).scaffoldBackgroundColor, + ), + ), + Container( + width: 20, + height: 20, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: assistanceState.stateColor(context), + ), + ), + Icon( + size: 16, + Icons.check, + color: Theme.of(context).scaffoldBackgroundColor, + ), + ], + ), + ), + ); + } +} + +extension AssistanceStateExtension on AssistanceState { + Color stateColor(context) { + switch (this) { + case AssistanceState.noMessage: + case AssistanceState.notFetched: + case AssistanceState.fetching: + return Theme.of(context).colorScheme.primary; + case AssistanceState.fetched: + return PangeaColors.igcError; + case AssistanceState.complete: + return AppConfig.success; + } + } + + String tooltip(L10n l10n) { + switch (this) { + case AssistanceState.noMessage: + case AssistanceState.notFetched: + return l10n.runGrammarCorrection; + case AssistanceState.fetching: + return ""; + case AssistanceState.fetched: + return l10n.grammarCorrectionFailed; + case AssistanceState.complete: + return l10n.grammarCorrectionComplete; + } + } +} diff --git a/lib/pangea/constants/model_keys.dart b/lib/pangea/constants/model_keys.dart index ce427f3d3..ef84a7064 100644 --- a/lib/pangea/constants/model_keys.dart +++ b/lib/pangea/constants/model_keys.dart @@ -54,6 +54,7 @@ class ModelKey { static const String offset = "offset"; static const String length = "length"; static const String langCode = 'lang_code'; + static const String confidence = 'confidence'; // some old analytics rooms have langCode instead of lang_code in the room creation content static const String oldLangCode = 'langCode'; static const String wordLang = "word_lang"; diff --git a/lib/pangea/controllers/class_controller.dart b/lib/pangea/controllers/class_controller.dart index 8febee1cb..1e2febf21 100644 --- a/lib/pangea/controllers/class_controller.dart +++ b/lib/pangea/controllers/class_controller.dart @@ -28,15 +28,16 @@ class ClassController extends BaseController { _pangeaController = pangeaController; } - setActiveSpaceIdInChatListController(String classId) { + setActiveSpaceIdInChatListController(String? classId) { setState(data: {"activeSpaceId": classId}); } Future fixClassPowerLevels() async { try { final List> classFixes = []; - for (final room in (await _pangeaController - .matrixState.client.classesAndExchangesImTeaching)) { + final teacherSpaces = await _pangeaController + .matrixState.client.classesAndExchangesImTeaching; + for (final room in teacherSpaces) { classFixes.add(room.setClassPowerLevels()); } await Future.wait(classFixes); diff --git a/lib/pangea/controllers/language_detection_controller.dart b/lib/pangea/controllers/language_detection_controller.dart index 0ff18b556..4ddfabc88 100644 --- a/lib/pangea/controllers/language_detection_controller.dart +++ b/lib/pangea/controllers/language_detection_controller.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'package:fluffychat/pangea/config/environment.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; +import 'package:fluffychat/pangea/models/language_detection_model.dart'; import 'package:fluffychat/pangea/network/urls.dart'; import 'package:http/http.dart' as http; @@ -48,7 +49,7 @@ class LanguageDetectionRequest { } class LanguageDetectionResponse { - List> detections; + List detections; String fullText; LanguageDetectionResponse({ @@ -58,7 +59,11 @@ class LanguageDetectionResponse { factory LanguageDetectionResponse.fromJson(Map json) { return LanguageDetectionResponse( - detections: List>.from(json['detections']), + detections: List.from( + json['detections'].map( + (e) => LanguageDetection.fromJson(e), + ), + ), fullText: json['full_text'], ); } @@ -69,6 +74,20 @@ class LanguageDetectionResponse { 'full_text': fullText, }; } + + LanguageDetection? get _bestDetection { + detections.sort((a, b) => b.confidence.compareTo(a.confidence)); + return detections.isNotEmpty ? detections.first : null; + } + + final double _confidenceThreshold = 0.95; + + LanguageDetection? bestDetection({double? threshold}) { + threshold ??= _confidenceThreshold; + return (_bestDetection?.confidence ?? 0) >= _confidenceThreshold + ? _bestDetection! + : null; + } } class _LanguageDetectionCacheItem { @@ -103,6 +122,19 @@ class LanguageDetectionController { _cacheClearTimer?.cancel(); } + Future detectLanguage( + String fullText, + String? userL2, + String? userL1, + ) async { + final LanguageDetectionRequest params = LanguageDetectionRequest( + fullText: fullText, + userL1: userL1, + userL2: userL2, + ); + return get(params); + } + Future get( LanguageDetectionRequest params, ) async { diff --git a/lib/pangea/controllers/local_settings.dart b/lib/pangea/controllers/local_settings.dart index 5984a7bf5..2dc30cfe7 100644 --- a/lib/pangea/controllers/local_settings.dart +++ b/lib/pangea/controllers/local_settings.dart @@ -8,8 +8,14 @@ class LocalSettings { _pangeaController = pangeaController; } - bool userLanguageToolSetting(ToolSetting setting) => - _pangeaController.pStoreService.read(setting.toString()) ?? true; + bool userLanguageToolSetting(ToolSetting setting) { + final profileSetting = + _pangeaController.pStoreService.read(setting.toString()); + if (profileSetting != null) { + return profileSetting; + } + return setting == ToolSetting.immersionMode ? false : true; + } // bool get userEnableIT => // _pangeaController.pStoreService.read(ToolSetting.interactiveTranslator.toString()) ?? true; diff --git a/lib/pangea/controllers/pangea_controller.dart b/lib/pangea/controllers/pangea_controller.dart index ad2a27145..68cfc59fc 100644 --- a/lib/pangea/controllers/pangea_controller.dart +++ b/lib/pangea/controllers/pangea_controller.dart @@ -248,29 +248,11 @@ class PangeaController { if (!userIds.contains(BotName.byEnvironment)) { try { await space.invite(BotName.byEnvironment); - await space.postLoad(); - await space.setPower( - BotName.byEnvironment, - ClassDefaultValues.powerLevelOfAdmin, - ); } catch (err) { ErrorHandler.logError( e: "Failed to invite pangea bot to space ${space.id}", ); } - } else if (space.getPowerLevelByUserId(BotName.byEnvironment) < - ClassDefaultValues.powerLevelOfAdmin) { - try { - await space.postLoad(); - await space.setPower( - BotName.byEnvironment, - ClassDefaultValues.powerLevelOfAdmin, - ); - } catch (err) { - ErrorHandler.logError( - e: "Failed to reset power level for pangea bot in space ${space.id}", - ); - } } } } diff --git a/lib/pangea/controllers/user_controller.dart b/lib/pangea/controllers/user_controller.dart index d3a17d365..c7a35b3e8 100644 --- a/lib/pangea/controllers/user_controller.dart +++ b/lib/pangea/controllers/user_controller.dart @@ -131,7 +131,7 @@ class UserController extends BaseController { final bool? immersionMode = migratedProfileInfo(MatrixProfile.immersionMode); final bool? definitions = migratedProfileInfo(MatrixProfile.definitions); - final bool? translations = migratedProfileInfo(MatrixProfile.translations); + // final bool? translations = migratedProfileInfo(MatrixProfile.translations); final bool? showItInstructions = migratedProfileInfo(MatrixProfile.showedItInstructions); final bool? showClickMessage = @@ -147,7 +147,7 @@ class UserController extends BaseController { interactiveGrammar: interactiveGrammar, immersionMode: immersionMode, definitions: definitions, - translations: translations, + // translations: translations, showedItInstructions: showItInstructions, showedClickMessage: showClickMessage, showedBlurMeansTranslate: showBlurMeansTranslate, @@ -228,10 +228,11 @@ class UserController extends BaseController { bool? interactiveGrammar, bool? immersionMode, bool? definitions, - bool? translations, + // bool? translations, bool? showedItInstructions, bool? showedClickMessage, bool? showedBlurMeansTranslate, + bool? showedTooltipInstructions, String? createdAt, String? targetLanguage, String? sourceLanguage, @@ -280,12 +281,12 @@ class UserController extends BaseController { definitions, ); } - if (translations != null) { - await _pangeaController.pStoreService.save( - MatrixProfile.translations.title, - translations, - ); - } + // if (translations != null) { + // await _pangeaController.pStoreService.save( + // MatrixProfile.translations.title, + // translations, + // ); + // } if (showedItInstructions != null) { await _pangeaController.pStoreService.save( MatrixProfile.showedItInstructions.title, @@ -304,6 +305,12 @@ class UserController extends BaseController { showedBlurMeansTranslate, ); } + if (showedTooltipInstructions != null) { + await _pangeaController.pStoreService.save( + MatrixProfile.showedTooltipInstructions.title, + showedTooltipInstructions, + ); + } if (createdAt != null) { await _pangeaController.pStoreService.save( MatrixProfile.createdAt.title, diff --git a/lib/pangea/extensions/pangea_room_extension/class_and_exchange_settings_extension.dart b/lib/pangea/extensions/pangea_room_extension/class_and_exchange_settings_extension.dart index 02888710e..e71ed8f4f 100644 --- a/lib/pangea/extensions/pangea_room_extension/class_and_exchange_settings_extension.dart +++ b/lib/pangea/extensions/pangea_room_extension/class_and_exchange_settings_extension.dart @@ -62,13 +62,19 @@ extension ClassAndExchangeSettingsRoomExtension on Room { } final Event? currentPower = getState(EventTypes.RoomPowerLevels); final Map? currentPowerContent = - currentPower?.content["events"] as Map?; - final spaceChildPower = currentPowerContent?[EventTypes.spaceChild]; - final studentAnalyticsPower = - currentPowerContent?[PangeaEventTypes.studentAnalyticsSummary]; + currentPower?.content as Map?; + if (currentPowerContent == null) { + return; + } + if (!(currentPowerContent.containsKey("events"))) { + currentPowerContent["events"] = {}; + } + final spaceChildPower = + currentPowerContent["events"][EventTypes.spaceChild]; + final studentAnalyticsPower = currentPowerContent["events"] + [PangeaEventTypes.studentAnalyticsSummary]; - if ((spaceChildPower == null || studentAnalyticsPower == null) && - currentPowerContent != null) { + if ((spaceChildPower == null || studentAnalyticsPower == null)) { currentPowerContent["events"][EventTypes.spaceChild] = 0; currentPowerContent["events"] [PangeaEventTypes.studentAnalyticsSummary] = 0; diff --git a/lib/pangea/extensions/pangea_room_extension/events_extension.dart b/lib/pangea/extensions/pangea_room_extension/events_extension.dart index ac7133311..30f994852 100644 --- a/lib/pangea/extensions/pangea_room_extension/events_extension.dart +++ b/lib/pangea/extensions/pangea_room_extension/events_extension.dart @@ -15,6 +15,102 @@ extension EventsRoomExtension on Room { return false; } + Future _archive() async { + final students = (await requestParticipants()) + .where( + (e) => + e.id != client.userID && + e.powerLevel < ClassDefaultValues.powerLevelOfAdmin && + e.id != BotName.byEnvironment, + ) + .toList(); + try { + for (final student in students) { + await kick(student.id); + } + if (!isSpace && membership == Membership.join && isUnread) { + await markUnread(false); + } + await leave(); + } catch (err, s) { + debugger(when: kDebugMode); + ErrorHandler.logError(e: err, s: s, data: toJson()); + } + } + + Future _archiveSpace( + BuildContext context, + Client client, { + bool onlyAdmin = false, + }) async { + final confirmed = await showOkCancelAlertDialog( + useRootNavigator: false, + context: context, + title: L10n.of(context)!.areYouSure, + okLabel: L10n.of(context)!.yes, + cancelLabel: L10n.of(context)!.cancel, + message: onlyAdmin + ? L10n.of(context)!.onlyAdminDescription + : L10n.of(context)!.archiveSpaceDescription, + ) == + OkCancelResult.ok; + if (!confirmed) return false; + final success = await showFutureLoadingDialog( + context: context, + future: () async { + final List children = await getChildRooms(); + for (final Room child in children) { + await child.archive(); + } + await archive(); + }, + ); + MatrixState.pangeaController.classController + .setActiveSpaceIdInChatListController( + null, + ); + return success.error == null; + } + + Future _leaveSpace(BuildContext context, Client client) async { + final confirmed = await showOkCancelAlertDialog( + useRootNavigator: false, + context: context, + title: L10n.of(context)!.areYouSure, + okLabel: L10n.of(context)!.yes, + cancelLabel: L10n.of(context)!.cancel, + message: L10n.of(context)!.leaveSpaceDescription, + ) == + OkCancelResult.ok; + if (!confirmed) return false; + final success = await showFutureLoadingDialog( + context: context, + future: () async { + try { + final List children = await getChildRooms(); + for (final Room child in children) { + if (!child.isSpace && + child.membership == Membership.join && + child.isUnread) { + await child.markUnread(false); + } + await child.leave(); + } + await leave(); + } catch (err, stack) { + debugger(when: kDebugMode); + ErrorHandler.logError(e: err, s: stack, data: powerLevels); + rethrow; + } + }, + ); + MatrixState.pangeaController.classController + .setActiveSpaceIdInChatListController( + null, + ); + return success.error == null; + } + Future _sendPangeaEvent({ required Map content, required String parentEventId, diff --git a/lib/pangea/extensions/pangea_room_extension/pangea_room_extension.dart b/lib/pangea/extensions/pangea_room_extension/pangea_room_extension.dart index c557393e7..a203619ac 100644 --- a/lib/pangea/extensions/pangea_room_extension/pangea_room_extension.dart +++ b/lib/pangea/extensions/pangea_room_extension/pangea_room_extension.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:developer'; +import 'package:adaptive_dialog/adaptive_dialog.dart'; import 'package:collection/collection.dart'; import 'package:fluffychat/pangea/constants/class_default_values.dart'; import 'package:fluffychat/pangea/constants/model_keys.dart'; @@ -11,8 +12,11 @@ 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/error_handler.dart'; +import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:future_loading_dialog/future_loading_dialog.dart'; // import markdown.dart import 'package:html_unescape/html_unescape.dart'; import 'package:matrix/matrix.dart'; @@ -139,6 +143,17 @@ extension PangeaRoom on Room { // events Future leaveIfFull() async => await _leaveIfFull(); + Future archive() async => await _archive(); + + Future archiveSpace( + BuildContext context, + Client client, { + bool onlyAdmin = false, + }) async => + await _archiveSpace(context, client, onlyAdmin: onlyAdmin); + + Future leaveSpace(BuildContext context, Client client) async => + await _leaveSpace(context, client); Future sendPangeaEvent({ required Map content, @@ -262,14 +277,15 @@ extension PangeaRoom on Room { BotOptionsModel? get botOptions => _botOptions; - Future suggestedInSpace(Room space) async => - await _suggestedInSpace(space); + Future setSuggested(bool suggested) async => + await _setSuggested(suggested); - Future setSuggestedInSpace(bool suggest, Room space) async => - await _setSuggestedInSpace(suggest, space); + Future isSuggested() async => await _isSuggested(); // user_permissions + Future isOnlyAdmin() async => await _isOnlyAdmin(); + bool isMadeByUser(String userId) => _isMadeByUser(userId); bool get isSpaceAdmin => _isSpaceAdmin; diff --git a/lib/pangea/extensions/pangea_room_extension/room_settings_extension.dart b/lib/pangea/extensions/pangea_room_extension/room_settings_extension.dart index 2c069055f..0eecb691d 100644 --- a/lib/pangea/extensions/pangea_room_extension/room_settings_extension.dart +++ b/lib/pangea/extensions/pangea_room_extension/room_settings_extension.dart @@ -68,7 +68,40 @@ extension RoomSettingsRoomExtension on Room { ); } - Future _suggestedInSpace(Room space) async { + Future _isSuggested() async { + final List spaceParents = client.rooms + .where( + (room) => + room.isSpace && + room.spaceChildren.any( + (sc) => sc.roomId == id, + ), + ) + .toList(); + + for (final parent in spaceParents) { + final suggested = await _isSuggestedInSpace(parent); + if (!suggested) return false; + } + return true; + } + + Future _setSuggested(bool suggested) async { + final List spaceParents = client.rooms + .where( + (room) => + room.isSpace && + room.spaceChildren.any( + (sc) => sc.roomId == id, + ), + ) + .toList(); + for (final parent in spaceParents) { + await _setSuggestedInSpace(suggested, parent); + } + } + + Future _isSuggestedInSpace(Room space) async { try { final Map resp = await client.getRoomStateWithKey(space.id, EventTypes.spaceChild, id); diff --git a/lib/pangea/extensions/pangea_room_extension/user_permissions_extension.dart b/lib/pangea/extensions/pangea_room_extension/user_permissions_extension.dart index 929a74e66..17d6aead2 100644 --- a/lib/pangea/extensions/pangea_room_extension/user_permissions_extension.dart +++ b/lib/pangea/extensions/pangea_room_extension/user_permissions_extension.dart @@ -1,6 +1,32 @@ part of "pangea_room_extension.dart"; extension UserPermissionsRoomExtension on Room { +// If there are no other admins, and at least one non-admin, return true + Future _isOnlyAdmin() async { + if (!isRoomAdmin) { + return false; + } + final List participants = await requestParticipants(); + + return ((participants + .where( + (e) => + e.powerLevel == ClassDefaultValues.powerLevelOfAdmin && + e.id != BotName.byEnvironment, + ) + .toList() + .length) == + 1) && + (participants + .where( + (e) => + e.powerLevel < ClassDefaultValues.powerLevelOfAdmin && + e.id != BotName.byEnvironment, + ) + .toList()) + .isNotEmpty; + } + bool _isMadeByUser(String userId) => getState(EventTypes.RoomCreate)?.senderId == userId; diff --git a/lib/pangea/matrix_event_wrappers/pangea_message_event.dart b/lib/pangea/matrix_event_wrappers/pangea_message_event.dart index 5fa2e2659..ff8696989 100644 --- a/lib/pangea/matrix_event_wrappers/pangea_message_event.dart +++ b/lib/pangea/matrix_event_wrappers/pangea_message_event.dart @@ -80,29 +80,6 @@ class PangeaMessageEvent { return _latestEdit; } - bool showRichText( - bool selected, { - bool highlighted = false, - bool isOverlay = false, - }) { - if (!_isValidPangeaMessageEvent) { - return false; - } - - if ([EventStatus.error, EventStatus.sending].contains(_event.status)) { - return false; - } - - if (isOverlay) return true; - - // if ownMessage, don't show rich text if not selected or highlighted - // and don't show is the message is not an overlay - if (ownMessage && ((!selected && !highlighted) || !isOverlay)) { - return false; - } - return true; - } - Future getMatrixAudioFile( String langCode, BuildContext context, diff --git a/lib/pangea/models/class_model.dart b/lib/pangea/models/class_model.dart index 1f588980c..7e95c9d26 100644 --- a/lib/pangea/models/class_model.dart +++ b/lib/pangea/models/class_model.dart @@ -1,13 +1,12 @@ import 'dart:developer'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; - -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:matrix/matrix.dart'; - import 'package:fluffychat/pangea/constants/model_keys.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 'package:matrix/matrix.dart'; + import '../constants/class_default_values.dart'; import '../constants/language_keys.dart'; import '../constants/pangea_event_types.dart'; @@ -124,6 +123,7 @@ class PangeaRoomRules { int immersionMode; int definitions; int translations; + int autoIGC; PangeaRoomRules({ this.isPublic = false, @@ -142,6 +142,7 @@ class PangeaRoomRules { this.immersionMode = ClassDefaultValues.languageToolPermissions, this.definitions = ClassDefaultValues.languageToolPermissions, this.translations = ClassDefaultValues.languageToolPermissions, + this.autoIGC = ClassDefaultValues.languageToolPermissions, }); updatePermission(String key, bool value) { @@ -198,8 +199,11 @@ class PangeaRoomRules { case ToolSetting.definitions: definitions = value; break; - case ToolSetting.translations: - translations = value; + // case ToolSetting.translations: + // translations = value; + // break; + case ToolSetting.autoIGC: + autoIGC = value; break; default: throw Exception('Invalid key for setting permissions - $setting'); @@ -235,6 +239,7 @@ class PangeaRoomRules { json['definitions'] ?? ClassDefaultValues.languageToolPermissions, translations: json['translations'] ?? ClassDefaultValues.languageToolPermissions, + autoIGC: json['auto_igc'] ?? ClassDefaultValues.languageToolPermissions, ); Map toJson() { @@ -256,6 +261,7 @@ class PangeaRoomRules { data['immersion_mode'] = immersionMode; data['definitions'] = definitions; data['translations'] = translations; + data['auto_igc'] = autoIGC; return data; } @@ -269,8 +275,10 @@ class PangeaRoomRules { return immersionMode; case ToolSetting.definitions: return definitions; - case ToolSetting.translations: - return translations; + // case ToolSetting.translations: + // return translations; + case ToolSetting.autoIGC: + return autoIGC; default: throw Exception('Invalid key for setting permissions - $setting'); } @@ -298,7 +306,8 @@ enum ToolSetting { interactiveGrammar, immersionMode, definitions, - translations, + // translations, + autoIGC, } extension SettingCopy on ToolSetting { @@ -312,8 +321,10 @@ extension SettingCopy on ToolSetting { return L10n.of(context)!.toggleImmersionMode; case ToolSetting.definitions: return L10n.of(context)!.definitionsToolName; - case ToolSetting.translations: - return L10n.of(context)!.messageTranslationsToolName; + // case ToolSetting.translations: + // return L10n.of(context)!.messageTranslationsToolName; + case ToolSetting.autoIGC: + return L10n.of(context)!.autoIGCToolName; } } @@ -328,8 +339,10 @@ extension SettingCopy on ToolSetting { return L10n.of(context)!.toggleImmersionModeDesc; case ToolSetting.definitions: return L10n.of(context)!.definitionsToolDescription; - case ToolSetting.translations: - return L10n.of(context)!.translationsToolDescrption; + // case ToolSetting.translations: + // return L10n.of(context)!.translationsToolDescrption; + case ToolSetting.autoIGC: + return L10n.of(context)!.autoIGCToolDescription; } } } diff --git a/lib/pangea/models/language_detection_model.dart b/lib/pangea/models/language_detection_model.dart index 6fa3d7299..7ed44868c 100644 --- a/lib/pangea/models/language_detection_model.dart +++ b/lib/pangea/models/language_detection_model.dart @@ -1,19 +1,23 @@ +import 'package:fluffychat/pangea/constants/model_keys.dart'; + class LanguageDetection { String langCode; + double confidence; LanguageDetection({ required this.langCode, + required this.confidence, }); factory LanguageDetection.fromJson(Map json) { return LanguageDetection( - langCode: json[_langCodeKey], + langCode: json[ModelKey.langCode], + confidence: json[ModelKey.confidence], ); } - static const _langCodeKey = "lang_code"; - Map toJson() => { - _langCodeKey: langCode, + ModelKey.langCode: langCode, + ModelKey.confidence: confidence, }; } diff --git a/lib/pangea/models/span_data.dart b/lib/pangea/models/span_data.dart index 186e2834e..d420e1ffd 100644 --- a/lib/pangea/models/span_data.dart +++ b/lib/pangea/models/span_data.dart @@ -4,9 +4,8 @@ // SpanChoice of text in message from options // Call to server for additional/followup info -import 'package:flutter/material.dart'; - import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; import '../enum/span_choice_type.dart'; import '../enum/span_data_type.dart'; @@ -105,6 +104,7 @@ class SpanChoice { required this.type, this.feedback, this.selected = false, + this.timestamp, }); factory SpanChoice.fromJson(Map json) { return SpanChoice( @@ -117,6 +117,8 @@ class SpanChoice { : SpanChoiceType.bestCorrection, feedback: json['feedback'], selected: json['selected'] ?? false, + timestamp: + json['timestamp'] != null ? DateTime.parse(json['timestamp']) : null, ); } @@ -124,12 +126,14 @@ class SpanChoice { SpanChoiceType type; bool selected; String? feedback; + DateTime? timestamp; Map toJson() => { 'value': value, 'type': type.name, 'selected': selected, 'feedback': feedback, + 'timestamp': timestamp?.toIso8601String(), }; String feedbackToDisplay(BuildContext context) { diff --git a/lib/pangea/models/user_model.dart b/lib/pangea/models/user_model.dart index 2169c6f70..a6ae5730f 100644 --- a/lib/pangea/models/user_model.dart +++ b/lib/pangea/models/user_model.dart @@ -59,15 +59,17 @@ enum MatrixProfile { interactiveGrammar, immersionMode, definitions, - translations, + // translations, showedItInstructions, showedClickMessage, showedBlurMeansTranslate, + showedTooltipInstructions, createdAt, targetLanguage, sourceLanguage, country, publicProfile, + autoIGC, } extension MatrixProfileExtension on MatrixProfile { @@ -87,14 +89,18 @@ extension MatrixProfileExtension on MatrixProfile { return ToolSetting.immersionMode.toString(); case MatrixProfile.definitions: return ToolSetting.definitions.toString(); - case MatrixProfile.translations: - return ToolSetting.translations.toString(); + // case MatrixProfile.translations: + // return ToolSetting.translations.toString(); + case MatrixProfile.autoIGC: + return ToolSetting.autoIGC.toString(); case MatrixProfile.showedItInstructions: return InstructionsEnum.itInstructions.toString(); case MatrixProfile.showedClickMessage: return InstructionsEnum.clickMessage.toString(); case MatrixProfile.showedBlurMeansTranslate: return InstructionsEnum.blurMeansTranslate.toString(); + case MatrixProfile.showedTooltipInstructions: + return InstructionsEnum.tooltipInstructions.toString(); case MatrixProfile.createdAt: return ModelKey.userCreatedAt; case MatrixProfile.targetLanguage: diff --git a/lib/pangea/repo/igc_repo.dart b/lib/pangea/repo/igc_repo.dart index 068a009e8..9517515d0 100644 --- a/lib/pangea/repo/igc_repo.dart +++ b/lib/pangea/repo/igc_repo.dart @@ -1,13 +1,13 @@ import 'dart:convert'; -import 'package:http/http.dart'; - import 'package:fluffychat/pangea/config/environment.dart'; import 'package:fluffychat/pangea/models/language_detection_model.dart'; import 'package:fluffychat/pangea/models/lemma.dart'; import 'package:fluffychat/pangea/models/pangea_match_model.dart'; import 'package:fluffychat/pangea/models/pangea_token_model.dart'; import 'package:fluffychat/pangea/repo/span_data_repo.dart'; +import 'package:http/http.dart'; + import '../constants/model_keys.dart'; import '../models/igc_text_data_model.dart'; import '../network/requests.dart'; @@ -39,7 +39,7 @@ class IgcRepo { await Future.delayed(const Duration(seconds: 2)); final IGCTextData igcTextData = IGCTextData( - detections: [LanguageDetection(langCode: "en")], + detections: [LanguageDetection(langCode: "en", confidence: 0.99)], tokens: [ PangeaToken( text: PangeaTokenText(content: "This", offset: 0, length: 4), diff --git a/lib/pangea/repo/span_data_repo.dart b/lib/pangea/repo/span_data_repo.dart index c6025af6c..e253bb1d0 100644 --- a/lib/pangea/repo/span_data_repo.dart +++ b/lib/pangea/repo/span_data_repo.dart @@ -72,6 +72,24 @@ class SpanDetailsRepoReqAndRes { enableIGC: json['enable_igc'] as bool, span: SpanData.fromJson(json['span']), ); + + /// Overrides the equality operator to compare two [SpanDetailsRepoReqAndRes] objects. + /// Returns true if the objects are identical or have the same property + /// values (based on the results of the toJson function), false otherwise. + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other is! SpanDetailsRepoReqAndRes) return false; + + return toJson().toString() == other.toJson().toString(); + } + + /// Overrides the hashCode getter to generate a hash code for the [SpanDetailsRepoReqAndRes] object. + /// Used as keys in response cache in igc_controller. + @override + int get hashCode { + return toJson().toString().hashCode; + } } final spanDataRepomockSpan = SpanData( diff --git a/lib/pangea/utils/archive_space.dart b/lib/pangea/utils/archive_space.dart deleted file mode 100644 index 68b9f82fd..000000000 --- a/lib/pangea/utils/archive_space.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; -import 'package:fluffychat/pangea/utils/error_handler.dart'; -import 'package:matrix/matrix.dart'; - -Future archiveSpace(Room? space, Client client) async { - if (space == null) { - ErrorHandler.logError( - e: 'Tried to archive a space that is null. This should not happen.', - s: StackTrace.current, - ); - return; - } - - final List children = await space.getChildRooms(); - for (final Room child in children) { - if (child.isUnread) { - await child.markUnread(false); - } - await child.leave(); - } - await space.leave(); -} diff --git a/lib/pangea/utils/get_chat_list_item_subtitle.dart b/lib/pangea/utils/get_chat_list_item_subtitle.dart index 2fee578bf..5ccb623d7 100644 --- a/lib/pangea/utils/get_chat_list_item_subtitle.dart +++ b/lib/pangea/utils/get_chat_list_item_subtitle.dart @@ -1,3 +1,4 @@ +import 'package:collection/collection.dart'; import 'package:fluffychat/pangea/constants/language_keys.dart'; import 'package:fluffychat/pangea/constants/model_keys.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; @@ -10,6 +11,17 @@ import 'package:matrix/matrix.dart'; import '../../utils/matrix_sdk_extensions/matrix_locals.dart'; class GetChatListItemSubtitle { + final List hideContentKeys = [ + ModelKey.transcription, + ]; + + bool moveBackInTimeline(Event event) => + hideContentKeys.any( + (key) => event.content.tryGet(key) != null, + ) || + event.type.startsWith("p.") || + event.type.startsWith("pangea."); + Future getSubtitle( L10n l10n, Event? event, @@ -22,23 +34,14 @@ class GetChatListItemSubtitle { eventContextId = null; } - final Timeline timeline = - await event.room.getTimeline(eventContextId: eventContextId); + final Timeline timeline = await event.room.getTimeline( + eventContextId: eventContextId, + ); - if (event.content.tryGet(ModelKey.transcription) != null) { - int index = timeline.events.indexWhere( - (e) => e.eventId == event!.eventId, - ); - - while (index < timeline.events.length && - (timeline.events[index].content.tryGet(ModelKey.transcription) != - null || - timeline.events[index].type != EventTypes.Message)) { - index++; - } - - if (timeline.events.length > index + 1) { - event = timeline.events[index]; + if (moveBackInTimeline(event)) { + event = timeline.events.firstWhereOrNull((e) => !moveBackInTimeline(e)); + if (event == null) { + return l10n.emptyChat; } } diff --git a/lib/pangea/utils/instructions.dart b/lib/pangea/utils/instructions.dart index f1fa8b59f..43350f518 100644 --- a/lib/pangea/utils/instructions.dart +++ b/lib/pangea/utils/instructions.dart @@ -1,3 +1,4 @@ +import 'package:fluffychat/utils/platform_infos.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; @@ -102,6 +103,7 @@ enum InstructionsEnum { itInstructions, clickMessage, blurMeansTranslate, + tooltipInstructions, } extension Copy on InstructionsEnum { @@ -113,6 +115,8 @@ extension Copy on InstructionsEnum { return L10n.of(context)!.clickMessageTitle; case InstructionsEnum.blurMeansTranslate: return L10n.of(context)!.blurMeansTranslateTitle; + case InstructionsEnum.tooltipInstructions: + return L10n.of(context)!.tooltipInstructionsTitle; } } @@ -124,6 +128,10 @@ extension Copy on InstructionsEnum { return L10n.of(context)!.clickMessageBody; case InstructionsEnum.blurMeansTranslate: return L10n.of(context)!.blurMeansTranslateBody; + case InstructionsEnum.tooltipInstructions: + return PlatformInfos.isMobile + ? L10n.of(context)!.tooltipInstructionsMobileBody + : L10n.of(context)!.tooltipInstructionsBrowserBody; } } } diff --git a/lib/pangea/widgets/chat/message_speech_to_text_card.dart b/lib/pangea/widgets/chat/message_speech_to_text_card.dart index 37b013b53..663e39ffe 100644 --- a/lib/pangea/widgets/chat/message_speech_to_text_card.dart +++ b/lib/pangea/widgets/chat/message_speech_to_text_card.dart @@ -3,6 +3,7 @@ import 'dart:developer'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/models/speech_to_text_models.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; +import 'package:fluffychat/pangea/utils/instructions.dart'; import 'package:fluffychat/pangea/widgets/chat/toolbar_content_loading_indicator.dart'; import 'package:fluffychat/pangea/widgets/common/icon_number_widget.dart'; import 'package:fluffychat/pangea/widgets/igc/card_error_widget.dart'; @@ -175,12 +176,24 @@ class MessageSpeechToTextCardState extends State { number: "${selectedToken?.confidence ?? speechToTextResponse!.transcript.confidence}%", toolTip: L10n.of(context)!.accuracy, + onPressed: () => MatrixState.pangeaController.instructions.show( + context, + InstructionsEnum.tooltipInstructions, + widget.messageEvent.eventId, + true, + ), ), IconNumberWidget( icon: Icons.speed, number: wordsPerMinuteString != null ? "$wordsPerMinuteString" : "??", toolTip: L10n.of(context)!.wordsPerMinute, + onPressed: () => MatrixState.pangeaController.instructions.show( + context, + InstructionsEnum.tooltipInstructions, + widget.messageEvent.eventId, + true, + ), ), ], ), diff --git a/lib/pangea/widgets/chat/message_toolbar.dart b/lib/pangea/widgets/chat/message_toolbar.dart index 142a27227..ba508906b 100644 --- a/lib/pangea/widgets/chat/message_toolbar.dart +++ b/lib/pangea/widgets/chat/message_toolbar.dart @@ -59,6 +59,7 @@ class ToolbarDisplayController { } void showToolbar(BuildContext context, {MessageMode? mode}) { + bool toolbarUp = true; if (highlighted) return; if (controller.selectMode) { controller.clearSelectedEvents(); @@ -76,8 +77,22 @@ class ToolbarDisplayController { if (targetRenderBox != null) { final Size transformTargetSize = (targetRenderBox as RenderBox).size; messageWidth = transformTargetSize.width; + final Offset targetOffset = (targetRenderBox).localToGlobal(Offset.zero); + final double screenHeight = MediaQuery.of(context).size.height; + toolbarUp = targetOffset.dy >= screenHeight / 2; } + final Widget overlayMessage = OverlayMessage( + pangeaMessageEvent.event, + timeline: pangeaMessageEvent.timeline, + immersionMode: immersionMode, + ownMessage: pangeaMessageEvent.ownMessage, + toolbarController: this, + width: messageWidth, + nextEvent: nextEvent, + previousEvent: previousEvent, + ); + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { Widget overlayEntry; if (toolbar == null) return; @@ -88,18 +103,9 @@ class ToolbarDisplayController { ? CrossAxisAlignment.end : CrossAxisAlignment.start, children: [ - toolbar!, + toolbarUp ? toolbar! : overlayMessage, const SizedBox(height: 6), - OverlayMessage( - pangeaMessageEvent.event, - timeline: pangeaMessageEvent.timeline, - immersionMode: immersionMode, - ownMessage: pangeaMessageEvent.ownMessage, - toolbarController: this, - width: messageWidth, - nextEvent: nextEvent, - previousEvent: previousEvent, - ), + toolbarUp ? overlayMessage : toolbar!, ], ); } catch (err) { @@ -113,11 +119,19 @@ class ToolbarDisplayController { child: overlayEntry, transformTargetId: targetId, targetAnchor: pangeaMessageEvent.ownMessage - ? Alignment.bottomRight - : Alignment.bottomLeft, + ? toolbarUp + ? Alignment.bottomRight + : Alignment.topRight + : toolbarUp + ? Alignment.bottomLeft + : Alignment.topLeft, followerAnchor: pangeaMessageEvent.ownMessage - ? Alignment.bottomRight - : Alignment.bottomLeft, + ? toolbarUp + ? Alignment.bottomRight + : Alignment.topRight + : toolbarUp + ? Alignment.bottomLeft + : Alignment.topLeft, backgroundColor: const Color.fromRGBO(0, 0, 0, 1).withAlpha(100), ); diff --git a/lib/pangea/widgets/class/add_space_toggles.dart b/lib/pangea/widgets/class/add_space_toggles.dart index d3bfdbd3d..cfea4dd7b 100644 --- a/lib/pangea/widgets/class/add_space_toggles.dart +++ b/lib/pangea/widgets/class/add_space_toggles.dart @@ -33,9 +33,10 @@ class AddToSpaceToggles extends StatefulWidget { class AddToSpaceState extends State { late Room? room; - late List parents; + late List parents; late List possibleParents; late bool isOpen; + late bool isSuggested; AddToSpaceState({Key? key}); @@ -46,6 +47,9 @@ class AddToSpaceState extends State { ? Matrix.of(context).client.getRoomById(widget.roomId!) : null; + isSuggested = true; + room?.isSuggested().then((value) => isSuggested = value); + possibleParents = Matrix.of(context) .client .rooms @@ -63,8 +67,6 @@ class AddToSpaceState extends State { (r) => r.spaceChildren.any((room) => room.roomId == widget.roomId), ) - .map((r) => SuggestionStatus(false, r)) - .cast() .toList() : []; @@ -72,7 +74,7 @@ class AddToSpaceState extends State { final activeSpace = Matrix.of(context).client.getRoomById(widget.activeSpaceId!); if (activeSpace != null && activeSpace.canIAddSpaceChild(null)) { - parents.add(SuggestionStatus(true, activeSpace)); + parents.add(activeSpace); } else { ErrorHandler.logError( e: Exception('activeSpaceId ${widget.activeSpaceId} not found'), @@ -84,10 +86,9 @@ class AddToSpaceState extends State { //if possibleParent in parents, put first //use sort but use any instead of contains because contains uses == and we want to compare by id possibleParents.sort((a, b) { - if (parents.any((suggestionStatus) => suggestionStatus.room.id == a.id)) { + if (parents.any((parent) => parent.id == a.id)) { return -1; - } else if (parents - .any((suggestionStatus) => suggestionStatus.room.id == b.id)) { + } else if (parents.any((parent) => parent.id == b.id)) { return 1; } else { return a.name.compareTo(b.name); @@ -95,35 +96,21 @@ class AddToSpaceState extends State { }); isOpen = widget.startOpen; - initSuggestedParents(); super.initState(); } - Future initSuggestedParents() async { - if (room != null) { - for (var i = 0; i < parents.length; i++) { - final parent = parents[i]; - final bool suggested = - await room?.suggestedInSpace(parent.room) ?? false; - parents[i].suggested = suggested; - } - setState(() {}); - } - } - Future _addSingleSpace(String roomToAddId, Room newParent) async { GoogleAnalytics.addParent(roomToAddId, newParent.classCode); await newParent.setSpaceChild( roomToAddId, - suggested: isSuggestedInSpace(newParent), + suggested: isSuggested, ); - await setSuggested(true, newParent); } Future addSpaces(String roomToAddId) async { final List> addFutures = []; - for (final SuggestionStatus newParent in parents) { - addFutures.add(_addSingleSpace(roomToAddId, newParent.room)); + for (final Room parent in parents) { + addFutures.add(_addSingleSpace(roomToAddId, parent)); } await addFutures.wait; } @@ -148,39 +135,18 @@ class AddToSpaceState extends State { setState( () => add - ? parents.add(SuggestionStatus(true, possibleParent)) + ? parents.add(possibleParent) : parents.removeWhere( - (suggestionStatus) => - suggestionStatus.room.id == possibleParent.id, + (parent) => parent.id == possibleParent.id, ), ); } - Future setSuggested(bool suggest, Room possibleParent) async { - if (room != null) { - await showFutureLoadingDialog( - context: context, - future: () => room!.setSuggestedInSpace(suggest, possibleParent), - ); - } - - for (final SuggestionStatus suggestionStatus in parents) { - if (suggestionStatus.room.id == possibleParent.id) { - suggestionStatus.suggested = suggest; - } - } - - setState(() {}); - } - - bool isSuggestedInSpace(Room parent) => - parents.firstWhereOrNull((r) => r.room.id == parent.id)?.suggested ?? - false; - Widget getAddToSpaceToggleItem(int index) { final Room possibleParent = possibleParents[index]; - final String possibleParentName = possibleParent.getLocalizedDisplayname(); - final bool canAdd = possibleParent.canIAddSpaceChild(room); + final bool canAdd = !(!possibleParent.isRoomAdmin && + widget.mode == AddToClassMode.exchange) && + possibleParent.canIAddSpaceChild(room); return Opacity( opacity: canAdd ? 1 : 0.5, @@ -189,7 +155,7 @@ class AddToSpaceState extends State { SwitchListTile.adaptive( title: possibleParent.nameAndRoomTypeIcon(), activeColor: AppConfig.activeToggleColor, - value: parents.any((r) => r.room.id == possibleParent.id), + value: parents.any((r) => r.id == possibleParent.id), onChanged: (bool add) => canAdd ? handleAdd(add, possibleParent) : ScaffoldMessenger.of(context).showSnackBar( @@ -198,53 +164,6 @@ class AddToSpaceState extends State { ), ), ), - AnimatedSize( - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOut, - child: parents.any((r) => r.room.id == possibleParent.id) - ? SwitchListTile.adaptive( - title: Row( - children: [ - const SizedBox(width: 32), - Expanded( - child: Text( - L10n.of(context)!.suggestTo(possibleParentName), - style: TextStyle( - color: Theme.of(context).colorScheme.secondary, - ), - overflow: TextOverflow.ellipsis, - ), - ), - ], - ), - subtitle: Row( - children: [ - const SizedBox(width: 32), - Expanded( - child: Text( - widget.mode == AddToClassMode.chat - ? L10n.of(context)! - .suggestChatDesc(possibleParentName) - : L10n.of(context)!.suggestExchangeDesc( - possibleParentName, - ), - overflow: TextOverflow.ellipsis, - ), - ), - ], - ), - activeColor: AppConfig.activeToggleColor, - value: isSuggestedInSpace(possibleParent), - onChanged: (bool suggest) => canAdd - ? setSuggested(suggest, possibleParent) - : ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(L10n.of(context)!.noPermission), - ), - ), - ) - : Container(), - ), Divider( height: 0.5, color: Theme.of(context).colorScheme.secondary.withAlpha(25), @@ -254,6 +173,16 @@ class AddToSpaceState extends State { ); } + Future setSuggested(bool suggested) async { + setState(() => isSuggested = suggested); + if (room != null) { + await showFutureLoadingDialog( + context: context, + future: () async => await room?.setSuggested(suggested), + ); + } + } + @override Widget build(BuildContext context) { final String title = widget.mode == AddToClassMode.exchange @@ -292,9 +221,28 @@ class AddToSpaceState extends State { const Divider(height: 1), possibleParents.isNotEmpty ? Column( - children: possibleParents - .mapIndexed((index, _) => getAddToSpaceToggleItem(index)) - .toList(), + children: [ + SwitchListTile.adaptive( + title: Text(L10n.of(context)!.suggestToChat), + secondary: Icon( + isSuggested + ? Icons.visibility_outlined + : Icons.visibility_off_outlined, + ), + subtitle: Text(L10n.of(context)!.suggestToChatDesc), + activeColor: AppConfig.activeToggleColor, + value: isSuggested, + onChanged: (bool add) => setSuggested(add), + ), + Divider( + height: 0.5, + color: + Theme.of(context).colorScheme.secondary.withAlpha(25), + ), + ...possibleParents.mapIndexed( + (index, _) => getAddToSpaceToggleItem(index), + ), + ], ) : Center( child: Padding( @@ -312,10 +260,3 @@ class AddToSpaceState extends State { ); } } - -class SuggestionStatus { - bool suggested; - final Room room; - - SuggestionStatus(this.suggested, this.room); -} diff --git a/lib/pangea/widgets/common/icon_number_widget.dart b/lib/pangea/widgets/common/icon_number_widget.dart index b42777f91..f677ea579 100644 --- a/lib/pangea/widgets/common/icon_number_widget.dart +++ b/lib/pangea/widgets/common/icon_number_widget.dart @@ -6,6 +6,7 @@ class IconNumberWidget extends StatelessWidget { final Color? iconColor; final double? iconSize; final String? toolTip; + final VoidCallback onPressed; const IconNumberWidget({ super.key, @@ -14,16 +15,20 @@ class IconNumberWidget extends StatelessWidget { this.toolTip, this.iconColor, this.iconSize, + required this.onPressed, }); Widget _content(BuildContext context) { return Row( mainAxisSize: MainAxisSize.min, children: [ - Icon( - icon, - color: iconColor ?? Theme.of(context).iconTheme.color, - size: iconSize ?? Theme.of(context).iconTheme.size, + IconButton( + icon: Icon( + icon, + color: iconColor ?? Theme.of(context).iconTheme.color, + size: iconSize ?? Theme.of(context).iconTheme.size, + ), + onPressed: onPressed, ), const SizedBox(width: 8), Text( diff --git a/lib/pangea/widgets/igc/span_card.dart b/lib/pangea/widgets/igc/span_card.dart index fd44383a0..09fdba4dd 100644 --- a/lib/pangea/widgets/igc/span_card.dart +++ b/lib/pangea/widgets/igc/span_card.dart @@ -55,18 +55,36 @@ class SpanCardState extends State { // debugger(when: kDebugMode); super.initState(); getSpanDetails(); + fetchSelected(); } //get selected choice SpanChoice? get selectedChoice { if (selectedChoiceIndex == null || widget.scm.pangeaMatch?.match.choices == null || - widget.scm.pangeaMatch!.match.choices!.length >= selectedChoiceIndex!) { + widget.scm.pangeaMatch!.match.choices!.length <= selectedChoiceIndex!) { return null; } return widget.scm.pangeaMatch?.match.choices?[selectedChoiceIndex!]; } + void fetchSelected() { + if (widget.scm.pangeaMatch?.match.choices == null) { + return; + } + if (selectedChoiceIndex == null) { + DateTime? mostRecent; + for (int i = 0; i < widget.scm.pangeaMatch!.match.choices!.length; i++) { + final choice = widget.scm.pangeaMatch?.match.choices![i]; + if (choice!.timestamp != null && + (mostRecent == null || choice.timestamp!.isAfter(mostRecent))) { + mostRecent = choice.timestamp; + selectedChoiceIndex = i; + } + } + } + } + Future getSpanDetails() async { try { if (widget.scm.pangeaMatch?.isITStart ?? false) return; @@ -110,6 +128,16 @@ class WordMatchContent extends StatelessWidget { Future onChoiceSelect(int index) async { controller.selectedChoiceIndex = index; + controller + .widget + .scm + .choreographer + .igc + .igcTextData + ?.matches[controller.widget.scm.matchIndex] + .match + .choices?[index] + .timestamp = DateTime.now(); controller .widget .scm @@ -152,6 +180,7 @@ class WordMatchContent extends StatelessWidget { offset: controller.widget.scm.pangeaMatch?.match.offset, ); } + final MatchCopy matchCopy = MatchCopy( context, controller.widget.scm.pangeaMatch!, diff --git a/lib/widgets/chat_settings_popup_menu.dart b/lib/widgets/chat_settings_popup_menu.dart index fb90aa8db..9b6e83270 100644 --- a/lib/widgets/chat_settings_popup_menu.dart +++ b/lib/widgets/chat_settings_popup_menu.dart @@ -85,20 +85,31 @@ class ChatSettingsPopupMenuState extends State { ), // #Pangea if (!widget.room.isArchived) - // Pangea# - PopupMenuItem( - value: 'leave', - child: Row( - children: [ - // #Pangea - // const Icon(Icons.delete_outlined), - const Icon(Icons.arrow_forward), - // Pangea# - const SizedBox(width: 12), - Text(L10n.of(context)!.leave), - ], + if (widget.room.isRoomAdmin) + PopupMenuItem( + value: 'archive', + child: Row( + children: [ + const Icon(Icons.archive_outlined), + const SizedBox(width: 12), + Text(L10n.of(context)!.archive), + ], + ), ), + // Pangea# + PopupMenuItem( + value: 'leave', + child: Row( + children: [ + // #Pangea + // const Icon(Icons.delete_outlined), + const Icon(Icons.arrow_forward), + // Pangea# + const SizedBox(width: 12), + Text(L10n.of(context)!.leave), + ], ), + ), // #Pangea if (classSettings != null) PopupMenuItem( @@ -167,7 +178,8 @@ class ChatSettingsPopupMenuState extends State { PopupMenuButton( onSelected: (String choice) async { switch (choice) { - case 'leave': + // #Pangea + case 'archive': final confirmed = await showOkCancelAlertDialog( useRootNavigator: false, context: context, @@ -179,7 +191,31 @@ class ChatSettingsPopupMenuState extends State { if (confirmed == OkCancelResult.ok) { final success = await showFutureLoadingDialog( context: context, - future: () => widget.room.leave(), + future: () => widget.room.archive(), + ); + if (success.error == null) { + context.go('/rooms'); + } + } + break; + // Pangea# + case 'leave': + final bool onlyAdmin = await widget.room.isOnlyAdmin(); + final confirmed = await showOkCancelAlertDialog( + useRootNavigator: false, + context: context, + title: L10n.of(context)!.areYouSure, + okLabel: L10n.of(context)!.ok, + cancelLabel: L10n.of(context)!.cancel, + message: onlyAdmin + ? L10n.of(context)!.onlyAdminDescription + : L10n.of(context)!.leaveRoomDescription, + ); + if (confirmed == OkCancelResult.ok) { + final success = await showFutureLoadingDialog( + context: context, + future: () => + onlyAdmin ? widget.room.archive() : widget.room.leave(), ); if (success.error == null) { context.go('/rooms'); diff --git a/needed-translations.txt b/needed-translations.txt index bb967d011..fa0febfcd 100644 --- a/needed-translations.txt +++ b/needed-translations.txt @@ -745,9 +745,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -839,7 +838,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "be": [ @@ -2124,9 +2144,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -2277,7 +2296,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "bn": [ @@ -3034,9 +3074,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -3177,7 +3216,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "bo": [ @@ -3934,9 +3994,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -4077,7 +4136,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "ca": [ @@ -4834,9 +4914,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -4977,7 +5056,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "cs": [ @@ -5734,9 +5834,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -5877,7 +5976,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "de": [ @@ -6629,9 +6749,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -6724,7 +6843,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "el": [ @@ -7481,9 +7621,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -7624,7 +7763,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "eo": [ @@ -8381,9 +8541,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -8524,7 +8683,54 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" + ], + + "es": [ + "suggestToChat", + "suggestToChatDesc", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "et": [ @@ -9273,9 +9479,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -9367,7 +9572,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "eu": [ @@ -10116,9 +10342,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -10210,7 +10435,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "fa": [ @@ -10967,9 +11213,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -11110,7 +11355,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "fi": [ @@ -11867,9 +12133,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -12010,7 +12275,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "fr": [ @@ -12767,9 +13053,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -12910,7 +13195,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "ga": [ @@ -13667,9 +13973,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -13810,7 +14115,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "gl": [ @@ -14559,9 +14885,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -14653,7 +14978,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "he": [ @@ -15410,9 +15756,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -15553,7 +15898,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "hi": [ @@ -16310,9 +16676,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -16453,7 +16818,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "hr": [ @@ -17207,9 +17593,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -17340,7 +17725,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "hu": [ @@ -18097,9 +18503,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -18240,7 +18645,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "ia": [ @@ -19511,9 +19937,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -19664,7 +20089,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "id": [ @@ -20421,9 +20867,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -20564,7 +21009,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "ie": [ @@ -21321,9 +21787,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -21464,7 +21929,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "it": [ @@ -22217,9 +22703,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -22349,7 +22834,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "ja": [ @@ -23106,9 +23612,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -23249,7 +23754,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "ko": [ @@ -24006,9 +24532,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -24149,7 +24674,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "lt": [ @@ -24906,9 +25452,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -25049,7 +25594,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "lv": [ @@ -25806,9 +26372,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -25949,7 +26514,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "nb": [ @@ -26706,9 +27292,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -26849,7 +27434,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "nl": [ @@ -27606,9 +28212,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -27749,7 +28354,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "pl": [ @@ -28506,9 +29132,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -28649,7 +29274,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "pt": [ @@ -29406,9 +30052,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -29549,7 +30194,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "pt_BR": [ @@ -30302,9 +30968,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -30418,7 +31083,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "pt_PT": [ @@ -31175,9 +31861,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -31318,7 +32003,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "ro": [ @@ -32075,9 +32781,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -32218,7 +32923,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "ru": [ @@ -32967,9 +33693,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -33061,7 +33786,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "sk": [ @@ -33818,9 +34564,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -33961,7 +34706,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "sl": [ @@ -34718,9 +35484,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -34861,7 +35626,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "sr": [ @@ -35618,9 +36404,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -35761,7 +36546,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "sv": [ @@ -36514,9 +37320,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -36626,7 +37431,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "ta": [ @@ -37383,9 +38209,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -37526,7 +38351,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "th": [ @@ -38283,9 +39129,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -38426,7 +39271,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "tr": [ @@ -39179,9 +40045,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -39311,7 +40176,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "uk": [ @@ -40060,9 +40946,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -40154,7 +41039,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "vi": [ @@ -40911,9 +41817,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -41054,7 +41959,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "zh": [ @@ -41803,9 +42729,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -41897,7 +42822,28 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ], "zh_Hant": [ @@ -42654,9 +43600,8 @@ "lockSpace", "lockChat", "archiveSpace", - "suggestTo", - "suggestChatDesc", - "suggestExchangeDesc", + "suggestToChat", + "suggestToChatDesc", "acceptSelection", "acceptSelectionAnyway", "makingActivity", @@ -42797,6 +43742,27 @@ "studentAnalyticsNotAvailable", "roomDataMissing", "updatePhoneOS", - "wordsPerMinute" + "wordsPerMinute", + "roomCapacity", + "roomFull", + "topicNotSet", + "capacityNotSet", + "roomCapacityHasBeenChanged", + "roomExceedsCapacity", + "capacitySetTooLow", + "roomCapacityExplanation", + "enterNumber", + "autoIGCToolName", + "autoIGCToolDescription", + "runGrammarCorrection", + "grammarCorrectionFailed", + "grammarCorrectionComplete", + "leaveRoomDescription", + "archiveSpaceDescription", + "leaveSpaceDescription", + "onlyAdminDescription", + "tooltipInstructionsTitle", + "tooltipInstructionsMobileBody", + "tooltipInstructionsBrowserBody" ] }