merge conflicts
This commit is contained in:
commit
0454e1419d
75 changed files with 1639 additions and 1385 deletions
66
README.md
66
README.md
|
|
@ -1,6 +1,10 @@
|
|||
Pangea Chat Client Setup:
|
||||
# Overview
|
||||
|
||||
* Download VSCode if you do not already have it installed
|
||||
[Pangea Chat](https://pangea.chat) is a web and mobile platform which lets students ‘learn a language while texting their friends.’ Addressing the gap in communicative language teaching, especially for beginners lacking skill and confidence, Pangea Chat provides a low-stress, high-support environment for language learning through authentic conversations. By integrating human and artificial intelligence, the app enhances communicative abilities and supports educators. Pangea Chat has been grant funded by the National Science Foundation and Virginia Innovation Partnership Corporation based on its technical innovation and potential for broad social impact. Our mission is to build a global, decentralized learning network supporting intercultural learning and exchange.
|
||||
|
||||
# Pangea Chat Client Setup
|
||||
|
||||
* Download VSCode if you do not already have it installed. This is the preferred IDE for development with Pangea Chat.
|
||||
* Download flutter on your device using this guide: https://docs.flutter.dev/get-started/install
|
||||
* Test to make sure that flutter is properly installed by running “flutter –version”
|
||||
* You may need to add flutter to your path manually. Instructions can be found here: https://docs.flutter.dev/get-started/install/macos/mobile-ios?tab=download#add-flutter-to-your-path
|
||||
|
|
@ -14,7 +18,7 @@ Pangea Chat Client Setup:
|
|||
* Run “brew install cocoapods” to install cocoapods
|
||||
* Run “flutter doctor” and for any missing components, follow the instructions from the print out to install / setup
|
||||
* Clone the client repo
|
||||
* Copy the .env file (and the .env.prod file, if you want to run production builds), into the root folder of the client and the assets/ folder
|
||||
* Copy the .env file (and the .env.prod file, if you want to run production builds), into the root folder of the client and the assets/ folder. Contact Gabby for a copy of this file.
|
||||
* Uncomment the lines in the pubspec.yaml file in the assets section with paths to .env file
|
||||
* To run on iOS:
|
||||
* Run “flutter precache --ios”
|
||||
|
|
@ -25,62 +29,10 @@ Pangea Chat Client Setup:
|
|||
* On web, run `flutter run -d chrome –hot`
|
||||
* On mobile device or simulator, run `flutter run –hot -d <DEVICE_NAME>`
|
||||
|
||||

|
||||
|
||||
[FluffyChat](https://fluffychat.im) is an open source, nonprofit and cute [[matrix](https://matrix.org)] client written in [Flutter](https://flutter.dev). The goal of the app is to create an easy to use instant messenger which is open source and accessible for everyone.
|
||||
|
||||
### Links:
|
||||
|
||||
- 🌐 [[Weblate] Translate FluffyChat into your language](https://hosted.weblate.org/projects/fluffychat/)
|
||||
- 🌍 [[m] Join the community](https://matrix.to/#/#fluffychat:matrix.org)
|
||||
- 📰 [[Mastodon] Get updates on social media](https://mastodon.art/@krille)
|
||||
- 🖥️ [[Famedly] Server hosting and professional support](https://famedly.com/kontakt)
|
||||
- 💝 [[Liberapay] Support FluffyChat development](https://de.liberapay.com/KrilleChritzelius)
|
||||
|
||||
<a href='https://ko-fi.com/C1C86VN53' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://storage.ko-fi.com/cdn/kofi5.png?v=3' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>
|
||||
|
||||
### Screenshots:
|
||||
|
||||

|
||||
|
||||
# Features
|
||||
|
||||
- 📩 Send all kinds of messages, images and files
|
||||
- 🎙️ Voice messages
|
||||
- 📍 Location sharing
|
||||
- 🔔 Push notifications
|
||||
- 💬 Unlimited private and public group chats
|
||||
- 📣 Public channels with thousands of participants
|
||||
- 🛠️ Feature rich group moderation including all matrix features
|
||||
- 🔍 Discover and join public groups
|
||||
- 🌙 Dark mode
|
||||
- 🎨 Material You design
|
||||
- 📟 Hides complexity of Matrix IDs behind simple QR codes
|
||||
- 😄 Custom emotes and stickers
|
||||
- 🌌 Spaces
|
||||
- 🔄 Compatible with Element, Nheko, NeoChat and all other Matrix apps
|
||||
- 🔐 End to end encryption
|
||||
- 🔒 Encrypted chat backup
|
||||
- 😀 Emoji verification & cross signing
|
||||
|
||||
... and much more.
|
||||
|
||||
|
||||
# Installation
|
||||
|
||||
Please visit the website for installation instructions:
|
||||
|
||||
- https://fluffychat.im
|
||||
|
||||
# How to build
|
||||
|
||||
Please visit the [Wiki](https://github.com/krille-chan/fluffychat/wiki) for build instructions:
|
||||
|
||||
- https://github.com/krille-chan/fluffychat/wiki/How-To-Build
|
||||
|
||||
|
||||
# Special thanks
|
||||
|
||||
* Pangea Chat is a fork of [FluffyChat](https://fluffychat.im), is an open source, nonprofit and cute [[matrix](https://matrix.org)] client written in [Flutter](https://flutter.dev). The goal of FluffyChat is to create an easy to use instant messenger which is open source and accessible for everyone. You can [support the primary maker of FluffyChat directly here.](https://ko-fi.com/C1C86VN53)
|
||||
|
||||
* <a href="https://github.com/fabiyamada">Fabiyamada</a> is a graphics designer and has made the fluffychat logo and the banner. Big thanks for her great designs.
|
||||
|
||||
* <a href="https://github.com/advocatux">Advocatux</a> has made the Spanish translation with great love and care. He always stands by my side and supports my work with great commitment.
|
||||
|
|
|
|||
|
|
@ -2504,7 +2504,7 @@
|
|||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"interactiveTranslatorAllowedDesc": "Students can choose whether to use translation assistance in space group chats in Settings > Learning Settings.",
|
||||
"interactiveTranslatorAllowedDesc": "Students can choose whether to use translation assistance in space group chats in Main Menu > My Learning Settings.",
|
||||
"@interactiveTranslatorAllowedDesc": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
|
|
@ -2984,9 +2984,9 @@
|
|||
"errorDisableLanguageAssistanceClassDesc": "Translation assistance and grammar assistance are turned off for the space that this chat is in.",
|
||||
"itIsDisabled": "Interactive Translation is disabled",
|
||||
"igcIsDisabled": "Interactive Grammar Checking is disabled",
|
||||
"goToLearningSettings": "Go to Learning Settings",
|
||||
"goToLearningSettings": "Go to My Learning Settings",
|
||||
"error405Title": "Languages not set",
|
||||
"error405Desc": "Please set your languages in Settings > Learning Settings",
|
||||
"error405Desc": "Please set your languages in Main Menu > My Learning Settings.",
|
||||
"loginOrSignup": "Sign in with",
|
||||
"@loginOrSignup": {
|
||||
"type": "text",
|
||||
|
|
@ -3049,7 +3049,7 @@
|
|||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"learningSettings": "Learning Settings",
|
||||
"learningSettings": "My Learning Settings",
|
||||
"classNameRequired": "Please enter a space name",
|
||||
"@classNameRequired": {
|
||||
"type": "text",
|
||||
|
|
@ -3674,7 +3674,7 @@
|
|||
"bestAnswerFeedback": "That's correct!",
|
||||
"definitionDefaultPrompt": "What does this word mean?",
|
||||
"practiceDefaultPrompt": "What is the best answer?",
|
||||
"correctionDefaultPrompt": "What is the best correction?",
|
||||
"correctionDefaultPrompt": "What is the best replacement?",
|
||||
"itStartDefaultPrompt": "Do you want help translating?",
|
||||
"languageLevelWarning": "Please select a class language level",
|
||||
"lockedChatWarning": "🔒 This chat has been locked",
|
||||
|
|
@ -3945,7 +3945,6 @@
|
|||
"accuracy": "Accuracy",
|
||||
"points": "Points",
|
||||
"noPaymentInfo": "No payment info necessary!",
|
||||
"updatePhoneOS": "You may need to update your device's OS version.",
|
||||
"conversationBotModeSelectDescription": "Bot mode",
|
||||
"conversationBotModeSelectOption_discussion": "Discussion",
|
||||
"conversationBotModeSelectOption_custom": "Custom",
|
||||
|
|
@ -3960,5 +3959,9 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel": "Send discussion prompt on a schedule",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel": "Hours between discussion prompts",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel": "Send discussion prompt when user reacts ⏩ to bot message",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel": "Reaction to send discussion prompt"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel": "Reaction to send discussion prompt",
|
||||
"studentAnalyticsNotAvailable": "Student data not currently available",
|
||||
"roomDataMissing": "Some data may be missing from rooms in which you are not a member.",
|
||||
"updatePhoneOS": "You may need to update your device's OS version.",
|
||||
"wordsPerMinute": "Words per minute"
|
||||
}
|
||||
|
|
@ -4389,7 +4389,7 @@
|
|||
"bestAnswerFeedback": "¡Correcto!",
|
||||
"definitionDefaultPrompt": "¿Qué significa esta palabra?",
|
||||
"practiceDefaultPrompt": "¿Cuál es la mejor respuesta?",
|
||||
"correctionDefaultPrompt": "¿Cuál es la mejor corrección?",
|
||||
"correctionDefaultPrompt": "¿Cuál es el mejor reemplazo?",
|
||||
"itStartDefaultPrompt": "¿Quiere ayuda para traducir?",
|
||||
"suggestTo": "Sugerir a {spaceName}",
|
||||
"suggestChatDesc": "Los chats sugeridos aparecerán en la lista de chats de {spaceName}.",
|
||||
|
|
@ -4587,5 +4587,52 @@
|
|||
"refresh": "Actualizar",
|
||||
"joinToView": "Únete a esta sala para ver los detalles",
|
||||
"autoPlayTitle": "Reproducción automática de mensajes",
|
||||
"autoPlayDesc": "Cuando está activado, el audio de texto a voz de los mensajes se reproducirá automáticamente cuando se seleccione."
|
||||
"autoPlayDesc": "Cuando está activado, el audio de texto a voz de los mensajes se reproducirá automáticamente cuando se seleccione.",
|
||||
"presenceStyle": "Presencia:",
|
||||
"presencesToggle": "Mostrar mensajes de estado de otros usuarios",
|
||||
"writeAMessageFlag": "Escribe un mensaje en {l1flag} o {l2flag}...",
|
||||
"@writeAMessageFlag": {
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"l1flag": {},
|
||||
"l2flag": {}
|
||||
}
|
||||
},
|
||||
"youInvitedToBy": "📩 Has sido invitado a través de un enlace a:\n{alias}",
|
||||
"@youInvitedToBy": {
|
||||
"placeholders": {
|
||||
"alias": {}
|
||||
}
|
||||
},
|
||||
"hidePresences": "¿Ocultar la lista de estados?",
|
||||
"sendReadReceipts": "Enviar recibos de lectura",
|
||||
"sendTypingNotificationsDescription": "Los demás participantes en un chat pueden ver cuándo estás escribiendo un nuevo mensaje.",
|
||||
"sendReadReceiptsDescription": "Los demás participantes en un chat pueden ver cuándo has leído un mensaje.",
|
||||
"formattedMessages": "Mensajes con formato",
|
||||
"formattedMessagesDescription": "Mostrar contenido de mensajes enriquecido como texto en negrita utilizando markdown.",
|
||||
"verifyOtherUser": "🔐 Verificar otro usuario",
|
||||
"verifyOtherUserDescription": "Si verificas a otro usuario, puedes estar seguro de saber a quién estás escribiendo realmente. 💪\n\nCuando inicies una verificación, tú y el otro usuario veréis una ventana emergente en la aplicación. Allí veréis una serie de emojis o números que tendréis que comparar entre vosotros.\n\nLa mejor forma de hacerlo es quedar o iniciar una videollamada. 👭",
|
||||
"verifyOtherDevice": "🔐 Verificar otro dispositivo",
|
||||
"verifyOtherDeviceDescription": "Cuando verificas otro dispositivo, esos dispositivos pueden intercambiar claves, aumentando tu seguridad general. 💪 Cuando inicies una verificación, aparecerá una ventana emergente en la app de ambos dispositivos. Allí verás entonces una serie de emojis o números que tienes que comparar entre sí. Lo mejor es que tengas ambos dispositivos a mano antes de iniciar la verificación. 🤳",
|
||||
"transparent": "Transparente",
|
||||
"incomingMessages": "Mensajes entrantes",
|
||||
"stickers": "Pegatinas",
|
||||
"commandHint_ignore": "Ignorar el ID de matriz dado",
|
||||
"commandHint_unignore": "Designorar el ID de matriz dado",
|
||||
"unreadChatsInApp": "{appname}: {unread} chats no leídos",
|
||||
"@unreadChatsInApp": {
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"appname": {},
|
||||
"unread": {}
|
||||
}
|
||||
},
|
||||
"messageAnalytics": "Análisis de mensajes",
|
||||
"words": "Palabras",
|
||||
"score": "Puntuación",
|
||||
"accuracy": "Precisión",
|
||||
"points": "Puntos",
|
||||
"noPaymentInfo": "No se necesitan datos de pago.",
|
||||
"updatePhoneOS": "Puede que necesites actualizar la versión del sistema operativo de tu dispositivo.",
|
||||
"wordsPerMinute": "Palabras por minuto"
|
||||
}
|
||||
|
|
@ -29,6 +29,8 @@ abstract class AppConfig {
|
|||
static const Color primaryColorLight = Color(0xFFDBC9FF);
|
||||
static const Color secondaryColor = Color(0xFF41a2bc);
|
||||
static const Color activeToggleColor = Color(0xFF33D057);
|
||||
static const Color success = Color(0xFF33D057);
|
||||
static const Color warning = Color.fromARGB(255, 210, 124, 12);
|
||||
// static String _privacyUrl =
|
||||
// 'https://gitlab.com/famedly/fluffychat/-/blob/main/PRIVACY.md';
|
||||
static String _privacyUrl = "https://www.pangeachat.com/privacy";
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ import 'package:fluffychat/pages/settings_security/settings_security.dart';
|
|||
import 'package:fluffychat/pages/settings_style/settings_style.dart';
|
||||
import 'package:fluffychat/pangea/guard/p_vguard.dart';
|
||||
import 'package:fluffychat/pangea/pages/analytics/student_analytics/student_analytics.dart';
|
||||
import 'package:fluffychat/pangea/pages/class_settings/class_settings_page.dart';
|
||||
import 'package:fluffychat/pangea/pages/exchange/add_exchange_to_class.dart';
|
||||
import 'package:fluffychat/pangea/pages/find_partner/find_partner.dart';
|
||||
import 'package:fluffychat/pangea/pages/p_user_age/p_user_age.dart';
|
||||
|
|
@ -150,19 +149,6 @@ abstract class AppRoutes {
|
|||
: child,
|
||||
),
|
||||
routes: [
|
||||
// #Pangea
|
||||
GoRoute(
|
||||
path: '/spaces/:roomid',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
ChatDetails(
|
||||
roomId: state.pathParameters['roomid']!,
|
||||
),
|
||||
),
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
// Pangea#
|
||||
GoRoute(
|
||||
path: '/rooms',
|
||||
redirect: loggedOutRedirect,
|
||||
|
|
@ -246,20 +232,26 @@ abstract class AppRoutes {
|
|||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
const NewGroup(),
|
||||
NewGroup(
|
||||
// #Pangea
|
||||
spaceId: state.uri.queryParameters['spaceId'],
|
||||
// Pangea#
|
||||
),
|
||||
),
|
||||
redirect: loggedOutRedirect,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: ':spaceid',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
const NewGroup(),
|
||||
),
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
],
|
||||
// #Pangea
|
||||
// routes: [
|
||||
// GoRoute(
|
||||
// path: ':spaceid',
|
||||
// pageBuilder: (context, state) => defaultPageBuilder(
|
||||
// context,
|
||||
// state,
|
||||
// const NewGroup(),
|
||||
// ),
|
||||
// redirect: loggedOutRedirect,
|
||||
// ),
|
||||
// ],
|
||||
// Pangea#
|
||||
),
|
||||
GoRoute(
|
||||
path: 'newspace',
|
||||
|
|
@ -521,17 +513,6 @@ abstract class AppRoutes {
|
|||
),
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
// #Pangea
|
||||
GoRoute(
|
||||
path: 'class_settings',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
const ClassSettingsPage(),
|
||||
),
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
// Pangea#
|
||||
GoRoute(
|
||||
path: 'invite',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:fluffychat/pages/archive/archive_view.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/pages/archive/archive_view.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class Archive extends StatefulWidget {
|
||||
const Archive({super.key});
|
||||
|
||||
|
|
@ -20,7 +19,12 @@ class ArchiveController extends State<Archive> {
|
|||
|
||||
Future<List<Room>> getArchive(BuildContext context) async {
|
||||
if (archive.isNotEmpty) return archive;
|
||||
return archive = await Matrix.of(context).client.loadArchive();
|
||||
// #Pangea
|
||||
//return archive = await Matrix.of(context).client.loadArchive();
|
||||
return archive = (await Matrix.of(context).client.loadArchive())
|
||||
.where((e) => (!e.isSpace && !e.isAnalyticsRoom))
|
||||
.toList();
|
||||
// Pangea#
|
||||
}
|
||||
|
||||
void forgetRoomAction(int i) async {
|
||||
|
|
|
|||
|
|
@ -481,7 +481,16 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
}
|
||||
|
||||
// Do not send read markers when app is not in foreground
|
||||
if (kIsWeb && !Matrix.of(context).webHasFocus) return;
|
||||
// #Pangea
|
||||
try {
|
||||
// Pangea#
|
||||
if (kIsWeb && !Matrix.of(context).webHasFocus) return;
|
||||
// #Pangea
|
||||
} catch (err, s) {
|
||||
ErrorHandler.logError(e: err, s: s);
|
||||
return;
|
||||
}
|
||||
// Pangea#
|
||||
if (!kIsWeb &&
|
||||
WidgetsBinding.instance.lifecycleState != AppLifecycleState.resumed) {
|
||||
return;
|
||||
|
|
@ -613,14 +622,14 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
useType: useType,
|
||||
)
|
||||
.then(
|
||||
(String? msgEventId) {
|
||||
(String? msgEventId) async {
|
||||
// #Pangea
|
||||
setState(() {
|
||||
if (previousEdit != null) {
|
||||
edittingEvents.add(previousEdit.eventId);
|
||||
}
|
||||
});
|
||||
// Pangea#
|
||||
|
||||
GoogleAnalytics.sendMessage(
|
||||
room.id,
|
||||
room.classCode,
|
||||
|
|
@ -635,6 +644,8 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
return;
|
||||
}
|
||||
|
||||
// ensure that analytics room exists / is created for the active langCode
|
||||
await room.ensureAnalyticsRoomExists();
|
||||
pangeaController.myAnalytics.handleMessage(
|
||||
room,
|
||||
RecentMessageRecord(
|
||||
|
|
@ -1067,6 +1078,9 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
bool get canEditSelectedEvents {
|
||||
if (isArchived ||
|
||||
selectedEvents.length != 1 ||
|
||||
// #Pangea
|
||||
selectedEvents.single.messageType != MessageTypes.Text ||
|
||||
// Pangea#
|
||||
!selectedEvents.first.status.isSent) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'package:animations/animations.dart';
|
|||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/widgets/it_bar.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/widgets/send_button.dart';
|
||||
import 'package:fluffychat/pangea/constants/language_keys.dart';
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
|
@ -330,7 +331,12 @@ class ChatInputRow extends StatelessWidget {
|
|||
bottom: 6.0,
|
||||
top: 3.0,
|
||||
),
|
||||
hintText: activel1 != null && activel2 != null
|
||||
hintText: activel1 != null &&
|
||||
activel2 != null &&
|
||||
activel1.langCode !=
|
||||
LanguageKeys.unknownLanguage &&
|
||||
activel2.langCode !=
|
||||
LanguageKeys.unknownLanguage
|
||||
? L10n.of(context)!.writeAMessageFlag(
|
||||
activel1.languageEmoji ??
|
||||
activel1.getDisplayName(context) ??
|
||||
|
|
|
|||
|
|
@ -116,7 +116,8 @@ class ChatView extends StatelessWidget {
|
|||
// #Pangea
|
||||
} else {
|
||||
return [
|
||||
ChatSettingsPopupMenu(controller.room, !controller.room.isDirectChat),
|
||||
ChatSettingsPopupMenu(controller.room,
|
||||
(!controller.room.isDirectChat && !controller.room.isArchived)),
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -167,9 +167,10 @@ class Message extends StatelessWidget {
|
|||
// #Pangea
|
||||
ToolbarDisplayController? toolbarController;
|
||||
if (event.type == EventTypes.Message &&
|
||||
event.messageType == MessageTypes.Text ||
|
||||
event.messageType == MessageTypes.Notice ||
|
||||
event.messageType == MessageTypes.Audio) {
|
||||
!event.redacted &&
|
||||
(event.messageType == MessageTypes.Text ||
|
||||
event.messageType == MessageTypes.Notice ||
|
||||
event.messageType == MessageTypes.Audio)) {
|
||||
toolbarController = controller.getToolbarDisplayController(
|
||||
event.eventId,
|
||||
nextEvent: nextEvent,
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ class MessageContent extends StatelessWidget {
|
|||
//further down in the chain is also using pangeaController so its not constant
|
||||
final bool immersionMode;
|
||||
final ToolbarDisplayController? toolbarController;
|
||||
final bool isOverlay;
|
||||
// Pangea#
|
||||
|
||||
const MessageContent(
|
||||
|
|
@ -50,6 +51,7 @@ class MessageContent extends StatelessWidget {
|
|||
this.pangeaMessageEvent,
|
||||
required this.immersionMode,
|
||||
required this.toolbarController,
|
||||
this.isOverlay = false,
|
||||
// Pangea#
|
||||
required this.borderRadius,
|
||||
});
|
||||
|
|
@ -203,7 +205,8 @@ class MessageContent extends StatelessWidget {
|
|||
&&
|
||||
!(pangeaMessageEvent?.showRichText(
|
||||
selected,
|
||||
toolbarController?.highlighted ?? false,
|
||||
isOverlay: isOverlay,
|
||||
highlighted: toolbarController?.highlighted ?? false,
|
||||
) ??
|
||||
false)
|
||||
// Pangea#
|
||||
|
|
@ -305,7 +308,8 @@ class MessageContent extends StatelessWidget {
|
|||
);
|
||||
if (pangeaMessageEvent?.showRichText(
|
||||
selected,
|
||||
toolbarController?.highlighted ?? false,
|
||||
isOverlay: isOverlay,
|
||||
highlighted: toolbarController?.highlighted ?? false,
|
||||
) ??
|
||||
false) {
|
||||
return PangeaRichText(
|
||||
|
|
|
|||
|
|
@ -61,11 +61,22 @@ class ChatDetailsView extends StatelessWidget {
|
|||
);
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: controller.widget.embeddedCloseButton ??
|
||||
const Center(child: BackButton()),
|
||||
leading:
|
||||
// #Pangea
|
||||
!room.isSpace
|
||||
?
|
||||
// Pangea#
|
||||
controller.widget.embeddedCloseButton ??
|
||||
const Center(child: BackButton())
|
||||
// #Pangea
|
||||
: BackButton(
|
||||
onPressed: () => context.go("/rooms"),
|
||||
)
|
||||
// Pangea#
|
||||
,
|
||||
elevation: Theme.of(context).appBarTheme.elevation,
|
||||
actions: <Widget>[
|
||||
// #Pangeas
|
||||
// #Pangea
|
||||
//if (room.canonicalAlias.isNotEmpty)
|
||||
// IconButton(
|
||||
// tooltip: L10n.of(context)!.share,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@ import 'package:collection/collection.dart';
|
|||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pages/chat_list/chat_list_view.dart';
|
||||
import 'package:fluffychat/pangea/constants/pangea_room_types.dart';
|
||||
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
|
||||
import 'package:fluffychat/pangea/extensions/client_extension.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/utils/add_to_space.dart';
|
||||
import 'package:fluffychat/pangea/utils/chat_list_handle_space_tap.dart';
|
||||
|
|
@ -521,7 +523,7 @@ class ChatListController extends State<ChatList>
|
|||
_invitedSpaceSubscription = pangeaController
|
||||
.matrixState.client.onSync.stream
|
||||
.where((event) => event.rooms?.invite != null)
|
||||
.listen((event) {
|
||||
.listen((event) async {
|
||||
for (final inviteEntry in event.rooms!.invite!.entries) {
|
||||
if (inviteEntry.value.inviteState == null) continue;
|
||||
final bool isSpace = inviteEntry.value.inviteState!.any(
|
||||
|
|
@ -529,17 +531,39 @@ class ChatListController extends State<ChatList>
|
|||
event.type == EventTypes.RoomCreate &&
|
||||
event.content['type'] == 'm.space',
|
||||
);
|
||||
if (!isSpace) continue;
|
||||
final String spaceId = inviteEntry.key;
|
||||
final Room? space = pangeaController.matrixState.client.getRoomById(
|
||||
spaceId,
|
||||
final bool isAnalytics = inviteEntry.value.inviteState!.any(
|
||||
(event) =>
|
||||
event.type == EventTypes.RoomCreate &&
|
||||
event.content['type'] == PangeaRoomTypes.analytics,
|
||||
);
|
||||
if (space != null) {
|
||||
chatListHandleSpaceTap(
|
||||
context,
|
||||
this,
|
||||
space,
|
||||
|
||||
if (isSpace) {
|
||||
final String spaceId = inviteEntry.key;
|
||||
final Room? space = pangeaController.matrixState.client.getRoomById(
|
||||
spaceId,
|
||||
);
|
||||
if (space != null) {
|
||||
chatListHandleSpaceTap(
|
||||
context,
|
||||
this,
|
||||
space,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (isAnalytics) {
|
||||
final Room? analyticsRoom =
|
||||
pangeaController.matrixState.client.getRoomById(inviteEntry.key);
|
||||
try {
|
||||
await analyticsRoom?.join();
|
||||
} catch (err, s) {
|
||||
ErrorHandler.logError(
|
||||
m: "Failed to join analytics room",
|
||||
e: err,
|
||||
s: s,
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -704,6 +728,11 @@ class ChatListController extends State<ChatList>
|
|||
while (selectedRoomIds.isNotEmpty) {
|
||||
final roomId = selectedRoomIds.first;
|
||||
try {
|
||||
// #Pangea
|
||||
if (client.getRoomById(roomId)!.isUnread) {
|
||||
await client.getRoomById(roomId)!.markUnread(false);
|
||||
}
|
||||
// Pangea#
|
||||
await client.getRoomById(roomId)!.leave();
|
||||
} finally {
|
||||
toggleSelection(roomId);
|
||||
|
|
@ -819,6 +848,7 @@ class ChatListController extends State<ChatList>
|
|||
pangeaController.afterSyncAndFirstLoginInitialization(context);
|
||||
await pangeaController.inviteBotToExistingSpaces();
|
||||
await pangeaController.setPangeaPushRules();
|
||||
await client.migrateAnalyticsRooms();
|
||||
} else {
|
||||
ErrorHandler.logError(
|
||||
m: "didn't run afterSyncAndFirstLoginInitialization because not mounted",
|
||||
|
|
|
|||
|
|
@ -53,6 +53,11 @@ 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(),
|
||||
|
|
|
|||
|
|
@ -75,7 +75,12 @@ class ChatListView extends StatelessWidget {
|
|||
label: L10n.of(context)!.allChats,
|
||||
// Pangea#
|
||||
),
|
||||
if (controller.spaces.isNotEmpty)
|
||||
if (controller.spaces.isNotEmpty
|
||||
// #Pangea
|
||||
&&
|
||||
!FluffyThemes.isColumnMode(context)
|
||||
// Pangea#
|
||||
)
|
||||
// #Pangea
|
||||
// const NavigationDestination(
|
||||
// icon: Icon(Icons.workspaces_outlined),
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ class ClientChooserButton extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
enabled: matrix.client.classesAndExchangesImIn.isNotEmpty,
|
||||
enabled: matrix.client.allMyAnalyticsRooms.isNotEmpty,
|
||||
value: SettingsAction.myAnalytics,
|
||||
child: Row(
|
||||
children: [
|
||||
|
|
@ -154,6 +154,18 @@ class ClientChooserButton extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
),
|
||||
// #Pangea
|
||||
PopupMenuItem(
|
||||
value: SettingsAction.learning,
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.psychology_outlined),
|
||||
const SizedBox(width: 18),
|
||||
Expanded(child: Text(L10n.of(context)!.learningSettings)),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Pangea#
|
||||
PopupMenuItem(
|
||||
value: SettingsAction.settings,
|
||||
child: Row(
|
||||
|
|
@ -382,6 +394,9 @@ class ClientChooserButton extends StatelessWidget {
|
|||
case SettingsAction.setStatus:
|
||||
controller.setStatus();
|
||||
// #Pangea
|
||||
case SettingsAction.learning:
|
||||
context.go('/rooms/settings/learning');
|
||||
break;
|
||||
case SettingsAction.newClass:
|
||||
context.go('/rooms/newspace');
|
||||
break;
|
||||
|
|
@ -493,6 +508,7 @@ enum SettingsAction {
|
|||
settings,
|
||||
archive,
|
||||
// #Pangea
|
||||
learning,
|
||||
joinWithClassCode,
|
||||
classAnalytics,
|
||||
myAnalytics,
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@ import 'dart:async';
|
|||
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pages/chat_list/chat_list.dart';
|
||||
import 'package:fluffychat/pages/chat_list/chat_list_item.dart';
|
||||
import 'package:fluffychat/pages/chat_list/search_title.dart';
|
||||
import 'package:fluffychat/pages/chat_list/utils/on_chat_tap.dart';
|
||||
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.dart';
|
||||
import 'package:fluffychat/pangea/extensions/sync_update_extension.dart';
|
||||
import 'package:fluffychat/pangea/utils/archive_space.dart';
|
||||
|
|
@ -46,11 +48,23 @@ class _SpaceViewState extends State<SpaceView> {
|
|||
// #Pangea
|
||||
StreamSubscription<SyncUpdate>? _roomSubscription;
|
||||
bool refreshing = false;
|
||||
|
||||
final String _chatCountsKey = 'chatCounts';
|
||||
Map<String, int> get chatCounts => Map.from(
|
||||
widget.controller.pangeaController.pStoreService.read(
|
||||
_chatCountsKey,
|
||||
local: true,
|
||||
) ??
|
||||
{},
|
||||
);
|
||||
// Pangea#
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
loadHierarchy();
|
||||
// #Pangea
|
||||
loadChatCounts();
|
||||
// Pangea#
|
||||
super.initState();
|
||||
}
|
||||
|
||||
|
|
@ -73,9 +87,15 @@ class _SpaceViewState extends State<SpaceView> {
|
|||
// Pangea#
|
||||
}
|
||||
|
||||
Future<GetSpaceHierarchyResponse> loadHierarchy([String? prevBatch]) async {
|
||||
Future<GetSpaceHierarchyResponse> loadHierarchy([
|
||||
String? prevBatch,
|
||||
// #Pangea
|
||||
if (widget.controller.activeSpaceId == null || loading) {
|
||||
String? spaceId,
|
||||
// Pangea#
|
||||
]) async {
|
||||
// #Pangea
|
||||
if ((widget.controller.activeSpaceId == null && spaceId == null) ||
|
||||
loading) {
|
||||
return GetSpaceHierarchyResponse(
|
||||
rooms: [],
|
||||
nextBatch: null,
|
||||
|
|
@ -88,7 +108,10 @@ class _SpaceViewState extends State<SpaceView> {
|
|||
});
|
||||
// Pangea#
|
||||
|
||||
final activeSpaceId = widget.controller.activeSpaceId!;
|
||||
// #Pangea
|
||||
// final activeSpaceId = widget.controller.activeSpaceId!;
|
||||
final activeSpaceId = (widget.controller.activeSpaceId ?? spaceId)!;
|
||||
// Pangea#
|
||||
final client = Matrix.of(context).client;
|
||||
|
||||
final activeSpace = client.getRoomById(activeSpaceId);
|
||||
|
|
@ -121,6 +144,14 @@ class _SpaceViewState extends State<SpaceView> {
|
|||
});
|
||||
rethrow;
|
||||
} finally {
|
||||
// #Pangea
|
||||
if (activeSpace != null) {
|
||||
await setChatCount(
|
||||
activeSpace,
|
||||
_lastResponse[activeSpaceId],
|
||||
);
|
||||
}
|
||||
// Pangea#
|
||||
setState(() {
|
||||
loading = false;
|
||||
});
|
||||
|
|
@ -173,7 +204,7 @@ class _SpaceViewState extends State<SpaceView> {
|
|||
if (spaceChild.roomId == widget.controller.activeSpaceId) {
|
||||
// #Pangea
|
||||
// context.go('/rooms/${spaceChild.roomId}');
|
||||
context.push('/spaces/${spaceChild.roomId}');
|
||||
context.go('/rooms/${spaceChild.roomId}/details');
|
||||
// Pangea#
|
||||
} else {
|
||||
widget.controller.setActiveSpace(spaceChild.roomId);
|
||||
|
|
@ -206,6 +237,10 @@ class _SpaceViewState extends State<SpaceView> {
|
|||
icon: Icons.send_outlined,
|
||||
),
|
||||
if (spaceChild != null &&
|
||||
// #Pangea
|
||||
room != null &&
|
||||
room.ownPowerLevel >= ClassDefaultValues.powerLevelOfAdmin &&
|
||||
// Pangea#
|
||||
(activeSpace?.canChangeStateEvent(EventTypes.spaceChild) ?? false))
|
||||
SheetAction(
|
||||
key: SpaceChildContextAction.removeFromSpace,
|
||||
|
|
@ -253,7 +288,10 @@ class _SpaceViewState extends State<SpaceView> {
|
|||
// #Pangea
|
||||
// future: room!.leave,
|
||||
future: () async {
|
||||
await room!.leave();
|
||||
if (room!.isUnread) {
|
||||
await room.markUnread(false);
|
||||
}
|
||||
await room.leave();
|
||||
if (Matrix.of(context).activeRoomId == room.id) {
|
||||
context.go('/rooms');
|
||||
}
|
||||
|
|
@ -386,6 +424,14 @@ class _SpaceViewState extends State<SpaceView> {
|
|||
}
|
||||
|
||||
// #Pangea
|
||||
Future<void> loadChatCounts() async {
|
||||
for (final Room room in Matrix.of(context).client.rooms) {
|
||||
if (room.isSpace && !chatCounts.containsKey(room.id)) {
|
||||
await loadHierarchy(null, room.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> refreshOnUpdate(SyncUpdate event) async {
|
||||
/* refresh on leave, invite, and space child update
|
||||
not join events, because there's already a listener on
|
||||
|
|
@ -411,6 +457,90 @@ class _SpaceViewState extends State<SpaceView> {
|
|||
}
|
||||
setState(() => refreshing = false);
|
||||
}
|
||||
|
||||
bool includeSpaceChild(sc, matchingSpaceChildren) {
|
||||
final bool isAnalyticsRoom = sc.roomType == PangeaRoomTypes.analytics;
|
||||
final bool isMember = [Membership.join, Membership.invite]
|
||||
.contains(Matrix.of(context).client.getRoomById(sc.roomId)?.membership);
|
||||
final bool isSuggested = matchingSpaceChildren.any(
|
||||
(matchingSpaceChild) =>
|
||||
matchingSpaceChild.roomId == sc.roomId &&
|
||||
matchingSpaceChild.suggested == true,
|
||||
);
|
||||
return !isAnalyticsRoom && (isMember || isSuggested);
|
||||
}
|
||||
|
||||
List<SpaceRoomsChunk> filterSpaceChildren(
|
||||
Room space,
|
||||
List<SpaceRoomsChunk> spaceChildren,
|
||||
) {
|
||||
final childIds =
|
||||
spaceChildren.map((hierarchyMember) => hierarchyMember.roomId);
|
||||
|
||||
final matchingSpaceChildren = space.spaceChildren
|
||||
.where((spaceChild) => childIds.contains(spaceChild.roomId))
|
||||
.toList();
|
||||
|
||||
final filteredSpaceChildren = spaceChildren
|
||||
.where(
|
||||
(sc) => includeSpaceChild(
|
||||
sc,
|
||||
matchingSpaceChildren,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
return filteredSpaceChildren;
|
||||
}
|
||||
|
||||
int sortSpaceChildren(
|
||||
SpaceRoomsChunk a,
|
||||
SpaceRoomsChunk b,
|
||||
) {
|
||||
final bool aIsSpace = a.roomType == 'm.space';
|
||||
final bool bIsSpace = b.roomType == 'm.space';
|
||||
|
||||
if (aIsSpace && !bIsSpace) {
|
||||
return -1;
|
||||
} else if (!aIsSpace && bIsSpace) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Future<void> setChatCount(
|
||||
Room space,
|
||||
GetSpaceHierarchyResponse? response,
|
||||
) async {
|
||||
final Map<String, int> updatedChatCounts = Map.from(chatCounts);
|
||||
final List<SpaceRoomsChunk> spaceChildren = response?.rooms ?? [];
|
||||
final filteredChildren = filterSpaceChildren(space, spaceChildren)
|
||||
.where((sc) => sc.roomId != space.id)
|
||||
.toList();
|
||||
updatedChatCounts[space.id] = filteredChildren.length;
|
||||
|
||||
await widget.controller.pangeaController.pStoreService.save(
|
||||
_chatCountsKey,
|
||||
updatedChatCounts,
|
||||
local: true,
|
||||
);
|
||||
}
|
||||
|
||||
bool roomCountLoading(Room space) =>
|
||||
space.membership == Membership.join && !chatCounts.containsKey(space.id);
|
||||
|
||||
Widget spaceSubtitle(Room space) {
|
||||
if (roomCountLoading(space)) {
|
||||
return const CircularProgressIndicator.adaptive();
|
||||
}
|
||||
|
||||
return Text(
|
||||
space.membership == Membership.join
|
||||
? L10n.of(context)!.numChats(
|
||||
chatCounts[space.id].toString(),
|
||||
)
|
||||
: L10n.of(context)!.youreInvited,
|
||||
);
|
||||
}
|
||||
// Pangea#
|
||||
|
||||
@override
|
||||
|
|
@ -472,14 +602,8 @@ class _SpaceViewState extends State<SpaceView> {
|
|||
// #Pangea
|
||||
subtitle: Row(
|
||||
children: [
|
||||
Text(
|
||||
rootSpace.membership == Membership.join
|
||||
? L10n.of(context)!.numChats(
|
||||
rootSpace.spaceChildren.length.toString(),
|
||||
)
|
||||
: L10n.of(context)!.youreInvited,
|
||||
),
|
||||
if (rootSpace.locked ?? false)
|
||||
spaceSubtitle(rootSpace),
|
||||
if (rootSpace.locked)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(left: 4.0),
|
||||
child: Icon(
|
||||
|
|
@ -545,12 +669,25 @@ class _SpaceViewState extends State<SpaceView> {
|
|||
titleSpacing: 0,
|
||||
title: ListTile(
|
||||
leading: BackButton(
|
||||
onPressed: () =>
|
||||
widget.controller.setActiveSpace(parentSpace?.id),
|
||||
// #Pangea
|
||||
onPressed: () {
|
||||
!FluffyThemes.isColumnMode(context) ||
|
||||
parentSpace?.id != null
|
||||
? widget.controller.setActiveSpace(parentSpace?.id)
|
||||
: widget.controller.onDestinationSelected(0);
|
||||
},
|
||||
// onPressed: () =>
|
||||
// widget.controller.setActiveSpace(parentSpace?.id),
|
||||
// Pangea#
|
||||
),
|
||||
title: Text(
|
||||
parentSpace == null
|
||||
? L10n.of(context)!.allSpaces
|
||||
// #Pangea
|
||||
// ? L10n.of(context)!.allSpaces
|
||||
? !FluffyThemes.isColumnMode(context)
|
||||
? L10n.of(context)!.allSpaces
|
||||
: L10n.of(context)!.allChats
|
||||
// Pangea#
|
||||
: parentSpace.getLocalizedDisplayname(
|
||||
MatrixLocals(L10n.of(context)!),
|
||||
),
|
||||
|
|
@ -611,42 +748,9 @@ class _SpaceViewState extends State<SpaceView> {
|
|||
final space =
|
||||
Matrix.of(context).client.getRoomById(activeSpaceId);
|
||||
if (space != null) {
|
||||
final matchingSpaceChildren = space.spaceChildren
|
||||
.where(
|
||||
(spaceChild) => spaceChildren
|
||||
.map((hierarchyMember) => hierarchyMember.roomId)
|
||||
.contains(spaceChild.roomId),
|
||||
)
|
||||
.toList();
|
||||
spaceChildren = spaceChildren
|
||||
.where(
|
||||
(spaceChild) =>
|
||||
matchingSpaceChildren.any(
|
||||
(matchingSpaceChild) =>
|
||||
matchingSpaceChild.roomId ==
|
||||
spaceChild.roomId &&
|
||||
matchingSpaceChild.suggested == true,
|
||||
) ||
|
||||
[Membership.join, Membership.invite].contains(
|
||||
Matrix.of(context)
|
||||
.client
|
||||
.getRoomById(spaceChild.roomId)
|
||||
?.membership,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
spaceChildren = filterSpaceChildren(space, spaceChildren);
|
||||
}
|
||||
spaceChildren.sort((a, b) {
|
||||
final bool aIsSpace = a.roomType == 'm.space';
|
||||
final bool bIsSpace = b.roomType == 'm.space';
|
||||
|
||||
if (aIsSpace && !bIsSpace) {
|
||||
return -1;
|
||||
} else if (!aIsSpace && bIsSpace) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
spaceChildren.sort(sortSpaceChildren);
|
||||
// Pangea#
|
||||
final canLoadMore = response.nextBatch != null;
|
||||
return SliverList(
|
||||
|
|
@ -721,8 +825,8 @@ class _SpaceViewState extends State<SpaceView> {
|
|||
),
|
||||
// #Pangea
|
||||
// onTap: () => _onJoinSpaceChild(spaceChild),
|
||||
onTap: () => context.push(
|
||||
'/spaces/${spaceChild.roomId}',
|
||||
onTap: () => context.go(
|
||||
'/rooms/${spaceChild.roomId}/details',
|
||||
),
|
||||
// Pangea#
|
||||
),
|
||||
|
|
|
|||
|
|
@ -30,7 +30,9 @@ class StartChatFloatingActionButton extends StatelessWidget {
|
|||
void _onPressed(BuildContext context) async {
|
||||
//#Pangea
|
||||
if (controller.activeSpaceId != null) {
|
||||
context.go('/rooms/newgroup/${controller.activeSpaceId ?? ''}');
|
||||
context.go(
|
||||
'/rooms/newgroup${controller.activeSpaceId != null ? '?spaceId=${controller.activeSpaceId}' : ''}',
|
||||
);
|
||||
return;
|
||||
}
|
||||
//Pangea#
|
||||
|
|
@ -44,7 +46,9 @@ class StartChatFloatingActionButton extends StatelessWidget {
|
|||
case ActiveFilter.groups:
|
||||
// #Pangea
|
||||
// context.go('/rooms/newgroup');
|
||||
context.go('/rooms/newgroup/${controller.activeSpaceId ?? ''}');
|
||||
context.go(
|
||||
'/rooms/newgroup${controller.activeSpaceId != null ? '?spaceId=${controller.activeSpaceId}' : ''}',
|
||||
);
|
||||
// Pangea#
|
||||
break;
|
||||
case ActiveFilter.spaces:
|
||||
|
|
|
|||
|
|
@ -1,15 +1,13 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:fluffychat/pages/chat/send_file_dialog.dart';
|
||||
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
||||
import 'package:fluffychat/widgets/matrix.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:go_router/go_router.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/pages/chat/send_file_dialog.dart';
|
||||
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
void onChatTap(Room room, BuildContext context) async {
|
||||
if (room.membership == Membership.invite) {
|
||||
final inviterId =
|
||||
|
|
@ -47,6 +45,11 @@ void onChatTap(Room room, BuildContext context) async {
|
|||
return;
|
||||
}
|
||||
if (inviteAction == InviteActions.decline) {
|
||||
// #Pangea
|
||||
if (room.isUnread) {
|
||||
await room.markUnread(false);
|
||||
}
|
||||
// Pangea#
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: room.leave,
|
||||
|
|
|
|||
|
|
@ -157,7 +157,6 @@ class InvitationSelectionController extends State<InvitationSelection> {
|
|||
//#Pangea
|
||||
// future: () => room.invite(id),
|
||||
future: () async {
|
||||
await room.invite(id);
|
||||
if (mode == InvitationSelectionMode.admin) {
|
||||
await inviteTeacherAction(room, id);
|
||||
}
|
||||
|
|
@ -175,7 +174,8 @@ class InvitationSelectionController extends State<InvitationSelection> {
|
|||
|
||||
// #Pangea
|
||||
Future<void> inviteTeacherAction(Room room, String id) async {
|
||||
room.setPower(id, ClassDefaultValues.powerLevelOfAdmin);
|
||||
await room.invite(id);
|
||||
await room.setPower(id, ClassDefaultValues.powerLevelOfAdmin);
|
||||
if (room.isSpace) {
|
||||
for (final spaceChild in room.spaceChildren) {
|
||||
if (spaceChild.roomId == null) continue;
|
||||
|
|
|
|||
|
|
@ -17,7 +17,14 @@ import 'package:go_router/go_router.dart';
|
|||
import 'package:matrix/matrix.dart' as sdk;
|
||||
|
||||
class NewGroup extends StatefulWidget {
|
||||
const NewGroup({super.key});
|
||||
// #Pangea
|
||||
final String? spaceId;
|
||||
|
||||
const NewGroup({
|
||||
super.key,
|
||||
this.spaceId,
|
||||
});
|
||||
// Pangea#
|
||||
|
||||
@override
|
||||
NewGroupController createState() => NewGroupController();
|
||||
|
|
@ -50,7 +57,7 @@ class NewGroupController extends State<NewGroup> {
|
|||
void setVocab(List<Lemma> vocab) => setState(() => chatTopic.vocab = vocab);
|
||||
|
||||
String? get activeSpaceId =>
|
||||
GoRouterState.of(context).pathParameters['spaceid'];
|
||||
GoRouterState.of(context).uri.queryParameters['spaceId'];
|
||||
// Pangea#
|
||||
|
||||
void setPublicGroup(bool b) => setState(() => publicGroup = b);
|
||||
|
|
|
|||
|
|
@ -57,6 +57,9 @@ class NewGroupView extends StatelessWidget {
|
|||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
// #Pangea
|
||||
maxLength: 32,
|
||||
// Pangea#
|
||||
controller: controller.nameController,
|
||||
autocorrect: false,
|
||||
readOnly: controller.loading,
|
||||
|
|
@ -91,7 +94,7 @@ class NewGroupView extends StatelessWidget {
|
|||
const Divider(height: 1),
|
||||
AddToSpaceToggles(
|
||||
key: controller.addToSpaceKey,
|
||||
startOpen: false,
|
||||
startOpen: true,
|
||||
activeSpaceId: controller.activeSpaceId,
|
||||
mode: AddToClassMode.chat,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -216,7 +216,7 @@ class NewSpaceController extends State<NewSpace> {
|
|||
);
|
||||
MatrixState.pangeaController.classController
|
||||
.setActiveSpaceIdInChatListController(spaceId);
|
||||
context.push('/spaces/$spaceId');
|
||||
context.go("/rooms/$spaceId/details");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -245,7 +245,7 @@ class NewSpaceController extends State<NewSpace> {
|
|||
// context.pop<String>(spaceId);
|
||||
MatrixState.pangeaController.classController
|
||||
.setActiveSpaceIdInChatListController(spaceId);
|
||||
context.push('/spaces/$spaceId');
|
||||
context.go("/rooms/$spaceId/details");
|
||||
// Pangea#
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
|
|
|
|||
|
|
@ -95,6 +95,9 @@ class NewSpaceView extends StatelessWidget {
|
|||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
// #Pangea
|
||||
maxLength: 32,
|
||||
// Pangea#
|
||||
controller: controller.nameController,
|
||||
autocorrect: false,
|
||||
readOnly: controller.loading,
|
||||
|
|
@ -138,7 +141,7 @@ class NewSpaceView extends StatelessWidget {
|
|||
if (!controller.newClassMode)
|
||||
AddToSpaceToggles(
|
||||
key: controller.addToSpaceKey,
|
||||
startOpen: false,
|
||||
startOpen: true,
|
||||
mode: !controller.newClassMode
|
||||
? AddToClassMode.exchange
|
||||
: AddToClassMode.chat,
|
||||
|
|
|
|||
|
|
@ -42,6 +42,9 @@ class SettingsController extends State<Settings> {
|
|||
cancelLabel: L10n.of(context)!.cancel,
|
||||
textFields: [
|
||||
DialogTextField(
|
||||
// #Pangea
|
||||
maxLength: 32,
|
||||
// Pangea#
|
||||
initialText: profile?.displayName ??
|
||||
Matrix.of(context).client.userID!.localpart,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -181,14 +181,6 @@ class SettingsView extends StatelessWidget {
|
|||
trailing: const Icon(Icons.chevron_right_outlined),
|
||||
),
|
||||
// #Pangea
|
||||
ListTile(
|
||||
leading: const Icon(Icons.account_circle_outlined),
|
||||
title: Text(L10n.of(context)!.learningSettings),
|
||||
onTap: () => context.go('/rooms/settings/learning'),
|
||||
trailing: const Icon(
|
||||
Icons.chevron_right_outlined,
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.account_circle_outlined),
|
||||
title: Text(L10n.of(context)!.subscriptionManagement),
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
import 'package:fluffychat/pangea/choreographer/controllers/it_controller.dart';
|
||||
|
||||
class MlController {
|
||||
final ITController controller;
|
||||
MlController(this.controller);
|
||||
|
||||
// sendPayloads(String message, String messageId) async {
|
||||
// final MessageServiceModel serviceModel = MessageServiceModel(
|
||||
// classId: controller.state!.classId,
|
||||
// roomId: controller.state!.roomId,
|
||||
// message: message.toString(),
|
||||
// messageId: messageId.toString(),
|
||||
// payloadIds: controller.state!.payLoadIds,
|
||||
// userId: controller.state!.userId!,
|
||||
// l1Lang: controller.state!.sourceLangCode,
|
||||
// l2Lang: controller.state!.targetLangCode!,
|
||||
// );
|
||||
// try {
|
||||
// await MessageServiceRepo.sendPayloads(serviceModel);
|
||||
// } catch (err) {
|
||||
// debugPrint('$err in sendPayloads');
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
|
@ -211,7 +211,8 @@ class Choreographer {
|
|||
final CanSendStatus canSendStatus =
|
||||
pangeaController.subscriptionController.canSendStatus;
|
||||
|
||||
if (canSendStatus != CanSendStatus.subscribed) {
|
||||
if (canSendStatus != CanSendStatus.subscribed ||
|
||||
(!igcEnabled && !itEnabled)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import '../../models/it_response_model.dart';
|
|||
import '../../models/it_step.dart';
|
||||
import '../../models/system_choice_translation_model.dart';
|
||||
import '../../repo/interactive_translation_repo.dart';
|
||||
import '../../repo/message_service.repo.dart';
|
||||
import 'choreographer.dart';
|
||||
|
||||
class ITController {
|
||||
|
|
@ -247,19 +246,19 @@ class ITController {
|
|||
),
|
||||
);
|
||||
|
||||
MessageServiceModel? messageServiceModelWithMessageId() =>
|
||||
usedInteractiveTranslation
|
||||
? MessageServiceModel(
|
||||
classId: choreographer.classId,
|
||||
roomId: choreographer.roomId,
|
||||
message: choreographer.currentText,
|
||||
messageId: null,
|
||||
payloadIds: payLoadIds,
|
||||
userId: choreographer.userId!,
|
||||
l1Lang: sourceLangCode,
|
||||
l2Lang: targetLangCode,
|
||||
)
|
||||
: null;
|
||||
// MessageServiceModel? messageServiceModelWithMessageId() =>
|
||||
// usedInteractiveTranslation
|
||||
// ? MessageServiceModel(
|
||||
// classId: choreographer.classId,
|
||||
// roomId: choreographer.roomId,
|
||||
// message: choreographer.currentText,
|
||||
// messageId: null,
|
||||
// payloadIds: payLoadIds,
|
||||
// userId: choreographer.userId!,
|
||||
// l1Lang: sourceLangCode,
|
||||
// l2Lang: targetLangCode,
|
||||
// )
|
||||
// : null;
|
||||
|
||||
//maybe we store IT data in the same format? make a specific kind of match?
|
||||
void selectTranslation(int chosenIndex) {
|
||||
|
|
|
|||
|
|
@ -120,21 +120,41 @@ class ClassController extends BaseController {
|
|||
|
||||
if (classChunk == null) {
|
||||
ClassCodeUtil.messageSnack(
|
||||
context, L10n.of(context)!.unableToFindClass);
|
||||
context,
|
||||
L10n.of(context)!.unableToFindClass,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Matrix.of(context)
|
||||
.client
|
||||
.rooms
|
||||
if (_pangeaController.matrixState.client.rooms
|
||||
.any((room) => room.id == classChunk.roomId)) {
|
||||
setActiveSpaceIdInChatListController(classChunk.roomId);
|
||||
ClassCodeUtil.messageSnack(context, L10n.of(context)!.alreadyInClass);
|
||||
return;
|
||||
}
|
||||
await _pangeaController.matrixState.client.joinRoom(classChunk.roomId);
|
||||
|
||||
setActiveSpaceIdInChatListController(classChunk.roomId);
|
||||
if (_pangeaController.matrixState.client.getRoomById(classChunk.roomId) ==
|
||||
null) {
|
||||
await _pangeaController.matrixState.client.waitForRoomInSync(
|
||||
classChunk.roomId,
|
||||
join: true,
|
||||
);
|
||||
}
|
||||
|
||||
// add the user's analytics room to this joined space
|
||||
// so their teachers can join them via the space hierarchy
|
||||
final Room? joinedSpace =
|
||||
_pangeaController.matrixState.client.getRoomById(classChunk.roomId);
|
||||
|
||||
// ensure that the user has an analytics room for this space's language
|
||||
await joinedSpace?.ensureAnalyticsRoomExists();
|
||||
|
||||
// when possible, add user's analytics room the to space they joined
|
||||
await joinedSpace?.addAnalyticsRoomsToSpace();
|
||||
|
||||
// and invite the space's teachers to the user's analytics rooms
|
||||
await joinedSpace?.inviteSpaceTeachersToAnalyticsRooms();
|
||||
GoogleAnalytics.joinClass(classCode);
|
||||
return;
|
||||
} catch (err) {
|
||||
|
|
|
|||
138
lib/pangea/controllers/language_detection_controller.dart
Normal file
138
lib/pangea/controllers/language_detection_controller.dart
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:fluffychat/pangea/config/environment.dart';
|
||||
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
|
||||
import 'package:fluffychat/pangea/network/urls.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import '../network/requests.dart';
|
||||
|
||||
class LanguageDetectionRequest {
|
||||
/// The full text from which to detect the language.
|
||||
String fullText;
|
||||
|
||||
/// The base language of the user, if known. Including this is much preferred
|
||||
/// and should return better results; however, it is not absolutely necessary.
|
||||
/// This property is nullable to allow for situations where the languages are not set
|
||||
/// at the time of the request.
|
||||
String? userL1;
|
||||
|
||||
/// The target language of the user. This is expected to be set for the request
|
||||
/// but is nullable to handle edge cases where it might not be.
|
||||
String? userL2;
|
||||
|
||||
LanguageDetectionRequest({
|
||||
required this.fullText,
|
||||
this.userL1 = "",
|
||||
required this.userL2,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'full_text': fullText,
|
||||
'user_l1': userL1,
|
||||
'user_l2': userL2,
|
||||
};
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
return other is LanguageDetectionRequest &&
|
||||
other.fullText == fullText &&
|
||||
other.userL1 == userL1 &&
|
||||
other.userL2 == userL2;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => fullText.hashCode ^ userL1.hashCode ^ userL2.hashCode;
|
||||
}
|
||||
|
||||
class LanguageDetectionResponse {
|
||||
List<Map<String, dynamic>> detections;
|
||||
String fullText;
|
||||
|
||||
LanguageDetectionResponse({
|
||||
required this.detections,
|
||||
required this.fullText,
|
||||
});
|
||||
|
||||
factory LanguageDetectionResponse.fromJson(Map<String, dynamic> json) {
|
||||
return LanguageDetectionResponse(
|
||||
detections: List<Map<String, dynamic>>.from(json['detections']),
|
||||
fullText: json['full_text'],
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'detections': detections,
|
||||
'full_text': fullText,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class _LanguageDetectionCacheItem {
|
||||
Future<LanguageDetectionResponse> data;
|
||||
|
||||
_LanguageDetectionCacheItem({
|
||||
required this.data,
|
||||
});
|
||||
}
|
||||
|
||||
class LanguageDetectionController {
|
||||
static final Map<LanguageDetectionRequest, _LanguageDetectionCacheItem>
|
||||
_cache = {};
|
||||
late final PangeaController _pangeaController;
|
||||
Timer? _cacheClearTimer;
|
||||
|
||||
LanguageDetectionController(PangeaController pangeaController) {
|
||||
_pangeaController = pangeaController;
|
||||
_initializeCacheClearing();
|
||||
}
|
||||
|
||||
void _initializeCacheClearing() {
|
||||
const duration = Duration(minutes: 15); // Adjust the duration as needed
|
||||
_cacheClearTimer = Timer.periodic(duration, (Timer t) => _clearCache());
|
||||
}
|
||||
|
||||
void _clearCache() {
|
||||
_cache.clear();
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
_cacheClearTimer?.cancel();
|
||||
}
|
||||
|
||||
Future<LanguageDetectionResponse> get(
|
||||
LanguageDetectionRequest params,
|
||||
) async {
|
||||
if (_cache.containsKey(params)) {
|
||||
return _cache[params]!.data;
|
||||
} else {
|
||||
final Future<LanguageDetectionResponse> response = _fetchResponse(
|
||||
await _pangeaController.userController.accessToken,
|
||||
params,
|
||||
);
|
||||
_cache[params] = _LanguageDetectionCacheItem(data: response);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LanguageDetectionResponse> _fetchResponse(
|
||||
String accessToken,
|
||||
LanguageDetectionRequest params,
|
||||
) async {
|
||||
final Requests request = Requests(
|
||||
choreoApiKey: Environment.choreoApiKey,
|
||||
accessToken: accessToken,
|
||||
);
|
||||
|
||||
final http.Response res = await request.post(
|
||||
url: PApiUrls.languageDetection,
|
||||
body: params.toJson(),
|
||||
);
|
||||
|
||||
final Map<String, dynamic> json = jsonDecode(res.body);
|
||||
return LanguageDetectionResponse.fromJson(json);
|
||||
}
|
||||
}
|
||||
|
|
@ -90,7 +90,7 @@ class MyAnalyticsController {
|
|||
}
|
||||
final Room analyticsRoom = await _pangeaController.matrixState.client
|
||||
.getMyAnalyticsRoom(langCode);
|
||||
analyticsRoom.makeSureTeachersAreInvitedToAnalyticsRoom();
|
||||
|
||||
final List<Future<void>> saveFutures = [];
|
||||
for (final uses in aggregatedVocabUse.entries) {
|
||||
debugPrint("saving of type ${uses.value.first.constructType}");
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import 'package:fluffychat/pangea/constants/pangea_event_types.dart';
|
|||
import 'package:fluffychat/pangea/controllers/class_controller.dart';
|
||||
import 'package:fluffychat/pangea/controllers/contextual_definition_controller.dart';
|
||||
import 'package:fluffychat/pangea/controllers/language_controller.dart';
|
||||
import 'package:fluffychat/pangea/controllers/language_detection_controller.dart';
|
||||
import 'package:fluffychat/pangea/controllers/language_list_controller.dart';
|
||||
import 'package:fluffychat/pangea/controllers/local_settings.dart';
|
||||
import 'package:fluffychat/pangea/controllers/message_data_controller.dart';
|
||||
|
|
@ -17,6 +18,7 @@ import 'package:fluffychat/pangea/controllers/text_to_speech_controller.dart';
|
|||
import 'package:fluffychat/pangea/controllers/user_controller.dart';
|
||||
import 'package:fluffychat/pangea/controllers/word_net_controller.dart';
|
||||
import 'package:fluffychat/pangea/extensions/client_extension.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/guard/p_vguard.dart';
|
||||
import 'package:fluffychat/pangea/utils/bot_name.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
|
|
@ -50,6 +52,7 @@ class PangeaController {
|
|||
late SubscriptionController subscriptionController;
|
||||
late TextToSpeechController textToSpeech;
|
||||
late SpeechToTextController speechToText;
|
||||
late LanguageDetectionController languageDetection;
|
||||
|
||||
///store Services
|
||||
late PLocalStore pStoreService;
|
||||
|
|
@ -97,6 +100,7 @@ class PangeaController {
|
|||
itFeedback = ITFeedbackController(this);
|
||||
textToSpeech = TextToSpeechController(this);
|
||||
speechToText = SpeechToTextController(this);
|
||||
languageDetection = LanguageDetectionController(this);
|
||||
PAuthGaurd.pController = this;
|
||||
}
|
||||
|
||||
|
|
@ -272,6 +276,16 @@ class PangeaController {
|
|||
}
|
||||
|
||||
Future<void> setPangeaPushRules() async {
|
||||
final List<Room> analyticsRooms =
|
||||
matrixState.client.rooms.where((room) => room.isAnalyticsRoom).toList();
|
||||
|
||||
for (final Room room in analyticsRooms) {
|
||||
final pushRule = room.pushRuleState;
|
||||
if (pushRule != PushRuleState.dontNotify) {
|
||||
await room.setPushRuleState(PushRuleState.dontNotify);
|
||||
}
|
||||
}
|
||||
|
||||
if (!(matrixState.client.globalPushRules?.override?.any(
|
||||
(element) => element.ruleId == PangeaEventTypes.textToSpeechRule,
|
||||
) ??
|
||||
|
|
|
|||
|
|
@ -74,18 +74,21 @@ class SpeechToTextController {
|
|||
}
|
||||
debugPrint('Saving transcript as matrix event');
|
||||
|
||||
requestModel.audioEvent?.room.sendPangeaEvent(
|
||||
content: PangeaRepresentation(
|
||||
langCode: response.langCode,
|
||||
text: response.transcript.text,
|
||||
originalSent: false,
|
||||
originalWritten: false,
|
||||
speechToText: response,
|
||||
).toJson(),
|
||||
parentEventId: requestModel.audioEvent!.eventId,
|
||||
type: PangeaEventTypes.representation,
|
||||
);
|
||||
debugPrint('Transcript saved as matrix event');
|
||||
requestModel.audioEvent?.room
|
||||
.sendPangeaEvent(
|
||||
content: PangeaRepresentation(
|
||||
langCode: response.langCode,
|
||||
text: response.transcript.text,
|
||||
originalSent: false,
|
||||
originalWritten: false,
|
||||
speechToText: response,
|
||||
).toJson(),
|
||||
parentEventId: requestModel.audioEvent!.eventId,
|
||||
type: PangeaEventTypes.representation,
|
||||
)
|
||||
.then(
|
||||
(_) => debugPrint('Transcript saved as matrix event'),
|
||||
);
|
||||
|
||||
return Future.value(null);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
enum MessageMode { translation, definition, speechToText, textToSpeech }
|
||||
|
||||
|
|
@ -52,4 +53,17 @@ extension MessageModeExtension on MessageMode {
|
|||
.oopsSomethingWentWrong; // Title to indicate an error or unsupported mode
|
||||
}
|
||||
}
|
||||
|
||||
bool isValidMode(Event event) {
|
||||
switch (this) {
|
||||
case MessageMode.translation:
|
||||
case MessageMode.textToSpeech:
|
||||
case MessageMode.definition:
|
||||
return event.messageType == MessageTypes.Text;
|
||||
case MessageMode.speechToText:
|
||||
return event.messageType == MessageTypes.Audio;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ extension PangeaClient on Client {
|
|||
for (final classRoom in classesAndExchangesImIn) {
|
||||
for (final teacher in await classRoom.teachers) {
|
||||
// If person requesting list of teachers is a teacher in another classroom, don't add them to the list
|
||||
if (!teachers.any((e) => e.id == teacher.id) && userID != teacher.id) {
|
||||
if (!teachers.any((e) => e.id == teacher.id) && userID != teacher.id) {
|
||||
teachers.add(teacher);
|
||||
}
|
||||
}
|
||||
|
|
@ -123,7 +123,7 @@ extension PangeaClient on Client {
|
|||
for (final room in rooms) {
|
||||
if (room.partial) await room.postLoad();
|
||||
}
|
||||
|
||||
|
||||
final Room? analyticsRoom = analyticsRoomLocal(langCode);
|
||||
|
||||
if (analyticsRoom != null) return analyticsRoom;
|
||||
|
|
@ -168,14 +168,20 @@ extension PangeaClient on Client {
|
|||
// BotName.localBot,
|
||||
BotName.byEnvironment,
|
||||
],
|
||||
visibility: Visibility.private,
|
||||
roomAliasName: "${userID!.localpart}_${langCode}_analytics",
|
||||
);
|
||||
if (getRoomById(roomID) == null) {
|
||||
// Wait for room actually appears in sync
|
||||
await waitForRoomInSync(roomID, join: true);
|
||||
}
|
||||
|
||||
final Room? analyticsRoom = getRoomById(roomID);
|
||||
|
||||
// add this analytics room to all spaces so teachers can join them
|
||||
// via the space hierarchy
|
||||
await analyticsRoom?.addAnalyticsRoomToSpaces();
|
||||
|
||||
// and invite all teachers to new analytics room
|
||||
await analyticsRoom?.inviteTeachersToAnalyticsRoom();
|
||||
return getRoomById(roomID)!;
|
||||
}
|
||||
|
||||
|
|
@ -245,4 +251,85 @@ extension PangeaClient on Client {
|
|||
editEvents.add(originalEvent);
|
||||
return editEvents.slice(1).map((e) => e.eventId).toList();
|
||||
}
|
||||
|
||||
// Get all my analytics rooms
|
||||
List<Room> get allMyAnalyticsRooms => rooms
|
||||
.where(
|
||||
(e) => e.isAnalyticsRoomOfUser(userID!),
|
||||
)
|
||||
.toList();
|
||||
|
||||
// migration function to change analytics rooms' vsibility to public
|
||||
// so they will appear in the space hierarchy
|
||||
Future<void> updateAnalyticsRoomVisibility() async {
|
||||
final List<Future> makePublicFutures = [];
|
||||
for (final Room room in allMyAnalyticsRooms) {
|
||||
final visability = await getRoomVisibilityOnDirectory(room.id);
|
||||
if (visability != Visibility.public) {
|
||||
await setRoomVisibilityOnDirectory(
|
||||
room.id,
|
||||
visibility: Visibility.public,
|
||||
);
|
||||
}
|
||||
}
|
||||
await Future.wait(makePublicFutures);
|
||||
}
|
||||
|
||||
// Add all the users' analytics room to all the spaces the student studies in
|
||||
// So teachers can join them via space hierarchy
|
||||
// Will not always work, as there may be spaces where students don't have permission to add chats
|
||||
// But allows teachers to join analytics rooms without being invited
|
||||
Future<void> addAnalyticsRoomsToAllSpaces() async {
|
||||
final List<Future> addFutures = [];
|
||||
for (final Room room in allMyAnalyticsRooms) {
|
||||
addFutures.add(room.addAnalyticsRoomToSpaces());
|
||||
}
|
||||
await Future.wait(addFutures);
|
||||
}
|
||||
|
||||
// Invite teachers to all my analytics room
|
||||
// Handles case when students cannot add analytics room to space(s)
|
||||
// So teacher is still able to get analytics data for this student
|
||||
Future<void> inviteAllTeachersToAllAnalyticsRooms() async {
|
||||
final List<Future> inviteFutures = [];
|
||||
for (final Room analyticsRoom in allMyAnalyticsRooms) {
|
||||
inviteFutures.add(analyticsRoom.inviteTeachersToAnalyticsRoom());
|
||||
}
|
||||
await Future.wait(inviteFutures);
|
||||
}
|
||||
|
||||
// Join all analytics rooms in all spaces
|
||||
// Allows teachers to join analytics rooms without being invited
|
||||
Future<void> joinAnalyticsRoomsInAllSpaces() async {
|
||||
final List<Future> joinFutures = [];
|
||||
for (final Room space in (await classesAndExchangesImTeaching)) {
|
||||
joinFutures.add(space.joinAnalyticsRoomsInSpace());
|
||||
}
|
||||
await Future.wait(joinFutures);
|
||||
}
|
||||
|
||||
// Join invited analytics rooms
|
||||
// Checks for invites to any student analytics rooms
|
||||
// Handles case of analytics rooms that can't be added to some space(s)
|
||||
Future<void> joinInvitedAnalyticsRooms() async {
|
||||
for (final Room room in rooms) {
|
||||
if (room.membership == Membership.invite && room.isAnalyticsRoom) {
|
||||
try {
|
||||
await room.join();
|
||||
} catch (err) {
|
||||
debugPrint("Failed to join analytics room ${room.id}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// helper function to join all relevant analytics rooms
|
||||
// and set up those rooms to be joined by relevant teachers
|
||||
Future<void> migrateAnalyticsRooms() async {
|
||||
await updateAnalyticsRoomVisibility();
|
||||
await addAnalyticsRoomsToAllSpaces();
|
||||
await inviteAllTeachersToAllAnalyticsRooms();
|
||||
await joinInvitedAnalyticsRooms();
|
||||
await joinAnalyticsRoomsInAllSpaces();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fluffychat/pangea/constants/class_default_values.dart';
|
||||
import 'package:fluffychat/pangea/constants/model_keys.dart';
|
||||
import 'package:fluffychat/pangea/constants/pangea_room_types.dart';
|
||||
|
|
@ -442,6 +443,7 @@ extension PangeaRoom on Room {
|
|||
/// save RoomAnalytics object to PangeaEventTypes.analyticsSummary event
|
||||
Future<Event?> _createStudentAnalyticsEvent() async {
|
||||
try {
|
||||
await postLoad();
|
||||
if (!pangeaCanSendEvent(PangeaEventTypes.studentAnalyticsSummary)) {
|
||||
ErrorHandler.logError(
|
||||
m: "null powerLevels in createStudentAnalytics",
|
||||
|
|
@ -453,7 +455,7 @@ extension PangeaRoom on Room {
|
|||
debugger(when: kDebugMode);
|
||||
throw Exception("null userId in createStudentAnalytics");
|
||||
}
|
||||
await postLoad();
|
||||
|
||||
final String eventId = await client.setRoomStateWithKey(
|
||||
id,
|
||||
PangeaEventTypes.studentAnalyticsSummary,
|
||||
|
|
@ -791,31 +793,6 @@ extension PangeaRoom on Room {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> makeSureTeachersAreInvitedToAnalyticsRoom() async {
|
||||
try {
|
||||
if (!isAnalyticsRoom) {
|
||||
throw Exception("not an analytics room");
|
||||
}
|
||||
if (!participantListComplete) {
|
||||
await requestParticipants();
|
||||
}
|
||||
final toAdd = [
|
||||
...getParticipants([Membership.invite, Membership.join])
|
||||
.map((e) => e.id),
|
||||
BotName.byEnvironment,
|
||||
];
|
||||
for (final teacher in (await client.myTeachers)) {
|
||||
if (!toAdd.contains(teacher.id)) {
|
||||
debugPrint("inviting ${teacher.id} to analytics room");
|
||||
await invite(teacher.id);
|
||||
}
|
||||
}
|
||||
} catch (err, stack) {
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(e: err, s: stack);
|
||||
}
|
||||
}
|
||||
|
||||
/// update state event and return eventId
|
||||
Future<String> updateStateEvent(Event stateEvent) {
|
||||
if (stateEvent.stateKey == null) {
|
||||
|
|
@ -838,6 +815,9 @@ extension PangeaRoom on Room {
|
|||
);
|
||||
return false;
|
||||
}
|
||||
if (room != null && !room.isRoomAdmin) {
|
||||
return false;
|
||||
}
|
||||
if (!pangeaCanSendEvent(EventTypes.spaceChild) && !isRoomAdmin) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1059,4 +1039,223 @@ extension PangeaRoom on Room {
|
|||
getState(PangeaEventTypes.botOptions)?.content ?? {},
|
||||
);
|
||||
}
|
||||
|
||||
// add 1 analytics room to 1 space
|
||||
Future<void> addAnalyticsRoomToSpace(Room analyticsRoom) async {
|
||||
if (!isSpace) {
|
||||
debugPrint("addAnalyticsRoomToSpace called on non-space room");
|
||||
Sentry.addBreadcrumb(
|
||||
Breadcrumb(
|
||||
message: "addAnalyticsRoomToSpace called on non-space room",
|
||||
),
|
||||
);
|
||||
return Future.value();
|
||||
}
|
||||
|
||||
if (spaceChildren.any((sc) => sc.roomId == analyticsRoom.id)) return;
|
||||
if (canIAddSpaceChild(null)) {
|
||||
try {
|
||||
await setSpaceChild(analyticsRoom.id);
|
||||
} catch (err) {
|
||||
debugPrint(
|
||||
"Failed to add analytics room ${analyticsRoom.id} for student to space $id",
|
||||
);
|
||||
Sentry.addBreadcrumb(
|
||||
Breadcrumb(
|
||||
message: "Failed to add analytics room to space $id",
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add analytics room to all spaces the user is a student in (1 analytics room to all spaces)
|
||||
// So teachers can join them via space hierarchy
|
||||
// Will not always work, as there may be spaces where students don't have permission to add chats
|
||||
// But allows teachers to join analytics rooms without being invited
|
||||
Future<void> addAnalyticsRoomToSpaces() async {
|
||||
if (!isAnalyticsRoomOfUser(client.userID!)) {
|
||||
debugPrint("addAnalyticsRoomToSpaces called on non-analytics room");
|
||||
Sentry.addBreadcrumb(
|
||||
Breadcrumb(
|
||||
message: "addAnalyticsRoomToSpaces called on non-analytics room",
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
for (final Room space in (await client.classesAndExchangesImStudyingIn)) {
|
||||
if (space.spaceChildren.any((sc) => sc.roomId == id)) continue;
|
||||
await space.addAnalyticsRoomToSpace(this);
|
||||
}
|
||||
}
|
||||
|
||||
// Add all analytics rooms to space
|
||||
// Similar to addAnalyticsRoomToSpaces, but all analytics room to 1 space
|
||||
Future<void> addAnalyticsRoomsToSpace() async {
|
||||
await postLoad();
|
||||
final List<Room> allMyAnalyticsRooms = client.allMyAnalyticsRooms;
|
||||
for (final Room analyticsRoom in allMyAnalyticsRooms) {
|
||||
await addAnalyticsRoomToSpace(analyticsRoom);
|
||||
}
|
||||
}
|
||||
|
||||
// invite teachers of 1 space to 1 analytics room
|
||||
Future<void> inviteSpaceTeachersToAnalyticsRoom(Room analyticsRoom) async {
|
||||
if (!isSpace) {
|
||||
debugPrint(
|
||||
"inviteSpaceTeachersToAnalyticsRoom called on non-space room",
|
||||
);
|
||||
Sentry.addBreadcrumb(
|
||||
Breadcrumb(
|
||||
message:
|
||||
"inviteSpaceTeachersToAnalyticsRoom called on non-space room",
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!analyticsRoom.participantListComplete) {
|
||||
await analyticsRoom.requestParticipants();
|
||||
}
|
||||
final List<User> participants = analyticsRoom.getParticipants();
|
||||
for (final User teacher in (await teachers)) {
|
||||
if (!participants.any((p) => p.id == teacher.id)) {
|
||||
try {
|
||||
await analyticsRoom.invite(teacher.id);
|
||||
} catch (err, s) {
|
||||
debugPrint(
|
||||
"Failed to invite teacher ${teacher.id} to analytics room ${analyticsRoom.id}",
|
||||
);
|
||||
ErrorHandler.logError(
|
||||
e: err,
|
||||
m: "Failed to invite teacher ${teacher.id} to analytics room ${analyticsRoom.id}",
|
||||
s: s,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Invite all teachers to 1 analytics room
|
||||
// Handles case when students cannot add analytics room to space
|
||||
// So teacher is still able to get analytics data for this student
|
||||
Future<void> inviteTeachersToAnalyticsRoom() async {
|
||||
if (client.userID == null) {
|
||||
debugPrint("inviteTeachersToAnalyticsRoom called with null userId");
|
||||
Sentry.addBreadcrumb(
|
||||
Breadcrumb(
|
||||
message: "inviteTeachersToAnalyticsRoom called with null userId",
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isAnalyticsRoomOfUser(client.userID!)) {
|
||||
debugPrint("inviteTeachersToAnalyticsRoom called on non-analytics room");
|
||||
Sentry.addBreadcrumb(
|
||||
Breadcrumb(
|
||||
message: "inviteTeachersToAnalyticsRoom called on non-analytics room",
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
for (final Room space in (await client.classesAndExchangesImStudyingIn)) {
|
||||
await space.inviteSpaceTeachersToAnalyticsRoom(this);
|
||||
}
|
||||
}
|
||||
|
||||
// Invite teachers of 1 space to all users' analytics rooms
|
||||
Future<void> inviteSpaceTeachersToAnalyticsRooms() async {
|
||||
for (final Room analyticsRoom in client.allMyAnalyticsRooms) {
|
||||
await inviteSpaceTeachersToAnalyticsRoom(analyticsRoom);
|
||||
}
|
||||
}
|
||||
|
||||
// Join analytics rooms in space
|
||||
// Allows teachers to join analytics rooms without being invited
|
||||
Future<void> joinAnalyticsRoomsInSpace() async {
|
||||
if (!isSpace) {
|
||||
debugPrint("joinAnalyticsRoomsInSpace called on non-space room");
|
||||
Sentry.addBreadcrumb(
|
||||
Breadcrumb(
|
||||
message: "joinAnalyticsRoomsInSpace called on non-space room",
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// added delay because without it power levels don't load and user is not
|
||||
// recognized as admin
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
await postLoad();
|
||||
|
||||
if (!isRoomAdmin) {
|
||||
debugPrint("joinAnalyticsRoomsInSpace called by non-admin");
|
||||
Sentry.addBreadcrumb(
|
||||
Breadcrumb(
|
||||
message: "joinAnalyticsRoomsInSpace called by non-admin",
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final spaceHierarchy = await client.getSpaceHierarchy(
|
||||
id,
|
||||
maxDepth: 1,
|
||||
);
|
||||
|
||||
final List<String> analyticsRoomIds = spaceHierarchy.rooms
|
||||
.where(
|
||||
(r) => r.roomType == PangeaRoomTypes.analytics,
|
||||
)
|
||||
.map((r) => r.roomId)
|
||||
.toList();
|
||||
|
||||
for (final String roomID in analyticsRoomIds) {
|
||||
try {
|
||||
await joinSpaceChild(roomID);
|
||||
} catch (err, s) {
|
||||
debugPrint("Failed to join analytics room $roomID in space $id");
|
||||
ErrorHandler.logError(
|
||||
e: err,
|
||||
m: "Failed to join analytics room $roomID in space $id",
|
||||
s: s,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> joinSpaceChild(String roomID) async {
|
||||
final Room? child = client.getRoomById(roomID);
|
||||
if (child == null) {
|
||||
await client.joinRoom(
|
||||
roomID,
|
||||
serverName: spaceChildren
|
||||
.firstWhereOrNull((child) => child.roomId == roomID)
|
||||
?.via,
|
||||
);
|
||||
if (client.getRoomById(roomID) == null) {
|
||||
await client.waitForRoomInSync(roomID, join: true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (![Membership.invite, Membership.join].contains(child.membership)) {
|
||||
final waitForRoom = client.waitForRoomInSync(
|
||||
roomID,
|
||||
join: true,
|
||||
);
|
||||
await child.join();
|
||||
await waitForRoom;
|
||||
}
|
||||
}
|
||||
|
||||
// check if analytics room exists for a given language code
|
||||
// and if not, create it
|
||||
Future<void> ensureAnalyticsRoomExists() async {
|
||||
await postLoad();
|
||||
if (firstLanguageSettings?.targetLanguage == null) return;
|
||||
await client.getMyAnalyticsRoom(firstLanguageSettings!.targetLanguage);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,18 +80,26 @@ class PangeaMessageEvent {
|
|||
return _latestEdit;
|
||||
}
|
||||
|
||||
bool showRichText(bool selected, bool highlighted) {
|
||||
bool showRichText(
|
||||
bool selected, {
|
||||
bool highlighted = false,
|
||||
bool isOverlay = false,
|
||||
}) {
|
||||
if (!_isValidPangeaMessageEvent) {
|
||||
return false;
|
||||
}
|
||||
// if (URLFinder.getMatches(event.body).isNotEmpty) {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
if ([EventStatus.error, EventStatus.sending].contains(_event.status)) {
|
||||
return false;
|
||||
}
|
||||
if (ownMessage && !selected && !highlighted) 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;
|
||||
}
|
||||
|
||||
|
|
@ -346,6 +354,19 @@ class PangeaMessageEvent {
|
|||
),
|
||||
);
|
||||
|
||||
_representations?.add(
|
||||
RepresentationEvent(
|
||||
timeline: timeline,
|
||||
content: PangeaRepresentation(
|
||||
langCode: response.langCode,
|
||||
text: response.transcript.text,
|
||||
originalSent: false,
|
||||
originalWritten: false,
|
||||
speechToText: response,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
|
|
@ -564,17 +585,20 @@ class PangeaMessageEvent {
|
|||
return langCode ?? LanguageKeys.unknownLanguage;
|
||||
}
|
||||
|
||||
PangeaMatch? firstErrorStep(String lemma) {
|
||||
List<PangeaMatch>? errorSteps(String lemma) {
|
||||
final RepresentationEvent? repEvent = originalSent ?? originalWritten;
|
||||
if (repEvent?.choreo == null) return null;
|
||||
|
||||
final PangeaMatch? step = repEvent!.choreo!.choreoSteps
|
||||
.firstWhereOrNull(
|
||||
(element) =>
|
||||
element.acceptedOrIgnoredMatch?.match.shortMessage == lemma,
|
||||
final List<PangeaMatch> steps = repEvent!.choreo!.choreoSteps
|
||||
.where(
|
||||
(choreoStep) =>
|
||||
choreoStep.acceptedOrIgnoredMatch != null &&
|
||||
choreoStep.acceptedOrIgnoredMatch?.match.shortMessage == lemma,
|
||||
)
|
||||
?.acceptedOrIgnoredMatch;
|
||||
return step;
|
||||
.map((element) => element.acceptedOrIgnoredMatch)
|
||||
.cast<PangeaMatch>()
|
||||
.toList();
|
||||
return steps;
|
||||
}
|
||||
|
||||
// List<SpanData> get activities =>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import 'dart:developer';
|
|||
import 'package:fluffychat/pangea/extensions/pangea_event_extension.dart';
|
||||
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_choreo_event.dart';
|
||||
import 'package:fluffychat/pangea/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/pangea/models/speech_to_text_models.dart';
|
||||
import 'package:fluffychat/pangea/models/tokens_event_content_model.dart';
|
||||
import 'package:fluffychat/pangea/repo/tokens_repo.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
|
@ -27,15 +26,12 @@ class RepresentationEvent {
|
|||
ChoreoRecord? _choreo;
|
||||
Timeline timeline;
|
||||
|
||||
SpeechToTextModel? _speechToTextResponse;
|
||||
|
||||
RepresentationEvent({
|
||||
required this.timeline,
|
||||
Event? event,
|
||||
PangeaRepresentation? content,
|
||||
PangeaMessageTokens? tokens,
|
||||
ChoreoRecord? choreo,
|
||||
SpeechToTextModel? speechToTextResponse,
|
||||
}) {
|
||||
if (event != null && event.type != PangeaEventTypes.representation) {
|
||||
throw Exception(
|
||||
|
|
@ -46,7 +42,6 @@ class RepresentationEvent {
|
|||
_content = content;
|
||||
_tokens = tokens;
|
||||
_choreo = choreo;
|
||||
_speechToTextResponse = speechToTextResponse;
|
||||
}
|
||||
|
||||
Event? get event => _event;
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ class PangeaToken {
|
|||
static const String _lemmaKey = ModelKey.lemma;
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
_textKey: text,
|
||||
_textKey: text.toJson(),
|
||||
_hasInfoKey: hasInfo,
|
||||
_lemmaKey: lemmas.map((e) => e.toJson()).toList(),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/enum/audio_encoding_enum.dart';
|
||||
import 'package:fluffychat/pangea/models/pangea_token_model.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
const int THRESHOLD_FOR_GREEN = 80;
|
||||
|
||||
class SpeechToTextAudioConfigModel {
|
||||
final AudioEncodingEnum encoding;
|
||||
final int sampleRateHertz;
|
||||
|
|
@ -93,13 +96,10 @@ class STTToken {
|
|||
? Colors.white
|
||||
: Colors.black);
|
||||
}
|
||||
if (confidence! > 80) {
|
||||
return const Color.fromARGB(255, 0, 152, 0);
|
||||
if (confidence! > THRESHOLD_FOR_GREEN) {
|
||||
return AppConfig.success;
|
||||
}
|
||||
if (confidence! > 50) {
|
||||
return const Color.fromARGB(255, 184, 142, 43);
|
||||
}
|
||||
return Colors.red;
|
||||
return AppConfig.warning;
|
||||
}
|
||||
|
||||
factory STTToken.fromJson(Map<String, dynamic> json) {
|
||||
|
|
@ -117,7 +117,7 @@ class STTToken {
|
|||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"token": token,
|
||||
"token": token.toJson(),
|
||||
"start_time": startTime?.inMilliseconds,
|
||||
"end_time": endTime?.inMilliseconds,
|
||||
"confidence": confidence,
|
||||
|
|
@ -150,14 +150,19 @@ class Transcript {
|
|||
final int confidence;
|
||||
final List<STTToken> sttTokens;
|
||||
final String langCode;
|
||||
final int? wordsPerHr;
|
||||
|
||||
Transcript({
|
||||
required this.text,
|
||||
required this.confidence,
|
||||
required this.sttTokens,
|
||||
required this.langCode,
|
||||
required this.wordsPerHr,
|
||||
});
|
||||
|
||||
/// Returns the number of words per minute rounded to one decimal place.
|
||||
double? get wordsPerMinute => wordsPerHr != null ? wordsPerHr! / 60 : null;
|
||||
|
||||
factory Transcript.fromJson(Map<String, dynamic> json) => Transcript(
|
||||
text: json['transcript'],
|
||||
confidence: json['confidence'] <= 100
|
||||
|
|
@ -167,6 +172,7 @@ class Transcript {
|
|||
.map((e) => STTToken.fromJson(e))
|
||||
.toList(),
|
||||
langCode: json['lang_code'],
|
||||
wordsPerHr: json['words_per_hr'],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
|
|
@ -174,7 +180,15 @@ class Transcript {
|
|||
"confidence": confidence,
|
||||
"stt_tokens": sttTokens.map((e) => e.toJson()).toList(),
|
||||
"lang_code": langCode,
|
||||
"words_per_hr": wordsPerHr,
|
||||
};
|
||||
|
||||
Color color(BuildContext context) {
|
||||
if (confidence > THRESHOLD_FOR_GREEN) {
|
||||
return AppConfig.success;
|
||||
}
|
||||
return AppConfig.warning;
|
||||
}
|
||||
}
|
||||
|
||||
class SpeechToTextResult {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/constants/model_keys.dart';
|
||||
import 'package:fluffychat/pangea/widgets/igc/word_data_card.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class WordData {
|
||||
final String word;
|
||||
|
|
@ -102,10 +101,11 @@ class WordData {
|
|||
}) =>
|
||||
word == w && userL1 == l1 && userL2 == l2 && fullText == f;
|
||||
|
||||
String formattedPartOfSpeech(LanguageType languageType) {
|
||||
String? formattedPartOfSpeech(LanguageType languageType) {
|
||||
final String pos = languageType == LanguageType.base
|
||||
? basePartOfSpeech
|
||||
: targetPartOfSpeech;
|
||||
if (pos.isEmpty) return null;
|
||||
return pos[0].toUpperCase() + pos.substring(1);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,13 +24,12 @@ class PApiUrls {
|
|||
/// ---------------------- Conversation Partner -------------------------
|
||||
static String searchUserProfiles = "/account/search";
|
||||
|
||||
///-------------------------------- Deprecated analytics --------------------
|
||||
static String classAnalytics = "${Environment.choreoApi}/class_analytics";
|
||||
static String messageService = "/message_service";
|
||||
|
||||
///-------------------------------- choreo --------------------------
|
||||
static String igc = "${Environment.choreoApi}/grammar";
|
||||
|
||||
static String languageDetection =
|
||||
"${Environment.choreoApi}/language_detection";
|
||||
|
||||
static String igcLite = "${Environment.choreoApi}/grammar_lite";
|
||||
static String spanDetails = "${Environment.choreoApi}/span_details";
|
||||
|
||||
|
|
|
|||
|
|
@ -52,7 +52,11 @@ class AnalyticsListTileState extends State<AnalyticsListTile> {
|
|||
child: Opacity(
|
||||
opacity: widget.enabled ? 1 : 0.5,
|
||||
child: Tooltip(
|
||||
message: widget.enabled ? "" : L10n.of(context)!.joinToView,
|
||||
message: widget.enabled
|
||||
? ""
|
||||
: widget.type == AnalyticsEntryType.room
|
||||
? L10n.of(context)!.joinToView
|
||||
: L10n.of(context)!.studentAnalyticsNotAvailable,
|
||||
child: ListTile(
|
||||
leading: widget.type == AnalyticsEntryType.privateChats
|
||||
? CircleAvatar(
|
||||
|
|
@ -101,18 +105,19 @@ class AnalyticsListTileState extends State<AnalyticsListTile> {
|
|||
: null,
|
||||
selected: widget.selected,
|
||||
enabled: widget.enabled,
|
||||
onTap: () =>
|
||||
(room?.isSpace ?? false) && widget.allowNavigateOnSelect
|
||||
? context.go(
|
||||
'/rooms/analytics/${room!.id}',
|
||||
)
|
||||
: widget.onTap(
|
||||
AnalyticsSelected(
|
||||
widget.id,
|
||||
widget.type,
|
||||
widget.displayName,
|
||||
),
|
||||
onTap: () {
|
||||
(room?.isSpace ?? false) && widget.allowNavigateOnSelect
|
||||
? context.go(
|
||||
'/rooms/analytics/${room!.id}',
|
||||
)
|
||||
: widget.onTap(
|
||||
AnalyticsSelected(
|
||||
widget.id,
|
||||
widget.type,
|
||||
widget.displayName,
|
||||
),
|
||||
);
|
||||
},
|
||||
trailing: (room?.isSpace ?? false) &&
|
||||
widget.type != AnalyticsEntryType.privateChats &&
|
||||
widget.allowNavigateOnSelect
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:fluffychat/pangea/enum/construct_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/extensions/client_extension.dart';
|
||||
import 'package:fluffychat/pangea/pages/analytics/base_analytics_view.dart';
|
||||
import 'package:fluffychat/pangea/pages/analytics/student_analytics/student_analytics.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
|
@ -142,14 +143,29 @@ class BaseAnalyticsController extends State<BaseAnalyticsPage> {
|
|||
}
|
||||
|
||||
bool enableSelection(AnalyticsSelected? selectedParam) {
|
||||
return selectedView == BarChartViewSelection.grammar &&
|
||||
selectedParam?.type == AnalyticsEntryType.room
|
||||
? Matrix.of(context)
|
||||
if (selectedView == BarChartViewSelection.grammar) {
|
||||
if (selectedParam?.type == AnalyticsEntryType.room) {
|
||||
return Matrix.of(context)
|
||||
.client
|
||||
.getRoomById(selectedParam!.id)
|
||||
?.membership ==
|
||||
Membership.join
|
||||
: true;
|
||||
Membership.join;
|
||||
}
|
||||
|
||||
if (selectedParam?.type == AnalyticsEntryType.student) {
|
||||
final String? langCode =
|
||||
pangeaController.languageController.activeL2Code(
|
||||
roomID: widget.defaultSelected.id,
|
||||
);
|
||||
if (langCode == null) return false;
|
||||
return Matrix.of(context).client.analyticsRoomLocal(
|
||||
langCode,
|
||||
selectedParam?.id,
|
||||
) !=
|
||||
null;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -246,6 +246,15 @@ class BaseAnalyticsView extends StatelessWidget {
|
|||
.widget
|
||||
.tabs[1]
|
||||
.allowNavigateOnSelect,
|
||||
enabled:
|
||||
controller.enableSelection(
|
||||
AnalyticsSelected(
|
||||
item.id,
|
||||
controller
|
||||
.widget.tabs[1].type,
|
||||
"",
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:fluffychat/pangea/constants/pangea_event_types.dart';
|
||||
import 'package:fluffychat/pangea/constants/pangea_room_types.dart';
|
||||
import 'package:fluffychat/pangea/enum/construct_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/models/chart_analytics_model.dart';
|
||||
|
|
@ -103,7 +104,11 @@ class ClassAnalyticsV2Controller extends State<ClassAnalyticsPage> {
|
|||
|
||||
students = classRoom!.students;
|
||||
chats = response.rooms
|
||||
.where((room) => room.roomId != classRoom!.id)
|
||||
.where(
|
||||
(room) =>
|
||||
room.roomId != classRoom!.id &&
|
||||
room.roomType != PangeaRoomTypes.analytics,
|
||||
)
|
||||
.toList();
|
||||
chats.sort((a, b) => a.roomType == 'm.space' ? -1 : 1);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
|
|
@ -20,7 +21,12 @@ class ClassAnalyticsView extends StatelessWidget {
|
|||
.map(
|
||||
(room) => TabItem(
|
||||
avatar: room.avatarUrl,
|
||||
displayName: room.name ?? "",
|
||||
displayName: room.name ??
|
||||
Matrix.of(context)
|
||||
.client
|
||||
.getRoomById(room.roomId)
|
||||
?.getLocalizedDisplayname() ??
|
||||
"",
|
||||
id: room.roomId,
|
||||
),
|
||||
)
|
||||
|
|
@ -33,7 +39,7 @@ class ClassAnalyticsView extends StatelessWidget {
|
|||
.map(
|
||||
(s) => TabItem(
|
||||
avatar: s.avatarUrl,
|
||||
displayName: s.displayName ?? "unknown",
|
||||
displayName: s.calcDisplayname(),
|
||||
id: s.id,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_representation_ev
|
|||
import 'package:fluffychat/pangea/models/constructs_analytics_model.dart';
|
||||
import 'package:fluffychat/pangea/models/pangea_match_model.dart';
|
||||
import 'package:fluffychat/pangea/pages/analytics/base_analytics.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:fluffychat/utils/date_time_extension.dart';
|
||||
import 'package:fluffychat/utils/string_color.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
|
@ -54,7 +55,7 @@ class ConstructListState extends State<ConstructList> {
|
|||
selected: widget.selected,
|
||||
forceUpdate: true,
|
||||
)
|
||||
.then((_) => setState(() => initialized = true));
|
||||
.whenComplete(() => setState(() => initialized = true));
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -160,11 +161,11 @@ class ConstructListViewState extends State<ConstructListView> {
|
|||
stateSub?.cancel();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(ConstructListView oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
fetchUses();
|
||||
}
|
||||
// @override
|
||||
// void didUpdateWidget(ConstructListView oldWidget) {
|
||||
// super.didUpdateWidget(oldWidget);
|
||||
// fetchUses();
|
||||
// }
|
||||
|
||||
int get lemmaIndex =>
|
||||
constructs?.indexWhere(
|
||||
|
|
@ -215,19 +216,29 @@ class ConstructListViewState extends State<ConstructListView> {
|
|||
}
|
||||
|
||||
setState(() => fetchingUses = true);
|
||||
final List<OneConstructUse> uses = currentConstruct!.content.uses;
|
||||
_msgEvents.clear();
|
||||
try {
|
||||
final List<OneConstructUse> uses = currentConstruct!.content.uses;
|
||||
_msgEvents.clear();
|
||||
|
||||
for (final OneConstructUse use in uses) {
|
||||
final PangeaMessageEvent? msgEvent = await getMessageEvent(use);
|
||||
final RepresentationEvent? repEvent =
|
||||
msgEvent?.originalSent ?? msgEvent?.originalWritten;
|
||||
if (repEvent?.choreo == null) {
|
||||
continue;
|
||||
for (final OneConstructUse use in uses) {
|
||||
final PangeaMessageEvent? msgEvent = await getMessageEvent(use);
|
||||
final RepresentationEvent? repEvent =
|
||||
msgEvent?.originalSent ?? msgEvent?.originalWritten;
|
||||
if (repEvent?.choreo == null) {
|
||||
continue;
|
||||
}
|
||||
_msgEvents.add(msgEvent!);
|
||||
}
|
||||
_msgEvents.add(msgEvent!);
|
||||
setState(() => fetchingUses = false);
|
||||
} catch (err, s) {
|
||||
setState(() => fetchingUses = false);
|
||||
debugPrint("Error fetching uses: $err");
|
||||
ErrorHandler.logError(
|
||||
e: err,
|
||||
s: s,
|
||||
m: "Failed to fetch uses for current construct ${currentConstruct?.content.lemma}",
|
||||
);
|
||||
}
|
||||
setState(() => fetchingUses = false);
|
||||
}
|
||||
|
||||
List<ConstructEvent>? get constructs =>
|
||||
|
|
@ -237,6 +248,38 @@ class ConstructListViewState extends State<ConstructListView> {
|
|||
(element) => element.content.lemma == widget.controller.currentLemma,
|
||||
);
|
||||
|
||||
// given the current lemma and list of message events, return a list of
|
||||
// MessageEventMatch objects, which contain one PangeaMessageEvent to one PangeaMatch
|
||||
// this is because some message events may have has more than one PangeaMatch of a
|
||||
// given lemma type.
|
||||
List<MessageEventMatch> getMessageEventMatches() {
|
||||
if (widget.controller.currentLemma == null) return [];
|
||||
final List<MessageEventMatch> allMsgErrorSteps = [];
|
||||
|
||||
for (final msgEvent in _msgEvents) {
|
||||
if (allMsgErrorSteps.any(
|
||||
(element) => element.msgEvent.eventId == msgEvent.eventId,
|
||||
)) {
|
||||
continue;
|
||||
}
|
||||
// get all the pangea matches in that message which have that lemma
|
||||
final List<PangeaMatch>? msgErrorSteps = msgEvent.errorSteps(
|
||||
widget.controller.currentLemma!,
|
||||
);
|
||||
if (msgErrorSteps == null) continue;
|
||||
|
||||
allMsgErrorSteps.addAll(
|
||||
msgErrorSteps.map(
|
||||
(errorStep) => MessageEventMatch(
|
||||
msgEvent: msgEvent,
|
||||
lemmaMatch: errorStep,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return allMsgErrorSteps;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!widget.init || fetchingUses) {
|
||||
|
|
@ -251,6 +294,8 @@ class ConstructListViewState extends State<ConstructListView> {
|
|||
);
|
||||
}
|
||||
|
||||
final msgEventMatches = getMessageEventMatches();
|
||||
|
||||
return widget.controller.currentLemma == null
|
||||
? Expanded(
|
||||
child: ListView.builder(
|
||||
|
|
@ -278,23 +323,22 @@ class ConstructListViewState extends State<ConstructListView> {
|
|||
children: [
|
||||
if (constructs![lemmaIndex].content.uses.length >
|
||||
_msgEvents.length)
|
||||
const Center(
|
||||
Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
"Some data may be missing from rooms in which you are not a member.",
|
||||
),
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(L10n.of(context)!.roomDataMissing),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ListView.separated(
|
||||
separatorBuilder: (context, index) =>
|
||||
const Divider(height: 1),
|
||||
itemCount: _msgEvents.length,
|
||||
itemCount: msgEventMatches.length,
|
||||
itemBuilder: (context, index) {
|
||||
return ConstructMessage(
|
||||
msgEvent: _msgEvents[index],
|
||||
msgEvent: msgEventMatches[index].msgEvent,
|
||||
lemma: widget.controller.currentLemma!,
|
||||
errorMessage: msgEventMatches[index].lemmaMatch,
|
||||
);
|
||||
},
|
||||
),
|
||||
|
|
@ -307,21 +351,18 @@ class ConstructListViewState extends State<ConstructListView> {
|
|||
|
||||
class ConstructMessage extends StatelessWidget {
|
||||
final PangeaMessageEvent msgEvent;
|
||||
final PangeaMatch errorMessage;
|
||||
final String lemma;
|
||||
|
||||
const ConstructMessage({
|
||||
super.key,
|
||||
required this.msgEvent,
|
||||
required this.errorMessage,
|
||||
required this.lemma,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final PangeaMatch? errorMessage = msgEvent.firstErrorStep(lemma);
|
||||
if (errorMessage == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final String? chosen = errorMessage.match.choices
|
||||
?.firstWhereOrNull(
|
||||
(element) => element.selected == true,
|
||||
|
|
@ -479,6 +520,14 @@ class ConstructMessageMetadata extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final String roomName = msgEvent.event.room.name.isEmpty
|
||||
? Matrix.of(context)
|
||||
.client
|
||||
.getRoomById(msgEvent.event.room.id)
|
||||
?.getLocalizedDisplayname() ??
|
||||
""
|
||||
: msgEvent.event.room.name;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(10, 0, 30, 0),
|
||||
child: Column(
|
||||
|
|
@ -487,9 +536,19 @@ class ConstructMessageMetadata extends StatelessWidget {
|
|||
msgEvent.event.originServerTs.localizedTime(context),
|
||||
style: TextStyle(fontSize: 13 * AppConfig.fontSizeFactor),
|
||||
),
|
||||
Text(msgEvent.event.room.name),
|
||||
Text(roomName),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MessageEventMatch {
|
||||
final PangeaMessageEvent msgEvent;
|
||||
final PangeaMatch lemmaMatch;
|
||||
|
||||
MessageEventMatch({
|
||||
required this.msgEvent,
|
||||
required this.lemmaMatch,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,96 +0,0 @@
|
|||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
|
||||
import 'package:fluffychat/widgets/layouts/empty_page.dart';
|
||||
import '../../../widgets/matrix.dart';
|
||||
import '../../utils/error_handler.dart';
|
||||
import '../../utils/set_class_name.dart';
|
||||
import '../../widgets/space/class_settings.dart';
|
||||
import 'class_settings_view.dart';
|
||||
import 'p_class_widgets/room_rules_editor.dart';
|
||||
|
||||
class ClassSettingsPage extends StatefulWidget {
|
||||
const ClassSettingsPage({super.key});
|
||||
|
||||
@override
|
||||
State<ClassSettingsPage> createState() => ClassSettingsController();
|
||||
}
|
||||
|
||||
class ClassSettingsController extends State<ClassSettingsPage> {
|
||||
PangeaController pangeaController = MatrixState.pangeaController;
|
||||
|
||||
final GlobalKey<RoomRulesState> rulesEditorKey = GlobalKey<RoomRulesState>();
|
||||
final GlobalKey<ClassSettingsState> classSettingsKey =
|
||||
GlobalKey<ClassSettingsState>();
|
||||
|
||||
Room? room;
|
||||
|
||||
String? get roomId => GoRouterState.of(context).pathParameters['roomid'];
|
||||
|
||||
Future<void> handleSave(BuildContext context) async {
|
||||
if (classSettingsKey.currentState!.sameLanguages) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(L10n.of(context)!.noIdenticalLanguages),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (rulesEditorKey.currentState != null) {
|
||||
await rulesEditorKey.currentState?.setRoomRules(roomId!);
|
||||
} else {
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(m: "Null rules editor state");
|
||||
}
|
||||
if (classSettingsKey.currentState != null) {
|
||||
await classSettingsKey.currentState?.setClassSettings(
|
||||
roomId!,
|
||||
);
|
||||
} else {
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(m: "Null class settings state");
|
||||
}
|
||||
}
|
||||
|
||||
void goback(BuildContext context) {
|
||||
context.push("/spaces/$roomId");
|
||||
}
|
||||
|
||||
String get className =>
|
||||
Matrix.of(context).client.getRoomById(roomId!)?.name ?? '';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
// TODO: implement initState
|
||||
super.initState();
|
||||
|
||||
Future.delayed(Duration.zero, () {
|
||||
room = Matrix.of(context).client.getRoomById(roomId!);
|
||||
if (room == null) {
|
||||
debugger(when: kDebugMode);
|
||||
context.pop();
|
||||
}
|
||||
setState(() {});
|
||||
});
|
||||
}
|
||||
//PTODO - show loading widget
|
||||
|
||||
void setDisplaynameAction() => setClassDisplayname(context, roomId);
|
||||
|
||||
bool showEditNameIcon = false;
|
||||
void hoverEditNameIcon(bool hovering) =>
|
||||
setState(() => showEditNameIcon = !showEditNameIcon);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => room != null
|
||||
? ClassSettingsPageView(controller: this)
|
||||
: const EmptyPage();
|
||||
}
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/pages/class_settings/class_settings_page.dart';
|
||||
import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/room_rules_editor.dart';
|
||||
import 'package:fluffychat/pangea/widgets/space/class_settings.dart';
|
||||
import '../../../widgets/layouts/max_width_body.dart';
|
||||
|
||||
class ClassSettingsPageView extends StatelessWidget {
|
||||
final ClassSettingsController controller;
|
||||
const ClassSettingsPageView({super.key, required this.controller});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
debugPrint("in class settings page with roomId ${controller.roomId}");
|
||||
// PTODO-Lala - make the page scrollable anywhere, not just in the area of the elements
|
||||
// so like, the user should be able scroll using the mouse wheel from anywhere within this view
|
||||
// currently, your cursor needs be horizontally within the tiles in order to scroll
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: GoRouterState.of(context).path?.startsWith('/spaces/') ?? false
|
||||
? null
|
||||
: IconButton(
|
||||
icon: const Icon(Icons.close_outlined),
|
||||
onPressed: () => controller.goback(context),
|
||||
),
|
||||
centerTitle: true,
|
||||
title: Text(L10n.of(context)!.classSettings),
|
||||
),
|
||||
body: ListView(
|
||||
children: [
|
||||
MaxWidthBody(
|
||||
child: ListTile(
|
||||
title: Center(
|
||||
child: TextButton.icon(
|
||||
onPressed: controller.setDisplaynameAction,
|
||||
onHover: controller.hoverEditNameIcon,
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 25),
|
||||
),
|
||||
label: Visibility(
|
||||
visible: controller.showEditNameIcon,
|
||||
child: Icon(
|
||||
Icons.edit,
|
||||
color: Theme.of(context).colorScheme.onBackground,
|
||||
),
|
||||
),
|
||||
icon: Text(
|
||||
controller.className,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
MaxWidthBody(
|
||||
child: Column(
|
||||
children: [
|
||||
ClassSettings(
|
||||
roomId: controller.roomId,
|
||||
startOpen: true,
|
||||
),
|
||||
RoomRulesEditor(roomId: controller.roomId),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
onPressed: () => showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => controller.handleSave(context),
|
||||
),
|
||||
label: Text(L10n.of(context)!.saveChanges),
|
||||
icon: const Icon(Icons.save_outlined),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class ClassSettingsButton extends StatelessWidget {
|
||||
const ClassSettingsButton({super.key});
|
||||
|
||||
// final PangeaController _pangeaController = MatrixState.pangeaController;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// final roomId = GoRouterState.of(context).pathParameters['roomid'];
|
||||
|
||||
final iconColor = Theme.of(context).textTheme.bodyLarge!.color;
|
||||
return Column(
|
||||
children: [
|
||||
ListTile(
|
||||
// enabled: roomId != null &&
|
||||
// _pangeaController.classController
|
||||
// .getClassModelBySpaceIdLocal(roomId) !=
|
||||
// null,
|
||||
title: Text(
|
||||
L10n.of(context)!.classSettings,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
subtitle: Text(L10n.of(context)!.classSettingsDesc),
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
foregroundColor: iconColor,
|
||||
child: const Icon(Icons.settings_outlined),
|
||||
),
|
||||
onTap: () => context.go('/class_settings'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,149 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/utils/delete_room.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class DeleteSpaceTile extends StatelessWidget {
|
||||
final Room room;
|
||||
|
||||
const DeleteSpaceTile({
|
||||
super.key,
|
||||
required this.room,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bool classNameMatch = true;
|
||||
final textController = TextEditingController();
|
||||
Future<void> deleteSpace() async {
|
||||
final Client client = Matrix.of(context).client;
|
||||
final GetSpaceHierarchyResponse spaceHierarchy =
|
||||
await client.getSpaceHierarchy(room.id);
|
||||
|
||||
if (spaceHierarchy.rooms.isNotEmpty) {
|
||||
final List<Room> spaceChats = spaceHierarchy.rooms
|
||||
.where((c) => c.roomId != room.id)
|
||||
.map((e) => Matrix.of(context).client.getRoomById(e.roomId))
|
||||
.where((c) => c != null && !c.isSpace && !c.isDirectChat)
|
||||
.cast<Room>()
|
||||
.toList();
|
||||
|
||||
await Future.wait(
|
||||
spaceChats.map((c) => deleteRoom(c.id, client)),
|
||||
);
|
||||
}
|
||||
deleteRoom(room.id, client);
|
||||
context.go('/rooms');
|
||||
return;
|
||||
}
|
||||
|
||||
Future<void> deleteChat() {
|
||||
context.go('/rooms');
|
||||
return deleteRoom(room.id, Matrix.of(context).client);
|
||||
}
|
||||
|
||||
Future<void> deleteChatAction() async {
|
||||
showDialog(
|
||||
context: context,
|
||||
useRootNavigator: false,
|
||||
builder: (context) {
|
||||
return StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
return AlertDialog(
|
||||
title: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
room.isSpace
|
||||
? L10n.of(context)!.areYouSureDeleteClass
|
||||
: L10n.of(context)!.areYouSureDeleteGroup,
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Text(
|
||||
L10n.of(context)!.cannotBeReversed,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
if (room.isSpace)
|
||||
Text(
|
||||
L10n.of(context)!.enterDeletedClassName,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
content: room.isSpace
|
||||
? TextField(
|
||||
autofocus: true,
|
||||
controller: textController,
|
||||
decoration: InputDecoration(
|
||||
hintText: room.name,
|
||||
errorText: !classNameMatch
|
||||
? L10n.of(context)!.incorrectClassName
|
||||
: null,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
child: Text(L10n.of(context)!.ok),
|
||||
onPressed: () async {
|
||||
if (room.isSpace) {
|
||||
setState(() {
|
||||
classNameMatch = textController.text == room.name;
|
||||
});
|
||||
if (classNameMatch) {
|
||||
Navigator.of(context).pop();
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => deleteSpace(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => deleteChat(),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text(L10n.of(context)!.cancel),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return ListTile(
|
||||
trailing: const Icon(Icons.delete_outlined),
|
||||
title: Text(
|
||||
room.isSpace
|
||||
? L10n.of(context)!.deleteSpace
|
||||
: L10n.of(context)!.deleteGroup,
|
||||
style: const TextStyle(color: Colors.red),
|
||||
),
|
||||
onTap: () => deleteChatAction(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,122 +0,0 @@
|
|||
import 'dart:developer';
|
||||
|
||||
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 'package:go_router/go_router.dart';
|
||||
import 'package:matrix/matrix.dart' as sdk;
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/pages/new_class/new_class_view.dart';
|
||||
import 'package:fluffychat/pangea/utils/class_code.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import '../../controllers/pangea_controller.dart';
|
||||
import '../../widgets/space/class_settings.dart';
|
||||
import '../class_settings/p_class_widgets/room_rules_editor.dart';
|
||||
|
||||
class NewClass extends StatefulWidget {
|
||||
const NewClass({super.key});
|
||||
|
||||
@override
|
||||
NewClassController createState() => NewClassController();
|
||||
}
|
||||
|
||||
class NewClassController extends State<NewClass> {
|
||||
TextEditingController controller = TextEditingController();
|
||||
|
||||
final PangeaController pangeaController = MatrixState.pangeaController;
|
||||
final GlobalKey<RoomRulesState> rulesEditorKey = GlobalKey<RoomRulesState>();
|
||||
final GlobalKey<ClassSettingsState> classSettingsKey =
|
||||
GlobalKey<ClassSettingsState>();
|
||||
|
||||
void submitAction([_]) async {
|
||||
//TODO: validate that object is complete
|
||||
final matrix = Matrix.of(context);
|
||||
if (controller.text.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(L10n.of(context)!.classNameRequired),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (classSettingsKey.currentState == null) {
|
||||
debugger(when: kDebugMode);
|
||||
}
|
||||
if (classSettingsKey.currentState!.sameLanguages) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(L10n.of(context)!.noIdenticalLanguages),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final roomID = await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () async {
|
||||
final String roomID = await matrix.client.createRoom(
|
||||
//PTODO - investigate effects of changing visibility from public
|
||||
preset: sdk.CreateRoomPreset.publicChat,
|
||||
creationContent: {
|
||||
'type': RoomCreationTypes.mSpace,
|
||||
},
|
||||
visibility: sdk.Visibility.public,
|
||||
// roomAliasName: controller.text.isNotEmpty
|
||||
// ? "${matrix.client.userID!.localpart}-${controller.text.trim().toLowerCase().replaceAll(' ', '_')}"
|
||||
// : null,
|
||||
roomAliasName: ClassCodeUtil.generateClassCode(),
|
||||
name: controller.text.isNotEmpty ? controller.text : null,
|
||||
);
|
||||
|
||||
if (rulesEditorKey.currentState != null) {
|
||||
await rulesEditorKey.currentState!.setRoomRules(roomID);
|
||||
} else {
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(m: "Null rules editor state");
|
||||
}
|
||||
if (classSettingsKey.currentState != null) {
|
||||
await classSettingsKey.currentState!.setClassSettings(
|
||||
roomID,
|
||||
);
|
||||
} else {
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(m: "Null class settings state");
|
||||
}
|
||||
return roomID;
|
||||
},
|
||||
onError: (e) {
|
||||
debugger(when: kDebugMode);
|
||||
return e;
|
||||
},
|
||||
);
|
||||
|
||||
if (roomID.error == null && roomID.result is String) {
|
||||
pangeaController.classController.setActiveSpaceIdInChatListController(
|
||||
roomID.result!,
|
||||
);
|
||||
context.push('/spaces/${roomID.result!}');
|
||||
} else {
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(e: roomID.error, s: StackTrace.current);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
// TODO: implement initState
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => NewSpaceView(this);
|
||||
}
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/constants/class_default_values.dart';
|
||||
import 'package:fluffychat/pangea/pages/new_class/new_class.dart';
|
||||
import 'package:fluffychat/widgets/layouts/max_width_body.dart';
|
||||
import '../../widgets/space/class_settings.dart';
|
||||
import '../class_settings/p_class_widgets/room_rules_editor.dart';
|
||||
|
||||
class NewSpaceView extends StatelessWidget {
|
||||
// #Pangea
|
||||
// final NewSpaceController controller;
|
||||
final NewClassController controller;
|
||||
// Pangea#
|
||||
|
||||
const NewSpaceView(this.controller, {super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
// #Pangea
|
||||
centerTitle: true,
|
||||
// Pangea#
|
||||
title: Text(L10n.of(context)!.createNewClass),
|
||||
),
|
||||
body: MaxWidthBody(
|
||||
// #Pangea
|
||||
child: ListView(
|
||||
// child: Column(
|
||||
// mainAxisSize: MainAxisSize.min,
|
||||
// #Pangea
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: TextField(
|
||||
// #Pangea
|
||||
maxLength: ClassDefaultValues.maxClassName,
|
||||
maxLengthEnforcement: MaxLengthEnforcement.enforced,
|
||||
// #Pangea
|
||||
controller: controller.controller,
|
||||
autofocus: true,
|
||||
autocorrect: false,
|
||||
textInputAction: TextInputAction.go,
|
||||
onSubmitted: controller.submitAction,
|
||||
decoration: InputDecoration(
|
||||
labelText: L10n.of(context)!.spaceName,
|
||||
prefixIcon: const Icon(Icons.people_outlined),
|
||||
hintText: L10n.of(context)!.enterASpacepName,
|
||||
),
|
||||
),
|
||||
),
|
||||
// #Pangea
|
||||
ClassSettings(
|
||||
key: controller.classSettingsKey,
|
||||
roomId: null,
|
||||
startOpen: true,
|
||||
),
|
||||
RoomRulesEditor(
|
||||
key: controller.rulesEditorKey,
|
||||
roomId: null,
|
||||
),
|
||||
const SizedBox(height: 45),
|
||||
// SwitchListTile.adaptive(
|
||||
// title: Text(L10n.of(context)!.spaceIsPublic),
|
||||
// value: controller.publicGroup,
|
||||
// onChanged: controller.setPublicGroup,
|
||||
// ),
|
||||
// ListTile(
|
||||
// trailing: const Padding(
|
||||
// padding: EdgeInsets.symmetric(horizontal: 16.0),
|
||||
// child: Icon(Icons.info_outlined),
|
||||
// ),
|
||||
// subtitle: Text(L10n.of(context)!.newSpaceDescription),
|
||||
// ),
|
||||
// #Pangea
|
||||
],
|
||||
),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: controller.submitAction,
|
||||
child: const Icon(Icons.arrow_forward_outlined),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
|
||||
import 'package:fluffychat/pangea/pages/settings_learning/settings_learning_view.dart';
|
||||
import 'package:fluffychat/pangea/widgets/user_settings/p_language_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SettingsLearning extends StatefulWidget {
|
||||
const SettingsLearning({super.key});
|
||||
|
|
@ -32,6 +32,11 @@ class SettingsLearningController extends State<SettingsLearning> {
|
|||
});
|
||||
}
|
||||
|
||||
Future<void> changeLanguage() async {
|
||||
await pLanguageDialog(context, () {});
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ class SettingsLearningView extends StatelessWidget {
|
|||
withScrolling: true,
|
||||
child: Column(
|
||||
children: [
|
||||
LanguageTile(),
|
||||
LanguageTile(controller),
|
||||
CountryPickerTile(),
|
||||
const SizedBox(height: 8),
|
||||
const Divider(height: 1),
|
||||
|
|
@ -65,7 +65,7 @@ class SettingsLearningView extends StatelessWidget {
|
|||
defaultValue: controller.pangeaController.pStoreService.read(
|
||||
PLocalKey.autoPlayMessages,
|
||||
) ??
|
||||
true,
|
||||
false,
|
||||
title: L10n.of(context)!.autoPlayTitle,
|
||||
subtitle: L10n.of(context)!.autoPlayDesc,
|
||||
pStoreKey: PLocalKey.autoPlayMessages,
|
||||
|
|
|
|||
|
|
@ -1,55 +0,0 @@
|
|||
import '../config/environment.dart';
|
||||
import '../network/requests.dart';
|
||||
import '../network/urls.dart';
|
||||
|
||||
class MessageServiceRepo {
|
||||
static Future<void> sendPayloads(
|
||||
MessageServiceModel serviceModel,
|
||||
String messageId,
|
||||
) async {
|
||||
final Requests req = Requests(
|
||||
baseUrl: Environment.choreoApi,
|
||||
choreoApiKey: Environment.choreoApiKey,
|
||||
);
|
||||
|
||||
final json = serviceModel.toJson();
|
||||
json["msg_id"] = messageId;
|
||||
|
||||
await req.post(url: PApiUrls.messageService, body: json);
|
||||
}
|
||||
}
|
||||
|
||||
class MessageServiceModel {
|
||||
List<int> payloadIds;
|
||||
String? messageId;
|
||||
String message;
|
||||
String userId;
|
||||
String roomId;
|
||||
String? classId;
|
||||
String? l1Lang;
|
||||
String l2Lang;
|
||||
|
||||
MessageServiceModel({
|
||||
required this.payloadIds,
|
||||
required this.messageId,
|
||||
required this.message,
|
||||
required this.userId,
|
||||
required this.roomId,
|
||||
required this.classId,
|
||||
required this.l1Lang,
|
||||
required this.l2Lang,
|
||||
});
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
'payload_ids': payloadIds,
|
||||
'msg_id': messageId,
|
||||
'message': message,
|
||||
'user_id': userId,
|
||||
'room_id': roomId,
|
||||
'class_id': classId,
|
||||
'l1_lang': l1Lang,
|
||||
'l2_lang': l2Lang,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,8 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
bool canAddToSpace(Room space, PangeaController pangeaController) {
|
||||
final bool pangeaPermission =
|
||||
|
|
@ -27,8 +25,9 @@ Future<void> pangeaAddToSpace(
|
|||
Room space,
|
||||
List<String> selectedRoomIds,
|
||||
BuildContext context,
|
||||
PangeaController pangeaController,
|
||||
) async {
|
||||
PangeaController pangeaController, {
|
||||
bool suggested = true,
|
||||
}) async {
|
||||
if (!canAddToSpace(space, pangeaController)) {
|
||||
throw L10n.of(context)!.noAddToSpacePermissions;
|
||||
}
|
||||
|
|
@ -37,6 +36,6 @@ Future<void> pangeaAddToSpace(
|
|||
if (room != null && chatIsInSpace(room, space)) {
|
||||
throw L10n.of(context)!.alreadyInSpace;
|
||||
}
|
||||
await space.setSpaceChild(roomId);
|
||||
await space.setSpaceChild(roomId, suggested: suggested);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
Future<void> archiveSpace(Room? space, Client client) async {
|
||||
if (space == null) {
|
||||
|
|
@ -14,6 +13,9 @@ Future<void> archiveSpace(Room? space, Client client) async {
|
|||
|
||||
final List<Room> children = await space.getChildRooms();
|
||||
for (final Room child in children) {
|
||||
if (child.isUnread) {
|
||||
await child.markUnread(false);
|
||||
}
|
||||
await child.leave();
|
||||
}
|
||||
await space.leave();
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ void chatListHandleSpaceTap(
|
|||
controller.setActiveSpace(space.id);
|
||||
|
||||
if (FluffyThemes.isColumnMode(context)) {
|
||||
context.push('/spaces/${space.id}');
|
||||
context.go('/rooms/${space.id}/details');
|
||||
} else if (controller.activeChat != null &&
|
||||
!space.isFirstOrSecondChild(controller.activeChat!)) {
|
||||
context.go("/rooms");
|
||||
|
|
@ -65,6 +65,9 @@ void chatListHandleSpaceTap(
|
|||
context: context,
|
||||
future: () async {
|
||||
await space.join();
|
||||
if (space.isSpace) {
|
||||
await space.joinAnalyticsRoomsInSpace();
|
||||
}
|
||||
setActiveSpaceAndCloseChat();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
|
|
@ -108,6 +111,9 @@ void chatListHandleSpaceTap(
|
|||
showAlertDialog(context);
|
||||
}
|
||||
break;
|
||||
case Membership.leave:
|
||||
autoJoin(space);
|
||||
break;
|
||||
default:
|
||||
setActiveSpaceAndCloseChat();
|
||||
ErrorHandler.logError(
|
||||
|
|
|
|||
|
|
@ -1,89 +0,0 @@
|
|||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/constants/class_default_values.dart';
|
||||
import 'error_handler.dart';
|
||||
|
||||
Future<void> deleteRoom(String? roomID, Client client) async {
|
||||
if (roomID == null) {
|
||||
ErrorHandler.logError(
|
||||
m: "in deleteRoomAction with null pangeaClassRoomID",
|
||||
s: StackTrace.current,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final Room? room = client.getRoomById(roomID);
|
||||
if (room == null) {
|
||||
ErrorHandler.logError(
|
||||
m: "failed to fetch room with roomID $roomID",
|
||||
s: StackTrace.current,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await room.join();
|
||||
} catch (err) {
|
||||
ErrorHandler.logError(
|
||||
m: "failed to join room with roomID $roomID",
|
||||
s: StackTrace.current,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
List<User> members;
|
||||
try {
|
||||
members = await room.requestParticipants();
|
||||
} catch (err) {
|
||||
ErrorHandler.logError(
|
||||
m: "failed to fetch members for room with roomID $roomID",
|
||||
s: StackTrace.current,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final List<User> otherAdmins = [];
|
||||
for (final User member in members) {
|
||||
final String memberID = member.id;
|
||||
final int memberPowerLevel = room.getPowerLevelByUserId(memberID);
|
||||
if (memberID == client.userID) continue;
|
||||
if (memberPowerLevel >= ClassDefaultValues.powerLevelOfAdmin) {
|
||||
otherAdmins.add(member);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
await room.kick(memberID);
|
||||
} catch (err) {
|
||||
ErrorHandler.logError(
|
||||
m: "Failed to kick user $memberID from room with id $roomID. Error: $err",
|
||||
s: StackTrace.current,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (otherAdmins.isNotEmpty && room.canSendEvent(EventTypes.RoomJoinRules)) {
|
||||
try {
|
||||
await client.setRoomStateWithKey(
|
||||
roomID,
|
||||
EventTypes.RoomJoinRules,
|
||||
"",
|
||||
{"join_rules": "invite"},
|
||||
);
|
||||
} catch (err) {
|
||||
ErrorHandler.logError(
|
||||
m: "Failed to update student create room permissions. error: $err, roomId: $roomID",
|
||||
s: StackTrace.current,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await room.leave();
|
||||
} catch (err) {
|
||||
ErrorHandler.logError(
|
||||
m: "Failed to leave room with id $roomID. Error: $err",
|
||||
s: StackTrace.current,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -43,6 +43,7 @@ class GetChatListItemSubtitle {
|
|||
}
|
||||
|
||||
if (!pangeaController.languageController.languagesSet ||
|
||||
event.redacted ||
|
||||
event.type != EventTypes.Message ||
|
||||
event.messageType != MessageTypes.Text ||
|
||||
!pangeaController.permissionsController
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ void setClassDisplayname(BuildContext context, String? roomId) async {
|
|||
: L10n.of(context)!.changeTheNameOfTheChat,
|
||||
),
|
||||
content: TextField(
|
||||
maxLength: 32,
|
||||
controller: textFieldController,
|
||||
),
|
||||
actions: [
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
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/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';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
|
@ -41,27 +45,27 @@ class MessageSpeechToTextCardState extends State<MessageSpeechToTextCard> {
|
|||
// look for transcription in message event
|
||||
// if not found, call API to transcribe audio
|
||||
Future<void> getSpeechToText() async {
|
||||
// try {
|
||||
if (l1Code == null || l2Code == null) {
|
||||
throw Exception('Language selection not found');
|
||||
}
|
||||
speechToTextResponse ??=
|
||||
await widget.messageEvent.getSpeechToText(l1Code!, l2Code!);
|
||||
try {
|
||||
if (l1Code == null || l2Code == null) {
|
||||
throw Exception('Language selection not found');
|
||||
}
|
||||
speechToTextResponse ??=
|
||||
await widget.messageEvent.getSpeechToText(l1Code!, l2Code!);
|
||||
|
||||
debugPrint(
|
||||
'Speech to text transcript: ${speechToTextResponse?.transcript.text}',
|
||||
);
|
||||
// } catch (e, s) {
|
||||
// debugger(when: kDebugMode);
|
||||
// error = e;
|
||||
// ErrorHandler.logError(
|
||||
// e: e,
|
||||
// s: s,
|
||||
// data: widget.messageEvent.event.content,
|
||||
// );
|
||||
// } finally {
|
||||
setState(() => _fetchingTranscription = false);
|
||||
// }
|
||||
debugPrint(
|
||||
'Speech to text transcript: ${speechToTextResponse?.transcript.text}',
|
||||
);
|
||||
} catch (e, s) {
|
||||
debugger(when: kDebugMode);
|
||||
error = e;
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
s: s,
|
||||
data: widget.messageEvent.event.content,
|
||||
);
|
||||
} finally {
|
||||
setState(() => _fetchingTranscription = false);
|
||||
}
|
||||
}
|
||||
|
||||
TextSpan _buildTranscriptText(BuildContext context) {
|
||||
|
|
@ -133,6 +137,9 @@ class MessageSpeechToTextCardState extends State<MessageSpeechToTextCard> {
|
|||
getSpeechToText();
|
||||
}
|
||||
|
||||
String? get wordsPerMinuteString =>
|
||||
speechToTextResponse?.transcript.wordsPerMinute?.toStringAsFixed(2);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_fetchingTranscription) {
|
||||
|
|
@ -158,11 +165,11 @@ class MessageSpeechToTextCardState extends State<MessageSpeechToTextCard> {
|
|||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
IconNumberWidget(
|
||||
icon: Icons.abc,
|
||||
number: (selectedToken == null ? words : 1).toString(),
|
||||
toolTip: L10n.of(context)!.words,
|
||||
),
|
||||
// IconNumberWidget(
|
||||
// icon: Icons.abc,
|
||||
// number: (selectedToken == null ? words : 1).toString(),
|
||||
// toolTip: L10n.of(context)!.words,
|
||||
// ),
|
||||
IconNumberWidget(
|
||||
icon: Symbols.target,
|
||||
number:
|
||||
|
|
@ -171,8 +178,9 @@ class MessageSpeechToTextCardState extends State<MessageSpeechToTextCard> {
|
|||
),
|
||||
IconNumberWidget(
|
||||
icon: Icons.speed,
|
||||
number: (selectedToken?.confidence ?? total).toString(),
|
||||
toolTip: L10n.of(context)!.points,
|
||||
number:
|
||||
wordsPerMinuteString != null ? "$wordsPerMinuteString" : "??",
|
||||
toolTip: L10n.of(context)!.wordsPerMinute,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import 'package:fluffychat/pangea/widgets/chat/message_translation_card.dart';
|
|||
import 'package:fluffychat/pangea/widgets/chat/message_unsubscribed_card.dart';
|
||||
import 'package:fluffychat/pangea/widgets/chat/overlay_message.dart';
|
||||
import 'package:fluffychat/pangea/widgets/igc/word_data_card.dart';
|
||||
import 'package:fluffychat/pangea/widgets/user_settings/p_language_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
|
@ -62,6 +63,10 @@ class ToolbarDisplayController {
|
|||
if (controller.selectMode) {
|
||||
controller.clearSelectedEvents();
|
||||
}
|
||||
if (!MatrixState.pangeaController.languageController.languagesSet) {
|
||||
pLanguageDialog(context, () {});
|
||||
return;
|
||||
}
|
||||
focusNode.requestFocus();
|
||||
|
||||
final LayerLinkAndKey layerLinkAndKey =
|
||||
|
|
@ -129,8 +134,11 @@ class ToolbarDisplayController {
|
|||
});
|
||||
}
|
||||
|
||||
bool get highlighted =>
|
||||
MatrixState.pAnyState.overlay.hashCode.toString() == overlayId;
|
||||
bool get highlighted {
|
||||
if (overlayId == null) return false;
|
||||
if (MatrixState.pAnyState.overlay == null) overlayId = null;
|
||||
return MatrixState.pAnyState.overlay.hashCode.toString() == overlayId;
|
||||
}
|
||||
}
|
||||
|
||||
class MessageToolbar extends StatefulWidget {
|
||||
|
|
@ -167,6 +175,19 @@ class MessageToolbarState extends State<MessageToolbar> {
|
|||
debugPrint("updating toolbar mode");
|
||||
final bool subscribed =
|
||||
MatrixState.pangeaController.subscriptionController.isSubscribed;
|
||||
|
||||
if (!newMode.isValidMode(widget.pangeaMessageEvent.event)) {
|
||||
ErrorHandler.logError(
|
||||
e: "Invalid mode for event",
|
||||
s: StackTrace.current,
|
||||
data: {
|
||||
"newMode": newMode,
|
||||
"event": widget.pangeaMessageEvent.event,
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
currentMode = newMode;
|
||||
updatingMode = true;
|
||||
|
|
@ -269,12 +290,14 @@ class MessageToolbarState extends State<MessageToolbar> {
|
|||
PLocalKey.autoPlayMessages,
|
||||
) ??
|
||||
true;
|
||||
|
||||
if (widget.pangeaMessageEvent.isAudioMessage) {
|
||||
updateMode(MessageMode.speechToText);
|
||||
return;
|
||||
}
|
||||
|
||||
autoplay
|
||||
? updateMode(
|
||||
widget.pangeaMessageEvent.isAudioMessage
|
||||
? MessageMode.speechToText
|
||||
: MessageMode.textToSpeech,
|
||||
)
|
||||
? updateMode(MessageMode.textToSpeech)
|
||||
: updateMode(MessageMode.translation);
|
||||
});
|
||||
|
||||
|
|
@ -345,8 +368,11 @@ class MessageToolbarState extends State<MessageToolbar> {
|
|||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: MessageMode.values.map((mode) {
|
||||
if ([MessageMode.definition, MessageMode.textToSpeech, MessageMode.translation]
|
||||
.contains(mode) &&
|
||||
if ([
|
||||
MessageMode.definition,
|
||||
MessageMode.textToSpeech,
|
||||
MessageMode.translation,
|
||||
].contains(mode) &&
|
||||
widget.pangeaMessageEvent.isAudioMessage) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -141,6 +141,7 @@ class OverlayMessage extends StatelessWidget {
|
|||
pangeaMessageEvent: pangeaMessageEvent,
|
||||
immersionMode: immersionMode,
|
||||
toolbarController: toolbarController,
|
||||
isOverlay: true,
|
||||
),
|
||||
if (event.hasAggregatedEvents(
|
||||
timeline,
|
||||
|
|
|
|||
|
|
@ -235,8 +235,13 @@ class AddToSpaceState extends State<AddToSpaceToggles> {
|
|||
),
|
||||
activeColor: AppConfig.activeToggleColor,
|
||||
value: isSuggestedInSpace(possibleParent),
|
||||
onChanged: (bool suggest) =>
|
||||
setSuggested(suggest, possibleParent),
|
||||
onChanged: (bool suggest) => canAdd
|
||||
? setSuggested(suggest, possibleParent)
|
||||
: ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(L10n.of(context)!.noPermission),
|
||||
),
|
||||
),
|
||||
)
|
||||
: Container(),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -48,28 +48,33 @@ class PangeaRichTextState extends State<PangeaRichText> {
|
|||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
updateTextSpan();
|
||||
}
|
||||
|
||||
void updateTextSpan() {
|
||||
setState(() {
|
||||
textSpan = getTextSpan();
|
||||
});
|
||||
setTextSpan();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(PangeaRichText oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
updateTextSpan();
|
||||
setTextSpan();
|
||||
}
|
||||
|
||||
String getTextSpan() {
|
||||
void _setTextSpan(String newTextSpan) {
|
||||
widget.toolbarController?.toolbar?.textSelection.setMessageText(
|
||||
newTextSpan,
|
||||
);
|
||||
setState(() {
|
||||
textSpan = newTextSpan;
|
||||
});
|
||||
}
|
||||
|
||||
void setTextSpan() {
|
||||
if (_fetchingRepresentation == true) {
|
||||
return widget.pangeaMessageEvent.body;
|
||||
_setTextSpan(textSpan = widget.pangeaMessageEvent.body);
|
||||
return;
|
||||
}
|
||||
|
||||
if (repEvent != null) {
|
||||
return repEvent!.text;
|
||||
_setTextSpan(repEvent!.text);
|
||||
return;
|
||||
}
|
||||
|
||||
if (widget.pangeaMessageEvent.eventId.contains("webdebug")) {
|
||||
|
|
@ -84,7 +89,6 @@ class PangeaRichTextState extends State<PangeaRichText> {
|
|||
|
||||
if (repEvent == null) {
|
||||
setState(() => _fetchingRepresentation = true);
|
||||
|
||||
widget.pangeaMessageEvent
|
||||
.representationByLanguageGlobal(
|
||||
langCode: widget.pangeaMessageEvent.messageDisplayLangCode,
|
||||
|
|
@ -95,23 +99,17 @@ class PangeaRichTextState extends State<PangeaRichText> {
|
|||
)
|
||||
.then((event) {
|
||||
repEvent = event;
|
||||
widget.toolbarController?.toolbar?.textSelection.setMessageText(
|
||||
repEvent?.text ?? widget.pangeaMessageEvent.body,
|
||||
);
|
||||
_setTextSpan(repEvent?.text ?? widget.pangeaMessageEvent.body);
|
||||
}).whenComplete(() {
|
||||
if (mounted) {
|
||||
setState(() => _fetchingRepresentation = false);
|
||||
}
|
||||
});
|
||||
return widget.pangeaMessageEvent.body;
|
||||
} else {
|
||||
widget.toolbarController?.toolbar?.textSelection.setMessageText(
|
||||
repEvent!.text,
|
||||
);
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
return repEvent!.text;
|
||||
_setTextSpan(widget.pangeaMessageEvent.body);
|
||||
} else {
|
||||
_setTextSpan(repEvent!.text);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -190,7 +188,10 @@ class PangeaRichTextState extends State<PangeaRichText> {
|
|||
|
||||
return blur > 0
|
||||
? ImageFiltered(
|
||||
imageFilter: ImageFilter.blur(sigmaX: blur, sigmaY: blur),
|
||||
imageFilter: ImageFilter.blur(
|
||||
sigmaX: blur,
|
||||
sigmaY: blur,
|
||||
),
|
||||
child: richText,
|
||||
)
|
||||
: richText;
|
||||
|
|
|
|||
|
|
@ -314,6 +314,23 @@ class PartOfSpeechBlock extends StatelessWidget {
|
|||
required this.languageType,
|
||||
});
|
||||
|
||||
String get exampleSentence => languageType == LanguageType.target
|
||||
? wordData.targetExampleSentence
|
||||
: wordData.baseExampleSentence;
|
||||
|
||||
String get definition => languageType == LanguageType.target
|
||||
? wordData.targetDefinition
|
||||
: wordData.baseDefinition;
|
||||
|
||||
String formattedTitle(BuildContext context) {
|
||||
final String word = languageType == LanguageType.target
|
||||
? wordData.targetWord
|
||||
: wordData.baseWord;
|
||||
String? pos = wordData.formattedPartOfSpeech(languageType);
|
||||
if (pos == null || pos.isEmpty) pos = L10n.of(context)!.unkDisplayName;
|
||||
return "$word (${wordData.formattedPartOfSpeech(languageType)})";
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
|
|
@ -324,9 +341,7 @@ class PartOfSpeechBlock extends StatelessWidget {
|
|||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
languageType == LanguageType.target
|
||||
? "${wordData.targetWord} (${wordData.formattedPartOfSpeech(languageType)})"
|
||||
: "${wordData.baseWord} (${wordData.formattedPartOfSpeech(languageType)})",
|
||||
formattedTitle(context),
|
||||
style: BotStyle.text(context, italics: true, bold: false),
|
||||
),
|
||||
),
|
||||
|
|
@ -337,47 +352,43 @@ class PartOfSpeechBlock extends StatelessWidget {
|
|||
alignment: Alignment.centerLeft,
|
||||
child: Column(
|
||||
children: [
|
||||
RichText(
|
||||
text: TextSpan(
|
||||
style: BotStyle.text(
|
||||
context,
|
||||
italics: false,
|
||||
bold: false,
|
||||
if (definition.isNotEmpty)
|
||||
RichText(
|
||||
text: TextSpan(
|
||||
style: BotStyle.text(
|
||||
context,
|
||||
italics: false,
|
||||
bold: false,
|
||||
),
|
||||
children: <TextSpan>[
|
||||
TextSpan(
|
||||
text: "${L10n.of(context)!.definition}: ",
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
TextSpan(text: definition),
|
||||
],
|
||||
),
|
||||
children: <TextSpan>[
|
||||
TextSpan(
|
||||
text: "${L10n.of(context)!.definition}: ",
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
TextSpan(
|
||||
text: languageType == LanguageType.target
|
||||
? wordData.targetDefinition
|
||||
: wordData.baseDefinition,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
RichText(
|
||||
text: TextSpan(
|
||||
style: BotStyle.text(
|
||||
context,
|
||||
italics: false,
|
||||
bold: false,
|
||||
if (exampleSentence.isNotEmpty)
|
||||
RichText(
|
||||
text: TextSpan(
|
||||
style: BotStyle.text(
|
||||
context,
|
||||
italics: false,
|
||||
bold: false,
|
||||
),
|
||||
children: <TextSpan>[
|
||||
TextSpan(
|
||||
text: "${L10n.of(context)!.exampleSentence}: ",
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
TextSpan(text: exampleSentence),
|
||||
],
|
||||
),
|
||||
children: <TextSpan>[
|
||||
TextSpan(
|
||||
text: "${L10n.of(context)!.exampleSentence}: ",
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
TextSpan(
|
||||
text: languageType == LanguageType.target
|
||||
? wordData.targetExampleSentence
|
||||
: wordData.baseExampleSentence,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
|
||||
import 'package:fluffychat/pangea/models/language_model.dart';
|
||||
import 'package:fluffychat/pangea/pages/settings_learning/settings_learning.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
import '../flag.dart';
|
||||
import 'p_language_dialog.dart';
|
||||
|
||||
//PTODO - move this to settings_learning_view.dart and make callback a setState
|
||||
|
||||
class LanguageTile extends StatelessWidget {
|
||||
final SettingsLearningController learningController;
|
||||
final PangeaController pangeaController = MatrixState.pangeaController;
|
||||
|
||||
LanguageTile({super.key});
|
||||
LanguageTile(this.learningController, {super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
@ -81,7 +81,9 @@ class LanguageTile extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
trailing: const Icon(Icons.edit_outlined),
|
||||
onTap: () => pLanguageDialog(context, () {}),
|
||||
onTap: () async {
|
||||
learningController.changeLanguage();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import '../../../widgets/matrix.dart';
|
|||
import 'p_language_dropdown.dart';
|
||||
import 'p_question_container.dart';
|
||||
|
||||
pLanguageDialog(BuildContext parentContext, Function callback) {
|
||||
pLanguageDialog(BuildContext parentContext, Function callback) async {
|
||||
final PangeaController pangeaController = MatrixState.pangeaController;
|
||||
//PTODO: if source language not set by user, default to languge from device settings
|
||||
final LanguageModel? userL1 = pangeaController.languageController.userL1;
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ class ChatSettingsPopupMenuState extends State<ChatSettingsPopupMenu> {
|
|||
value: 'learning_settings',
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.settings),
|
||||
const Icon(Icons.psychology_outlined),
|
||||
const SizedBox(width: 12),
|
||||
Text(L10n.of(context)!.learningSettings),
|
||||
],
|
||||
|
|
@ -83,19 +83,22 @@ class ChatSettingsPopupMenuState extends State<ChatSettingsPopupMenu> {
|
|||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem<String>(
|
||||
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 (!widget.room.isArchived)
|
||||
// Pangea#
|
||||
PopupMenuItem<String>(
|
||||
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<String>(
|
||||
|
|
|
|||
|
|
@ -821,7 +821,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -836,7 +835,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"be": [
|
||||
|
|
@ -2256,7 +2259,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -2271,7 +2273,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"bn": [
|
||||
|
|
@ -3153,7 +3159,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -3168,7 +3173,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"bo": [
|
||||
|
|
@ -4050,7 +4059,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -4065,7 +4073,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"ca": [
|
||||
|
|
@ -4947,7 +4959,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -4962,7 +4973,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"cs": [
|
||||
|
|
@ -5844,7 +5859,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -5859,7 +5873,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"de": [
|
||||
|
|
@ -6688,7 +6706,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -6703,7 +6720,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"el": [
|
||||
|
|
@ -7585,7 +7606,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -7600,7 +7620,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"eo": [
|
||||
|
|
@ -8482,7 +8506,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -8497,37 +8520,14 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"es": [
|
||||
"presenceStyle",
|
||||
"presencesToggle",
|
||||
"writeAMessageFlag",
|
||||
"youInvitedToBy",
|
||||
"hidePresences",
|
||||
"sendReadReceipts",
|
||||
"sendTypingNotificationsDescription",
|
||||
"sendReadReceiptsDescription",
|
||||
"formattedMessages",
|
||||
"formattedMessagesDescription",
|
||||
"verifyOtherUser",
|
||||
"verifyOtherUserDescription",
|
||||
"verifyOtherDevice",
|
||||
"verifyOtherDeviceDescription",
|
||||
"transparent",
|
||||
"incomingMessages",
|
||||
"stickers",
|
||||
"commandHint_ignore",
|
||||
"commandHint_unignore",
|
||||
"unreadChatsInApp",
|
||||
"messageAnalytics",
|
||||
"words",
|
||||
"score",
|
||||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -8542,7 +8542,9 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing"
|
||||
],
|
||||
|
||||
"et": [
|
||||
|
|
@ -9367,7 +9369,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -9382,7 +9383,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"eu": [
|
||||
|
|
@ -10207,7 +10212,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -10222,7 +10226,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"fa": [
|
||||
|
|
@ -11104,7 +11112,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -11119,7 +11126,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"fi": [
|
||||
|
|
@ -12001,7 +12012,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -12016,7 +12026,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"fr": [
|
||||
|
|
@ -12898,7 +12912,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -12913,7 +12926,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"ga": [
|
||||
|
|
@ -13795,7 +13812,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -13810,7 +13826,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"gl": [
|
||||
|
|
@ -14635,7 +14655,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -14650,7 +14669,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"he": [
|
||||
|
|
@ -15532,7 +15555,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -15547,7 +15569,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"hi": [
|
||||
|
|
@ -16429,7 +16455,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -16444,7 +16469,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"hr": [
|
||||
|
|
@ -17313,7 +17342,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -17328,7 +17356,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"hu": [
|
||||
|
|
@ -18210,7 +18242,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -18225,7 +18256,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"ia": [
|
||||
|
|
@ -19631,7 +19666,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -19646,7 +19680,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"id": [
|
||||
|
|
@ -20528,7 +20566,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -20543,7 +20580,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"ie": [
|
||||
|
|
@ -21425,7 +21466,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -21440,7 +21480,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"it": [
|
||||
|
|
@ -22307,7 +22351,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -22322,7 +22365,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"ja": [
|
||||
|
|
@ -23204,7 +23251,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -23219,7 +23265,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"ko": [
|
||||
|
|
@ -24101,7 +24151,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -24116,7 +24165,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"lt": [
|
||||
|
|
@ -24998,7 +25051,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -25013,7 +25065,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"lv": [
|
||||
|
|
@ -25895,7 +25951,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -25910,7 +25965,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"nb": [
|
||||
|
|
@ -26792,7 +26851,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -26807,7 +26865,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"nl": [
|
||||
|
|
@ -27689,7 +27751,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -27704,7 +27765,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"pl": [
|
||||
|
|
@ -28586,7 +28651,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -28601,7 +28665,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"pt": [
|
||||
|
|
@ -29483,7 +29551,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -29498,7 +29565,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"pt_BR": [
|
||||
|
|
@ -30349,7 +30420,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -30364,7 +30434,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"pt_PT": [
|
||||
|
|
@ -31246,7 +31320,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -31261,7 +31334,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"ro": [
|
||||
|
|
@ -32143,7 +32220,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -32158,7 +32234,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"ru": [
|
||||
|
|
@ -32983,7 +33063,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -32998,7 +33077,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"sk": [
|
||||
|
|
@ -33880,7 +33963,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -33895,7 +33977,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"sl": [
|
||||
|
|
@ -34777,7 +34863,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -34792,7 +34877,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"sr": [
|
||||
|
|
@ -35674,7 +35763,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -35689,7 +35777,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"sv": [
|
||||
|
|
@ -36536,7 +36628,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -36551,7 +36642,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"ta": [
|
||||
|
|
@ -37433,7 +37528,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -37448,7 +37542,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"th": [
|
||||
|
|
@ -38330,7 +38428,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -38345,7 +38442,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"tr": [
|
||||
|
|
@ -39212,7 +39313,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -39227,7 +39327,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"uk": [
|
||||
|
|
@ -40052,7 +40156,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -40067,7 +40170,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"vi": [
|
||||
|
|
@ -40949,7 +41056,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -40964,7 +41070,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"zh": [
|
||||
|
|
@ -41789,7 +41899,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -41804,7 +41913,11 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
],
|
||||
|
||||
"zh_Hant": [
|
||||
|
|
@ -42686,7 +42799,6 @@
|
|||
"accuracy",
|
||||
"points",
|
||||
"noPaymentInfo",
|
||||
"updatePhoneOS",
|
||||
"conversationBotModeSelectDescription",
|
||||
"conversationBotModeSelectOption_discussion",
|
||||
"conversationBotModeSelectOption_custom",
|
||||
|
|
@ -42701,6 +42813,10 @@
|
|||
"conversationBotDiscussionZone_discussionTriggerScheduleEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerScheduleHourIntervalLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionEnabledLabel",
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel"
|
||||
"conversationBotDiscussionZone_discussionTriggerReactionKeyLabel",
|
||||
"studentAnalyticsNotAvailable",
|
||||
"roomDataMissing",
|
||||
"updatePhoneOS",
|
||||
"wordsPerMinute"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
52
pubspec.lock
52
pubspec.lock
|
|
@ -1841,50 +1841,58 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: record
|
||||
sha256: f703397f5a60d9b2b655b3acc94ba079b2d9a67dc0725bdb90ef2fee2441ebf7
|
||||
sha256: "113b368168c49c78902ab37c2b354dea30a0aec5bdeca434073826b6ea73eca1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.4.4"
|
||||
version: "5.0.5"
|
||||
record_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: record_android
|
||||
sha256: "0df98e05873b22b443309e289bf1eb3b5b9a60e7779134334e2073eb0763a992"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
record_darwin:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: record_darwin
|
||||
sha256: ee8cb1bb1712d7ce38140ecabe70e5c286c02f05296d66043bee865ace7eb1b9
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
record_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: record_linux
|
||||
sha256: "348db92c4ec1b67b1b85d791381c8c99d7c6908de141e7c9edc20dad399b15ce"
|
||||
sha256: "7d0e70cd51635128fe9d37d89bafd6011d7cbba9af8dc323079ae60f23546aef"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.1"
|
||||
record_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: record_macos
|
||||
sha256: d1d0199d1395f05e218207e8cacd03eb9dc9e256ddfe2cfcbbb90e8edea06057
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.2"
|
||||
version: "0.7.1"
|
||||
record_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: record_platform_interface
|
||||
sha256: "7a2d4ce7ac3752505157e416e4e0d666a54b1d5d8601701b7e7e5e30bec181b4"
|
||||
sha256: "3a4b56e94ecd2a0b2b43eb1fa6f94c5b8484334f5d38ef43959c4bf97fb374cf"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.0"
|
||||
version: "1.0.2"
|
||||
record_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: record_web
|
||||
sha256: "219ffb4ca59b4338117857db56d3ffadbde3169bcaf1136f5f4d4656f4a2372d"
|
||||
sha256: "24847cdbcf999f7a5762170792f622ac844858766becd0f2370ec8ae22f7526e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.0"
|
||||
version: "1.0.5"
|
||||
record_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: record_windows
|
||||
sha256: "42d545155a26b20d74f5107648dbb3382dbbc84dc3f1adc767040359e57a1345"
|
||||
sha256: "39998b3ea7d8d28b04159d82220e6e5e32a7c357c6fb2794f5736beea272f6c3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.1"
|
||||
version: "1.0.2"
|
||||
remove_emoji:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1945,18 +1953,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: sentry
|
||||
sha256: e572d33a3ff1d69549f33ee828a8ff514047d43ca8eea4ab093d72461205aa3e
|
||||
sha256: fd1fbfe860c05f5c52820ec4dbf2b6473789e83ead26cfc18bca4fe80bf3f008
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.20.1"
|
||||
version: "8.2.0"
|
||||
sentry_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: sentry_flutter
|
||||
sha256: ac8cf6bb849f3560353ae33672e17b2713809a4e8de0d3cf372e9e9c42013757
|
||||
sha256: c64f0aec5332bec87083b61514d1b6b29e435b9045d03ce1575861192b9a5680
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.20.1"
|
||||
version: "8.2.0"
|
||||
share_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue